Commit fade0dc0 authored by Adam Barth's avatar Adam Barth

Update to new editing.mojom

Now the keyboard is responsible for maintaining the state of the text
field.
parent 9e784f0c
...@@ -6,6 +6,7 @@ import 'package:flutter/animation.dart'; ...@@ -6,6 +6,7 @@ import 'package:flutter/animation.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart'; import 'package:flutter/widgets.dart';
import 'package:sky_services/editing/editing.mojom.dart' as mojom;
import 'colors.dart'; import 'colors.dart';
import 'debug.dart'; import 'debug.dart';
...@@ -13,7 +14,7 @@ import 'icon.dart'; ...@@ -13,7 +14,7 @@ import 'icon.dart';
import 'theme.dart'; import 'theme.dart';
export 'package:flutter/rendering.dart' show ValueChanged; export 'package:flutter/rendering.dart' show ValueChanged;
export 'package:flutter/services.dart' show KeyboardType; export 'package:sky_services/editing/editing.mojom.dart' show KeyboardType;
/// A material design text input field. /// A material design text input field.
class Input extends StatefulComponent { class Input extends StatefulComponent {
...@@ -110,10 +111,11 @@ class _InputState extends State<Input> { ...@@ -110,10 +111,11 @@ class _InputState extends State<Input> {
void _attachOrDetachKeyboard(bool focused) { void _attachOrDetachKeyboard(bool focused) {
if (focused && !_isAttachedToKeyboard) { if (focused && !_isAttachedToKeyboard) {
_keyboardHandle = keyboard.show(_editableString.createStub(), config.keyboardType); _keyboardHandle = keyboard.attach(_editableString.createStub(),
_keyboardHandle.setText(_editableString.text); new mojom.KeyboardConfiguration()
_keyboardHandle.setSelection(_editableString.selection.start, ..type = config.keyboardType);
_editableString.selection.end); _keyboardHandle.setEditingState(_editableString.editingState);
_keyboardHandle.show();
} else if (!focused && _isAttachedToKeyboard) { } else if (!focused && _isAttachedToKeyboard) {
_keyboardHandle.release(); _keyboardHandle.release();
_keyboardHandle = null; _keyboardHandle = null;
...@@ -124,7 +126,7 @@ class _InputState extends State<Input> { ...@@ -124,7 +126,7 @@ class _InputState extends State<Input> {
void _requestKeyboard() { void _requestKeyboard() {
if (Focus.at(context)) { if (Focus.at(context)) {
assert(_isAttachedToKeyboard); assert(_isAttachedToKeyboard);
_keyboardHandle.showByRequest(); _keyboardHandle.show();
} else { } else {
Focus.moveTo(config.key); Focus.moveTo(config.key);
// we'll get told to rebuild and we'll take care of the keyboard then // we'll get told to rebuild and we'll take care of the keyboard then
...@@ -149,7 +151,8 @@ class _InputState extends State<Input> { ...@@ -149,7 +151,8 @@ class _InputState extends State<Input> {
void _handleSelectionChanged(TextSelection selection) { void _handleSelectionChanged(TextSelection selection) {
if (_isAttachedToKeyboard) { if (_isAttachedToKeyboard) {
_keyboardHandle.setSelection(selection.start, selection.end); _editableString.setSelection(selection);
_keyboardHandle.setEditingState(_editableString.editingState);
} else { } else {
_editableString.setSelection(selection); _editableString.setSelection(selection);
_requestKeyboard(); _requestKeyboard();
......
...@@ -4,11 +4,11 @@ ...@@ -4,11 +4,11 @@
import 'dart:async'; import 'dart:async';
import 'package:mojo_services/keyboard/keyboard.mojom.dart'; import 'package:sky_services/editing/editing.mojom.dart' as mojom;
import 'shell.dart'; import 'shell.dart';
export 'package:mojo_services/keyboard/keyboard.mojom.dart'; export 'package:sky_services/editing/editing.mojom.dart' show KeyboardType;
/// An interface to the system's keyboard. /// An interface to the system's keyboard.
/// ///
...@@ -19,22 +19,24 @@ class Keyboard { ...@@ -19,22 +19,24 @@ class Keyboard {
// The service is exposed in case you need direct access. // The service is exposed in case you need direct access.
// However, as a general rule, you should be able to do // However, as a general rule, you should be able to do
// most of what you need using only this class. // most of what you need using only this class.
final KeyboardService service; final mojom.Keyboard service;
KeyboardHandle _currentHandle; KeyboardHandle _currentHandle;
bool _hidePending = false; bool _hidePending = false;
KeyboardHandle show(KeyboardClientStub stub, KeyboardType keyboardType) { KeyboardHandle attach(mojom.KeyboardClientStub stub, mojom.KeyboardConfiguration configuration) {
assert(stub != null); assert(stub != null);
_currentHandle?.release(); _currentHandle?.release();
assert(_currentHandle == null); assert(_currentHandle == null);
_currentHandle = new KeyboardHandle._show(this, stub, keyboardType); _currentHandle = new KeyboardHandle._(this);
service.setClient(stub, configuration);
return _currentHandle; return _currentHandle;
} }
void _scheduleHide() { void _scheduleHide() {
if (_hidePending) return; if (_hidePending)
return;
_hidePending = true; _hidePending = true;
// Schedule a deferred task that hides the keyboard. If someone else shows // Schedule a deferred task that hides the keyboard. If someone else shows
...@@ -50,21 +52,17 @@ class Keyboard { ...@@ -50,21 +52,17 @@ class Keyboard {
} }
class KeyboardHandle { class KeyboardHandle {
KeyboardHandle._(Keyboard keyboard) : _keyboard = keyboard, _attached = true;
KeyboardHandle._show(Keyboard keyboard, KeyboardClientStub stub, KeyboardType keyboardType) : _keyboard = keyboard {
_keyboard.service.show(stub, keyboardType);
_attached = true;
}
final Keyboard _keyboard; final Keyboard _keyboard;
bool _attached; bool _attached;
bool get attached => _attached; bool get attached => _attached;
void showByRequest() { void show() {
assert(_attached); assert(_attached);
assert(_keyboard._currentHandle == this); assert(_keyboard._currentHandle == this);
_keyboard.service.showByRequest(); _keyboard.service.show();
} }
void release() { void release() {
...@@ -77,25 +75,18 @@ class KeyboardHandle { ...@@ -77,25 +75,18 @@ class KeyboardHandle {
assert(_keyboard._currentHandle != this); assert(_keyboard._currentHandle != this);
} }
void setText(String text) { void setEditingState(mojom.EditingState state) {
assert(_attached); assert(_attached);
assert(_keyboard._currentHandle == this); assert(_keyboard._currentHandle == this);
_keyboard.service.setText(text); _keyboard.service.setEditingState(state);
} }
void setSelection(int start, int end) {
assert(_attached);
assert(_keyboard._currentHandle == this);
_keyboard.service.setSelection(start, end);
}
} }
KeyboardServiceProxy _initKeyboardProxy() { mojom.KeyboardProxy _initKeyboardProxy() {
KeyboardServiceProxy proxy = new KeyboardServiceProxy.unbound(); mojom.KeyboardProxy proxy = new mojom.KeyboardProxy.unbound();
shell.connectToService(null, proxy); shell.connectToService(null, proxy);
return proxy; return proxy;
} }
final KeyboardServiceProxy _keyboardProxy = _initKeyboardProxy(); final mojom.KeyboardProxy _keyboardProxy = _initKeyboardProxy();
final Keyboard keyboard = new Keyboard(_keyboardProxy.ptr); final Keyboard keyboard = new Keyboard(_keyboardProxy.ptr);
...@@ -3,9 +3,8 @@ ...@@ -3,9 +3,8 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:math' as math;
import 'package:mojo_services/keyboard/keyboard.mojom.dart'; import 'package:sky_services/editing/editing.mojom.dart' as mojom;
import 'package:flutter/painting.dart'; import 'package:flutter/painting.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
...@@ -18,7 +17,16 @@ export 'package:flutter/painting.dart' show TextSelection; ...@@ -18,7 +17,16 @@ export 'package:flutter/painting.dart' show TextSelection;
const Duration _kCursorBlinkHalfPeriod = const Duration(milliseconds: 500); const Duration _kCursorBlinkHalfPeriod = const Duration(milliseconds: 500);
class _KeyboardClientImpl implements KeyboardClient { TextSelection _getTextSelectionFromEditingState(mojom.EditingState state) {
return new TextSelection(
baseOffset: state.selectionBase,
extentOffset: state.selectionExtent,
affinity: TextAffinity.values[state.selectionAffinity.mojoEnumValue],
isDirectional: state.selectionIsDirectional
);
}
class _KeyboardClientImpl implements mojom.KeyboardClient {
_KeyboardClientImpl({ _KeyboardClientImpl({
String text: '', String text: '',
TextSelection selection, TextSelection selection,
...@@ -45,86 +53,29 @@ class _KeyboardClientImpl implements KeyboardClient { ...@@ -45,86 +53,29 @@ class _KeyboardClientImpl implements KeyboardClient {
TextSelection selection; TextSelection selection;
/// A keyboard client stub that can be attached to a keyboard service. /// A keyboard client stub that can be attached to a keyboard service.
KeyboardClientStub createStub() { mojom.KeyboardClientStub createStub() {
return new KeyboardClientStub.unbound()..impl = this; return new mojom.KeyboardClientStub.unbound()..impl = this;
}
void _delete(TextRange range) {
if (range.isCollapsed || !range.isValid) return;
text = range.textBefore(text) + range.textAfter(text);
}
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 = range.textBefore(text);
String after = range.textAfter(text);
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) { mojom.EditingState get editingState {
// TODO(abarth): Not implemented. return new mojom.EditingState()
..text = text
..selectionBase = selection.baseOffset
..selectionExtent = selection.extentOffset
..selectionAffinity = mojom.TextAffinity.values[selection.affinity.index]
..selectionIsDirectional = selection.isDirectional
..composingBase = composing.start
..composingExtent = composing.end;
} }
void commitText(String text, int newCursorPosition) { void updateEditingState(mojom.EditingState state) {
// TODO(abarth): Why is |newCursorPosition| always 1? text = state.text;
TextRange committedRange = _replaceOrAppend(composing, text); selection = _getTextSelectionFromEditingState(state);
selection = new TextSelection.collapsed(offset: committedRange.end); composing = new TextRange(start: state.composingBase, end: state.composingExtent);
composing = TextRange.empty;
onUpdated();
}
void deleteSurroundingText(int beforeLength, int afterLength) {
TextRange beforeRange = new TextRange(
start: selection.start - beforeLength, end: selection.start);
int afterRangeEnd = math.min(selection.end + afterLength, text.length);
TextRange afterRange =
new TextRange(start: selection.end, end: afterRangeEnd);
_delete(afterRange);
_delete(beforeRange);
selection = new TextSelection(
baseOffset: math.max(selection.start - beforeLength, 0),
extentOffset: math.max(selection.end - beforeLength, 0)
);
onUpdated();
}
void setComposingRegion(int start, int end) {
composing = new TextRange(start: start, end: end);
onUpdated(); onUpdated();
} }
void setComposingText(String text, int newCursorPosition) { void submit(mojom.SubmitAction action) {
// TODO(abarth): Why is |newCursorPosition| always 1?
composing = _replaceOrAppend(composing, text);
selection = new TextSelection.collapsed(offset: composing.end);
onUpdated();
}
void setSelection(int start, int end) {
selection = new TextSelection(baseOffset: start, extentOffset: end);
onUpdated();
}
void submit(SubmitAction action) {
composing = TextRange.empty; composing = TextRange.empty;
onSubmitted(); onSubmitted();
} }
...@@ -162,10 +113,12 @@ class EditableString { ...@@ -162,10 +113,12 @@ class EditableString {
_client.selection = selection; _client.selection = selection;
} }
mojom.EditingState get editingState => _client.editingState;
/// A keyboard client stub that can be attached to a keyboard service. /// A keyboard client stub that can be attached to a keyboard service.
/// ///
/// See [Keyboard]. /// See [Keyboard].
KeyboardClientStub createStub() => _client.createStub(); mojom.KeyboardClientStub createStub() => _client.createStub();
void didDetachKeyboard() { void didDetachKeyboard() {
_client.composing = TextRange.empty; _client.composing = TextRange.empty;
......
...@@ -5,31 +5,28 @@ ...@@ -5,31 +5,28 @@
import 'package:flutter_test/flutter_test.dart'; import 'package:flutter_test/flutter_test.dart';
import 'package:flutter/material.dart'; import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart'; import 'package:sky_services/editing/editing.mojom.dart' as mojom;
import 'package:mojo_services/keyboard/keyboard.mojom.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import '../services/mock_services.dart'; import '../services/mock_services.dart';
class MockKeyboard implements KeyboardService { class MockKeyboard implements mojom.Keyboard {
KeyboardClient client; mojom.KeyboardClient client;
void show(KeyboardClientStub client, KeyboardType type) { void setClient(mojom.KeyboardClientStub client, mojom.KeyboardConfiguration configuraiton) {
this.client = client.impl; this.client = client.impl;
} }
void showByRequest() {} void show() {}
void hide() {} void hide() {}
void setText(String text) {} void setEditingState(mojom.EditingState state) {}
void setSelection(int start, int end) {}
} }
void main() { void main() {
MockKeyboard mockKeyboard = new MockKeyboard(); MockKeyboard mockKeyboard = new MockKeyboard();
serviceMocker.registerMockService(KeyboardService.serviceName, mockKeyboard); serviceMocker.registerMockService(mojom.Keyboard.serviceName, mockKeyboard);
test('Editable text has consistent size', () { test('Editable text has consistent size', () {
testWidgets((WidgetTester tester) { testWidgets((WidgetTester tester) {
...@@ -56,7 +53,10 @@ void main() { ...@@ -56,7 +53,10 @@ void main() {
void enterText(String testValue) { void enterText(String testValue) {
// Simulate entry of text through the keyboard. // Simulate entry of text through the keyboard.
expect(mockKeyboard.client, isNotNull); expect(mockKeyboard.client, isNotNull);
mockKeyboard.client.setComposingText(testValue, testValue.length); mockKeyboard.client.updateEditingState(new mojom.EditingState()
..text = testValue
..composingBase = 0
..composingExtent = testValue.length);
// Check that the onChanged event handler fired. // Check that the onChanged event handler fired.
expect(inputValue, equals(testValue)); expect(inputValue, equals(testValue));
...@@ -109,46 +109,14 @@ void main() { ...@@ -109,46 +109,14 @@ void main() {
checkCursorToggle(); checkCursorToggle();
// Try the test again with a nonempty EditableText. // Try the test again with a nonempty EditableText.
mockKeyboard.client.setComposingText('X', 1); mockKeyboard.client.updateEditingState(new mojom.EditingState()
..text = 'X'
..selectionBase = 1
..selectionExtent = 1);
checkCursorToggle(); checkCursorToggle();
}); });
}); });
test('Selection remains valid', () {
testWidgets((WidgetTester tester) {
GlobalKey inputKey = new GlobalKey();
Widget builder() {
return new Center(
child: new Material(
child: new Input(
key: inputKey,
hintText: 'Placeholder'
)
)
);
}
tester.pumpWidget(builder());
const String testValue = 'ABC';
mockKeyboard.client.commitText(testValue, testValue.length);
dynamic input = inputKey.currentState;
// Delete characters and verify that the selection follows the length
// of the text.
for (int i = 0; i < testValue.length; i++) {
mockKeyboard.client.deleteSurroundingText(1, 0);
expect(input.editableValue.selection.start, equals(testValue.length - i - 1));
}
// Delete a characters when the text is empty. The selection should
// remain at zero.
mockKeyboard.client.deleteSurroundingText(1, 0);
expect(input.editableValue.selection.start, equals(0));
});
});
test('hideText control test', () { test('hideText control test', () {
testWidgets((WidgetTester tester) { testWidgets((WidgetTester tester) {
GlobalKey inputKey = new GlobalKey(); GlobalKey inputKey = new GlobalKey();
...@@ -168,7 +136,10 @@ void main() { ...@@ -168,7 +136,10 @@ void main() {
tester.pumpWidget(builder()); tester.pumpWidget(builder());
const String testValue = 'ABC'; const String testValue = 'ABC';
mockKeyboard.client.commitText(testValue, testValue.length); mockKeyboard.client.updateEditingState(new mojom.EditingState()
..text = testValue
..selectionBase = testValue.length
..selectionExtent = testValue.length);
tester.pump(); tester.pump();
}); });
......
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