Commit 6f691a04 authored by Adam Barth's avatar Adam Barth

Make Sky's EditableText mostly work

This CL factors EditableString out of EditableText and implements more of the
InputConnection functions. As a result, EditableText now basically works.

This CL also paves the way to make EditableText stateless by using
EditableString as its state object. However, there's still a bit more work to
do to make that a reality (e.g., factoring out the cursor blink timer and the
connection to the keyboard).

R=eseidel@chromium.org

Review URL: https://codereview.chromium.org/995073002
parent f7cdfda4
// 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:sky/services/keyboard/keyboard.mojom.dart';
typedef void StringChangedCallback(EditableString updated);
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 StringChangedCallback onChanged;
KeyboardClientStub stub;
EditableString({this.text: '', this.onChanged}) {
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();
onChanged(this);
}
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);
onChanged(this);
}
void setComposingRegion(int start, int end) {
composing = new TextRange(start: start, end: end);
onChanged(this);
}
void setComposingText(String text, int newCursorPosition) {
// TODO(abarth): Why is |newCursorPosition| always 1?
composing = _replaceOrAppend(composing, text);
selection = new TextRange.collapsed(composing.end);
onChanged(this);
}
void setSelection(int start, int end) {
selection = new TextRange(start: start, end: end);
onChanged(this);
}
}
...@@ -4,105 +4,118 @@ ...@@ -4,105 +4,118 @@
import '../../framework/fn.dart'; import '../../framework/fn.dart';
import '../../framework/shell.dart' as shell; import '../../framework/shell.dart' as shell;
import 'package:sky/services/keyboard/keyboard.mojom.dart'; import 'dart:async';
import 'dart:math'; import 'dart:math';
import 'editable_string.dart';
import 'package:sky/services/keyboard/keyboard.mojom.dart';
class Input extends Component implements KeyboardClient{ class EditableText extends Component {
static Style _style = new Style(''' static Style _style = new Style('''
display: paragraph; display: paragraph;
white-space: pre-wrap;
padding: 10px; padding: 10px;
height: 200px; height: 200px;
background-color: lightblue;''' background-color: lightblue;'''
); );
static Style _cusorStyle = new Style('''
display: inline-block;
width: 2px;
height: 1.2em;
vertical-align: top;
background-color: blue;'''
);
static Style _composingStyle = new Style(''' static Style _composingStyle = new Style('''
display: inline; display: inline;
text-decoration: underline;''' text-decoration: underline;'''
); );
KeyboardServiceProxy _service; KeyboardServiceProxy _service;
KeyboardClientStub _stub;
String _text = ""; EditableString _string;
int _composingStart = -1; Timer _cursorTimer;
int _composingEnd = -1; bool _showCursor = false;
Input({Object key}) : super(key: key, stateful: true) { EditableText({Object key}) : super(key: key, stateful: true) {
_string = new EditableString(text: '', onChanged: _handleTextChanged);
events.listen('click', _handleClick); events.listen('click', _handleClick);
_stub = new KeyboardClientStub.unbound()..impl = this; events.listen('focus', _handleFocus);
events.listen('blur', _handleBlur);
} }
bool get _hasComposingRegion => _composingStart != -1 && _composingEnd != -1; void _handleTextChanged(EditableString string) {
setState(() {
_string = string;
_showCursor = true;
_restartCursorTimer();
});
}
void _handleClick(_) { void _handleClick(_) {
if (_service != null) if (_service != null)
return; return;
_service = new KeyboardServiceProxy.unbound(); _service = new KeyboardServiceProxy.unbound();
shell.requestService(_service); shell.requestService(_service);
_service.ptr.show(_stub); _service.ptr.show(_string.stub);
_restartCursorTimer();
setState(() {
_showCursor = true;
});
} }
void _replaceComposing(String text) { void _handleFocus(_) {
if (!_hasComposingRegion) { print("_handleFocus");
_composingStart = _text.length;
_composingEnd = _composingStart + text.length;
_text += text;
return;
}
_text = _text.substring(0, _composingStart)
+ text + _text.substring(_composingEnd);
_composingEnd = _composingStart + text.length;
} }
void _clearComposingRegion() { void _handleBlur(_) {
_composingStart = -1; print("_handleBlur");
_composingEnd = -1;
} }
void commitText(String text, int newCursorPosition) { void _cursorTick(Timer timer) {
setState(() { setState(() {
_replaceComposing(text); _showCursor = !_showCursor;
_clearComposingRegion();
}); });
} }
void setComposingText(String text, int newCursorPosition) { void _restartCursorTimer() {
setState(() { if (_cursorTimer != null)
_replaceComposing(text); _cursorTimer.cancel();
}); _cursorTimer = new Timer.periodic(
new Duration(milliseconds: 500), _cursorTick);
} }
void setComposingRegion(int start, int end) { void didUnmount() {
setState(() { _cursorTimer.cancel();
_composingStart = start;
_composingEnd = end;
});
} }
Node build() { Node build() {
List<Node> children = new List<Node>(); List<Node> children = new List<Node>();
if (!_hasComposingRegion) { if (!_string.composing.isValid) {
children.add(new Text(_text)); children.add(new Text(_string.text));
} else { } else {
String run = _text.substring(0, _composingStart); String beforeComposing = _string.textBefore(_string.composing);
if (!run.isEmpty) if (!beforeComposing.isEmpty)
children.add(new Text(run)); children.add(new Text(beforeComposing));
run = _text.substring(_composingStart, _composingEnd); String composing = _string.textInside(_string.composing);
if (!run.isEmpty) { if (!composing.isEmpty) {
children.add(new Container( children.add(new Container(
key: 'composing',
style: _composingStyle, style: _composingStyle,
children: [new Text(_text.substring(_composingStart, _composingEnd))] children: [new Text(composing)]
)); ));
} }
run = _text.substring(_composingEnd); String afterComposing = _string.textAfter(_string.composing);
if (!run.isEmpty) if (!afterComposing.isEmpty)
children.add(new Text(_text.substring(_composingEnd))); children.add(new Text(afterComposing));
} }
if (_showCursor)
children.add(new Container(key: 'cursor', style: _cusorStyle));
return new Container( return new Container(
style: _style, style: _style,
children: children children: children
......
...@@ -3,10 +3,10 @@ ...@@ -3,10 +3,10 @@
// found in the LICENSE file. // found in the LICENSE file.
import '../../framework/fn.dart'; import '../../framework/fn.dart';
import 'input.dart'; import 'editable_text.dart';
class EditorApp extends App { class EditorApp extends App {
Node build() { Node build() {
return new Input(); return new EditableText();
} }
} }
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment