Index: sky/examples/editor/editable_string.dart |
diff --git a/sky/examples/editor/editable_string.dart b/sky/examples/editor/editable_string.dart |
new file mode 100644 |
index 0000000000000000000000000000000000000000..63ed5cdc5f52e16bc3c97a013fb52d83224cfec4 |
--- /dev/null |
+++ b/sky/examples/editor/editable_string.dart |
@@ -0,0 +1,112 @@ |
+// 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); |
+ } |
+ |
+ // Returns the range of |newText| inside this string. |
+ TextRange _replace(TextRange range, String newText) { |
+ if (!range.isValid) { |
+ int start = text.length; |
+ int end = start + newText.length; |
+ text += newText; |
eseidel
2015/03/10 18:39:29
You should make this more self-documenting.
|
+ return new TextRange(start: start, end: end); |
+ } |
+ |
+ String before = textBefore(range); |
+ String after = textAfter(range); |
+ |
+ text = before + newText + after; |
+ return new TextRange(start: before.length, |
+ end: before.length + newText.length); |
+ } |
+ |
+ 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 = _replace(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); |
+ } |
+ |
+ 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 = _replace(composing, text); |
+ selection = new TextRange.collapsed(composing.end); |
+ onChanged(this); |
+ } |
+ |
+ void setSelection(int start, int end) { |
+ selection = new TextRange(start: start, end: end); |
+ } |
+} |
+ |