diff --git a/sdk/lib/editing/editable_string.dart b/sdk/lib/editing/editable_string.dart new file mode 100644 index 00000000000..728788ec52d --- /dev/null +++ b/sdk/lib/editing/editable_string.dart @@ -0,0 +1,122 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:mojom/keyboard/keyboard.mojom.dart'; + +typedef void StringUpdated(); + +class TextRange { + final int start; + final int end; + + TextRange({this.start, this.end}); + TextRange.collapsed(int position) + : start = position, + end = position; + const TextRange.empty() + : start = -1, + end = -1; + + bool get isValid => start >= 0 && end >= 0; + bool get isCollapsed => start == end; +} + +class EditableString implements KeyboardClient { + String text; + TextRange composing = const TextRange.empty(); + TextRange selection = const TextRange.empty(); + + final StringUpdated onUpdated; + + KeyboardClientStub stub; + + EditableString({this.text: '', this.onUpdated}) { + stub = new KeyboardClientStub.unbound()..impl = this; + } + + String textBefore(TextRange range) { + return text.substring(0, range.start); + } + + String textAfter(TextRange range) { + return text.substring(range.end); + } + + String textInside(TextRange range) { + return text.substring(range.start, range.end); + } + + void _delete(TextRange range) { + if (range.isCollapsed || !range.isValid) return; + text = textBefore(range) + textAfter(range); + } + + TextRange _append(String newText) { + int start = text.length; + text += newText; + return new TextRange(start: start, end: start + newText.length); + } + + TextRange _replace(TextRange range, String newText) { + assert(range.isValid); + + String before = textBefore(range); + String after = textAfter(range); + + text = before + newText + after; + return new TextRange( + start: before.length, end: before.length + newText.length); + } + + TextRange _replaceOrAppend(TextRange range, String newText) { + if (!range.isValid) return _append(newText); + return _replace(range, newText); + } + + void commitCompletion(CompletionData completion) { + // TODO(abarth): Not implemented. + } + + void commitCorrection(CorrectionData correction) { + // TODO(abarth): Not implemented. + } + + void commitText(String text, int newCursorPosition) { + // TODO(abarth): Why is |newCursorPosition| always 1? + TextRange committedRange = _replaceOrAppend(composing, text); + selection = new TextRange.collapsed(committedRange.end); + composing = const TextRange.empty(); + onUpdated(); + } + + void deleteSurroundingText(int beforeLength, int afterLength) { + TextRange beforeRange = new TextRange( + start: selection.start - beforeLength, end: selection.start); + TextRange afterRange = + new TextRange(start: selection.end, end: selection.end + afterLength); + _delete(afterRange); + _delete(beforeRange); + selection = new TextRange( + start: selection.start - beforeLength, + end: selection.end - beforeLength); + onUpdated(); + } + + void setComposingRegion(int start, int end) { + composing = new TextRange(start: start, end: end); + onUpdated(); + } + + void setComposingText(String text, int newCursorPosition) { + // TODO(abarth): Why is |newCursorPosition| always 1? + composing = _replaceOrAppend(composing, text); + selection = new TextRange.collapsed(composing.end); + onUpdated(); + } + + void setSelection(int start, int end) { + selection = new TextRange(start: start, end: end); + onUpdated(); + } +} diff --git a/sdk/lib/editing/editable_text.dart b/sdk/lib/editing/editable_text.dart new file mode 100644 index 00000000000..3f5bea54884 --- /dev/null +++ b/sdk/lib/editing/editable_text.dart @@ -0,0 +1,98 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'dart:async'; + +import '../widgets/basic.dart'; +import 'editable_string.dart'; + +class EditableText extends Component { + + EditableText({String key, this.value, this.focused}) + : super(key: key, stateful: true); + + // static final Style _cursorStyle = new Style(''' + // width: 2px; + // height: 1.2em; + // vertical-align: top; + // background-color: ${Blue[500]};''' + // ); + + // static final Style _composingStyle = new Style(''' + // text-decoration: underline;''' + // ); + + EditableString value; + bool focused; + + void syncFields(EditableText source) { + value = source.value; + focused = source.focused; + } + + Timer _cursorTimer; + bool _showCursor = false; + + void _cursorTick(Timer timer) { + setState(() { + _showCursor = !_showCursor; + }); + } + + void _startCursorTimer() { + _showCursor = true; + _cursorTimer = new Timer.periodic( + new Duration(milliseconds: 500), _cursorTick); + } + + void didUnmount() { + if (_cursorTimer != null) + _stopCursorTimer(); + super.didUnmount(); + } + + void _stopCursorTimer() { + _cursorTimer.cancel(); + _cursorTimer = null; + _showCursor = false; + } + + Widget build() { + if (focused && _cursorTimer == null) + _startCursorTimer(); + else if (!focused && _cursorTimer != null) + _stopCursorTimer(); + + //List children = new List(); + String hack = ""; + + if (!value.composing.isValid) { + // children.add(new TextFragment(value.text)); + hack += value.text; + } else { + hack += value.textBefore(value.composing); + hack += value.textInside(value.composing); + hack += value.textAfter(value.composing); + // if (!composing.isEmpty) { + // children.add(new TextFragment( + // composing, + // key: 'composing', + // style: _composingStyle + // )); + // } + + // String afterComposing = value.textAfter(value.composing); + // if (!afterComposing.isEmpty) + // children.add(new TextFragment(afterComposing)); + } + + // if (_showCursor) + // children.add(new Container( + // key: 'cursor', + // // style: _cursorStyle + // )); + + return new Text(hack); + } +} diff --git a/sdk/lib/editing/input.dart b/sdk/lib/editing/input.dart new file mode 100644 index 00000000000..12985f81f95 --- /dev/null +++ b/sdk/lib/editing/input.dart @@ -0,0 +1,101 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import '../widgets/basic.dart'; +import 'editable_string.dart'; +import 'editable_text.dart'; +import 'keyboard.dart'; + +typedef void ValueChanged(value); + +class Input extends Component { + + Input({String key, + this.placeholder, + this.onChanged, + this.focused}) + : super(key: key, stateful: true) { + _editableValue = new EditableString( + text: _value, + onUpdated: _handleTextUpdated + ); + } + + // static final Style _style = new Style(''' + // transform: translateX(0); + // margin: 8px; + // padding: 8px; + // border-bottom: 1px solid ${Grey[200]}; + // align-self: center; + // height: 1.2em; + // white-space: pre; + // overflow: hidden;''' + // ); + + // static final Style _placeholderStyle = new Style(''' + // top: 8px; + // left: 8px; + // position: absolute; + // ${typography.black.caption};''' + // ); + + // static final String _focusedInlineStyle = ''' + // padding: 7px; + // border-bottom: 2px solid ${Blue[500]};'''; + + String placeholder; + ValueChanged onChanged; + bool focused = false; + + void syncFields(Input source) { + placeholder = source.placeholder; + onChanged = source.onChanged; + focused = source.focused; + } + + String _value = ''; + bool _isAttachedToKeyboard = false; + EditableString _editableValue; + + void _handleTextUpdated() { + scheduleBuild(); + if (_value != _editableValue.text) { + _value = _editableValue.text; + if (onChanged != null) + onChanged(_value); + } + } + + Widget build() { + if (focused && !_isAttachedToKeyboard) { + keyboard.show(_editableValue.stub); + _isAttachedToKeyboard = true; + } + + List children = []; + + if (placeholder != null && _value.isEmpty) { + children.add(new Container( + // style: _placeholderStyle, + child: new Text(placeholder) + )); + } + + children.add(new EditableText(value: _editableValue, focused: focused)); + + return new Listener( + // style: _style, + // inlineStyle: focused ? _focusedInlineStyle : null, + child: new Stack(children), + onPointerDown: (_) => keyboard.showByRequest() + ); + } + + void didUnmount() { + if (_isAttachedToKeyboard) + keyboard.hide(); + super.didUnmount(); + } + +} diff --git a/sdk/lib/editing/keyboard.dart b/sdk/lib/editing/keyboard.dart new file mode 100644 index 00000000000..f64e89f5d3d --- /dev/null +++ b/sdk/lib/editing/keyboard.dart @@ -0,0 +1,21 @@ +// Copyright 2015 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +import 'package:mojom/keyboard/keyboard.mojom.dart'; + +import '../framework/shell.dart' as shell; + +class _KeyboardConnection { + KeyboardServiceProxy proxy; + + _KeyboardConnection() { + proxy = new KeyboardServiceProxy.unbound(); + shell.requestService("mojo:keyboard", proxy); + } + + KeyboardService get keyboard => proxy.ptr; +} + +final _KeyboardConnection _connection = new _KeyboardConnection(); +final KeyboardService keyboard = _connection.keyboard;