diff --git a/examples/stocks2/lib/stock_app.dart b/examples/stocks2/lib/stock_app.dart index 23905d6f2cb..7e68f94f813 100644 --- a/examples/stocks2/lib/stock_app.dart +++ b/examples/stocks2/lib/stock_app.dart @@ -8,9 +8,9 @@ import 'package:sky/framework/components2/drawer.dart'; // import 'package:sky/framework/components2/floating_action_button.dart'; // import 'package:sky/framework/components2/icon.dart'; import 'package:sky/framework/components2/icon_button.dart'; -// import 'package:sky/framework/components2/input.dart'; import 'package:sky/framework/components2/menu_divider.dart'; import 'package:sky/framework/components2/menu_item.dart'; +import 'package:sky/framework/components2/input.dart'; // import 'package:sky/framework/components2/modal_overlay.dart'; // import 'package:sky/framework/components2/popup_menu.dart'; // import 'package:sky/framework/components2/radio.dart'; @@ -30,9 +30,6 @@ enum StockMode { optimistic, pessimistic } class StocksApp extends App { - // static final Style _searchBarStyle = new Style(''' - // background-color: ${Grey[50]};'''); - // static final Style _titleStyle = new Style(''' // ${typography.white.title};'''); @@ -179,16 +176,16 @@ class StocksApp extends App { // TODO(abarth): Should we factor this into a SearchBar in the framework? UINode buildSearchBar() { - // return new StyleNode( - // new ToolBar( - // left: new IconButton( - // icon: 'navigation/arrow_back_grey600', - // onGestureTap: _handleSearchEnd), - // center: new Input( - // focused: true, - // placeholder: 'Search stocks', - // onChanged: _handleSearchQueryChanged)), - // _searchBarStyle); + return new ToolBar( + left: new IconButton( + icon: 'navigation/arrow_back_grey600', + onGestureTap: _handleSearchEnd), + center: new Input( + focused: true, + placeholder: 'Search stocks', + onChanged: _handleSearchQueryChanged), + backgroundColor: colors.Grey[50] + ); } void addMenuToOverlays(List overlays) { diff --git a/sdk/BUILD.gn b/sdk/BUILD.gn index d9761e5ce4d..33204212df5 100644 --- a/sdk/BUILD.gn +++ b/sdk/BUILD.gn @@ -70,6 +70,9 @@ dart_pkg("sdk") { "lib/framework/editing/editable_string.dart", "lib/framework/editing/editable_text.dart", "lib/framework/editing/keyboard.dart", + "lib/framework/editing2/editable_string.dart", + "lib/framework/editing2/editable_text.dart", + "lib/framework/editing2/keyboard.dart", "lib/framework/elements/animation/controller.dart", "lib/framework/elements/animation/timer.dart", "lib/framework/elements/material-element.sky", diff --git a/sdk/lib/framework/components2/input.dart b/sdk/lib/framework/components2/input.dart index eba94a3abba..68a1018810d 100644 --- a/sdk/lib/framework/components2/input.dart +++ b/sdk/lib/framework/components2/input.dart @@ -2,38 +2,39 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import '../editing/editable_string.dart'; -import '../editing/editable_text.dart'; -import '../editing/keyboard.dart'; +import '../editing2/editable_string.dart'; +import '../editing2/editable_text.dart'; +import '../editing2/keyboard.dart'; import '../fn2.dart'; -import '../theme/colors.dart'; -import '../theme/typography.dart' as typography; +import '../theme2/colors.dart'; +import '../theme2/typography.dart' as typography; +import '../rendering/flex.dart'; import 'dart:sky' as sky; typedef void ValueChanged(value); class Input extends Component { - 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 _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 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]};'''; + // static final String _focusedInlineStyle = ''' + // padding: 7px; + // border-bottom: 2px solid ${Blue[500]};'''; ValueChanged onChanged; String placeholder; @@ -75,8 +76,8 @@ class Input extends Component { if (placeholder != null && _value.isEmpty) { children.add(new Container( - style: _placeholderStyle, - children: [new Text(placeholder)] + // style: _placeholderStyle, + child: new Text(placeholder) )); } @@ -85,8 +86,8 @@ class Input extends Component { return new EventListenerNode( new FlexContainer( direction: FlexDirection.vertical, - style: _style, - inlineStyle: focused ? _focusedInlineStyle : null, + // style: _style, + // inlineStyle: focused ? _focusedInlineStyle : null, children: children ), onPointerDown: (sky.Event e) => keyboard.showByRequest() diff --git a/sdk/lib/framework/editing2/editable_string.dart b/sdk/lib/framework/editing2/editable_string.dart new file mode 100644 index 00000000000..728788ec52d --- /dev/null +++ b/sdk/lib/framework/editing2/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/framework/editing2/editable_text.dart b/sdk/lib/framework/editing2/editable_text.dart new file mode 100644 index 00000000000..711e930360c --- /dev/null +++ b/sdk/lib/framework/editing2/editable_text.dart @@ -0,0 +1,99 @@ +// 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 '../fn2.dart'; +import '../theme/colors.dart'; +import 'dart:async'; +import 'editable_string.dart'; + +class EditableText extends Component { + + // 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; + Timer _cursorTimer; + bool _showCursor = false; + + EditableText({Object key, this.value, this.focused}) + : super(key: key, stateful: true) { + onDidUnmount(() { + if (_cursorTimer != null) + _stopCursorTimer(); + }); + } + + void _cursorTick(Timer timer) { + setState(() { + _showCursor = !_showCursor; + }); + } + + void _startCursorTimer() { + _showCursor = true; + _cursorTimer = new Timer.periodic( + new Duration(milliseconds: 500), _cursorTick); + } + + void _stopCursorTimer() { + _cursorTimer.cancel(); + _cursorTimer = null; + _showCursor = false; + } + + UINode 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 { + String beforeComposing = value.textBefore(value.composing); + if (!beforeComposing.isEmpty) { + // children.add(new TextFragment(beforeComposing)); + hack += value.beforeComposing; + } + + String composing = value.textInside(value.composing); + hack += value.composing; + hack += value.afterComposing; + // 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 Paragraph( + text: hack + ); + } +} diff --git a/sdk/lib/framework/editing2/keyboard.dart b/sdk/lib/framework/editing2/keyboard.dart new file mode 100644 index 00000000000..cf0e0ab5de4 --- /dev/null +++ b/sdk/lib/framework/editing2/keyboard.dart @@ -0,0 +1,20 @@ +// 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 '../shell.dart' as shell; +import 'package:mojom/keyboard/keyboard.mojom.dart'; + +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;