From d443d598c2ac387303390fa93aa55ea0e4c0a34c Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Mon, 20 Mar 2017 17:18:35 -0700 Subject: [PATCH] Align TextEditingValue and InputValue (#8922) This patch prepares us to remove InputValue in favor of TextEditingValue. --- packages/flutter/lib/painting.dart | 1 - packages/flutter/lib/services.dart | 1 + .../lib/src/painting/flutter_logo.dart | 1 - .../lib/src/painting/text_painter.dart | 4 +- .../flutter/lib/src/painting/text_span.dart | 4 +- .../flutter/lib/src/rendering/editable.dart | 1 + .../flutter/lib/src/rendering/paragraph.dart | 1 + .../{painting => services}/text_editing.dart | 0 .../flutter/lib/src/services/text_input.dart | 153 +++++++++--------- .../lib/src/widgets/editable_text.dart | 40 ++--- .../lib/src/widgets/text_selection.dart | 1 + .../test/rendering/paragraph_test.dart | 1 + packages/flutter/test/widgets/input_test.dart | 10 +- .../flutter_test/lib/src/test_text_input.dart | 9 +- 14 files changed, 111 insertions(+), 116 deletions(-) rename packages/flutter/lib/src/{painting => services}/text_editing.dart (100%) diff --git a/packages/flutter/lib/painting.dart b/packages/flutter/lib/painting.dart index a67656d4bfd..043f319ff00 100644 --- a/packages/flutter/lib/painting.dart +++ b/packages/flutter/lib/painting.dart @@ -25,7 +25,6 @@ export 'src/painting/decoration.dart'; export 'src/painting/edge_insets.dart'; export 'src/painting/flutter_logo.dart'; export 'src/painting/fractional_offset.dart'; -export 'src/painting/text_editing.dart'; export 'src/painting/text_painter.dart'; export 'src/painting/text_span.dart'; export 'src/painting/text_style.dart'; diff --git a/packages/flutter/lib/services.dart b/packages/flutter/lib/services.dart index c6be68779b3..956a7b022bc 100644 --- a/packages/flutter/lib/services.dart +++ b/packages/flutter/lib/services.dart @@ -30,5 +30,6 @@ export 'src/services/system_channels.dart'; export 'src/services/system_chrome.dart'; export 'src/services/system_navigator.dart'; export 'src/services/system_sound.dart'; +export 'src/services/text_editing.dart'; export 'src/services/text_input.dart'; export 'src/services/url_launcher.dart'; diff --git a/packages/flutter/lib/src/painting/flutter_logo.dart b/packages/flutter/lib/src/painting/flutter_logo.dart index a9aac36c19f..338302f6317 100644 --- a/packages/flutter/lib/src/painting/flutter_logo.dart +++ b/packages/flutter/lib/src/painting/flutter_logo.dart @@ -12,7 +12,6 @@ import 'basic_types.dart'; import 'box_fit.dart'; import 'decoration.dart'; import 'fractional_offset.dart'; -import 'text_editing.dart'; import 'text_painter.dart'; import 'text_span.dart'; import 'text_style.dart'; diff --git a/packages/flutter/lib/src/painting/text_painter.dart b/packages/flutter/lib/src/painting/text_painter.dart index 8fef6698df7..8e114d274ef 100644 --- a/packages/flutter/lib/src/painting/text_painter.dart +++ b/packages/flutter/lib/src/painting/text_painter.dart @@ -4,11 +4,11 @@ import 'dart:ui' as ui show Paragraph, ParagraphBuilder, ParagraphConstraints, ParagraphStyle, TextBox; -import 'package:flutter/gestures.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/services.dart'; import 'basic_types.dart'; -import 'text_editing.dart'; import 'text_span.dart'; final String _kZeroWidthSpace = new String.fromCharCode(0x200B); diff --git a/packages/flutter/lib/src/painting/text_span.dart b/packages/flutter/lib/src/painting/text_span.dart index b083bbdeea2..fa1d18317df 100644 --- a/packages/flutter/lib/src/painting/text_span.dart +++ b/packages/flutter/lib/src/painting/text_span.dart @@ -4,11 +4,11 @@ import 'dart:ui' as ui show ParagraphBuilder; -import 'package:flutter/gestures.dart'; import 'package:flutter/foundation.dart'; +import 'package:flutter/gestures.dart'; +import 'package:flutter/services.dart'; import 'basic_types.dart'; -import 'text_editing.dart'; import 'text_style.dart'; // TODO(abarth): Should this be somewhere more general? diff --git a/packages/flutter/lib/src/rendering/editable.dart b/packages/flutter/lib/src/rendering/editable.dart index b10fa7ec2b9..79fce20beb9 100644 --- a/packages/flutter/lib/src/rendering/editable.dart +++ b/packages/flutter/lib/src/rendering/editable.dart @@ -6,6 +6,7 @@ import 'dart:math' as math; import 'dart:ui' as ui show TextBox; import 'package:flutter/gestures.dart'; +import 'package:flutter/services.dart'; import 'package:meta/meta.dart'; import 'box.dart'; diff --git a/packages/flutter/lib/src/rendering/paragraph.dart b/packages/flutter/lib/src/rendering/paragraph.dart index 5726c2e30b0..7f455001826 100644 --- a/packages/flutter/lib/src/rendering/paragraph.dart +++ b/packages/flutter/lib/src/rendering/paragraph.dart @@ -5,6 +5,7 @@ import 'dart:ui' as ui; import 'package:flutter/gestures.dart'; +import 'package:flutter/services.dart'; import 'box.dart'; import 'debug.dart'; diff --git a/packages/flutter/lib/src/painting/text_editing.dart b/packages/flutter/lib/src/services/text_editing.dart similarity index 100% rename from packages/flutter/lib/src/painting/text_editing.dart rename to packages/flutter/lib/src/services/text_editing.dart diff --git a/packages/flutter/lib/src/services/text_input.dart b/packages/flutter/lib/src/services/text_input.dart index 05b4c8abfee..cf74672b171 100644 --- a/packages/flutter/lib/src/services/text_input.dart +++ b/packages/flutter/lib/src/services/text_input.dart @@ -3,11 +3,12 @@ // found in the LICENSE file. import 'dart:async'; -import 'dart:ui' show TextAffinity; +import 'dart:ui' show TextAffinity, hashValues; import 'package:flutter/foundation.dart'; import 'system_channels.dart'; +import 'text_editing.dart'; import 'message_codec.dart'; export 'dart:ui' show TextAffinity; @@ -73,72 +74,33 @@ TextAffinity _toTextAffinity(String affinity) { } /// The current text, selection, and composing state for editing a run of text. -class TextEditingState { - /// Creates state for text editing. +class TextEditingValue { + /// Creates information for editing a run of text. /// - /// The [selectionBase], [selectionExtent], [selectionAffinity], - /// [selectionIsDirectional], [selectionIsDirectional], [composingBase], and - /// [composingExtent] arguments must not be null. - const TextEditingState({ - this.text, - this.selectionBase: -1, - this.selectionExtent: -1, - this.selectionAffinity: TextAffinity.downstream, - this.selectionIsDirectional: false, - this.composingBase: -1, - this.composingExtent: -1, + /// The selection and composing range must be within the text. + /// + /// The [text], [selection], and [composing] arguments must not be null but + /// each have default values. + const TextEditingValue({ + this.text: '', + this.selection: const TextSelection.collapsed(offset: -1), + this.composing: TextRange.empty }); - /// The text that is currently being edited. - final String text; - - /// The offset in [text] at which the selection originates. - /// - /// Might be larger than, smaller than, or equal to [selectionExtent]. - final int selectionBase; - - /// The offset in [text] at which the selection terminates. - /// - /// When the user uses the arrow keys to adjust the selection, this is the - /// value that changes. Similarly, if the current theme paints a caret on one - /// side of the selection, this is the location at which to paint the caret. - /// - /// Might be larger than, smaller than, or equal to [selectionBase]. - final int selectionExtent; - - /// If the the text range is collapsed and has more than one visual location - /// (e.g., occurs at a line break), which of the two locations to use when - /// painting the caret. - final TextAffinity selectionAffinity; - - /// Whether this selection has disambiguated its base and extent. - /// - /// On some platforms, the base and extent are not disambiguated until the - /// first time the user adjusts the selection. At that point, either the start - /// or the end of the selection becomes the base and the other one becomes the - /// extent and is adjusted. - final bool selectionIsDirectional; - - /// The offset in [text] at which the composing region originates. - /// - /// Always smaller than, or equal to, [composingExtent]. - final int composingBase; - - /// The offset in [text] at which the selection terminates. - /// - /// Always larger than, or equal to, [composingBase]. - final int composingExtent; - /// Creates an instance of this class from a JSON object. - factory TextEditingState.fromJSON(Map encoded) { - return new TextEditingState( + factory TextEditingValue.fromJSON(Map encoded) { + return new TextEditingValue( text: encoded['text'], - selectionBase: encoded['selectionBase'] ?? -1, - selectionExtent: encoded['selectionExtent'] ?? -1, - selectionIsDirectional: encoded['selectionIsDirectional'] ?? false, - selectionAffinity: _toTextAffinity(encoded['selectionAffinity']) ?? TextAffinity.downstream, - composingBase: encoded['composingBase'] ?? -1, - composingExtent: encoded['composingExtent'] ?? -1, + selection: new TextSelection( + baseOffset: encoded['selectionBase'] ?? -1, + extentOffset: encoded['selectionExtent'] ?? -1, + affinity: _toTextAffinity(encoded['selectionAffinity']) ?? TextAffinity.downstream, + isDirectional: encoded['selectionIsDirectional'] ?? false, + ), + composing: new TextRange( + start: encoded['composingBase'] ?? -1, + end: encoded['composingExtent'] ?? -1, + ), ); } @@ -146,14 +108,61 @@ class TextEditingState { Map toJSON() { return { 'text': text, - 'selectionBase': selectionBase, - 'selectionExtent': selectionExtent, - 'selectionAffinity': selectionAffinity.toString(), - 'selectionIsDirectional': selectionIsDirectional, - 'composingBase': composingBase, - 'composingExtent': composingExtent, + 'selectionBase': selection.baseOffset, + 'selectionExtent': selection.extentOffset, + 'selectionAffinity': selection.affinity.toString(), + 'selectionIsDirectional': selection.isDirectional, + 'composingBase': composing.start, + 'composingExtent': composing.end, }; } + + /// The current text being edited. + final String text; + + /// The range of text that is currently selected. + final TextSelection selection; + + /// The range of text that is still being composed. + final TextRange composing; + + /// A value that corresponds to the empty string with no selection and no composing range. + static const TextEditingValue empty = const TextEditingValue(); + + /// Creates a copy of this value but with the given fields replaced with the new values. + TextEditingValue copyWith({ + String text, + TextSelection selection, + TextRange composing + }) { + return new TextEditingValue( + text: text ?? this.text, + selection: selection ?? this.selection, + composing: composing ?? this.composing + ); + } + + @override + String toString() => '$runtimeType(text: \u2524$text\u251C, selection: $selection, composing: $composing)'; + + @override + bool operator ==(dynamic other) { + if (identical(this, other)) + return true; + if (other is! TextEditingValue) + return false; + final TextEditingValue typedOther = other; + return typedOther.text == text + && typedOther.selection == selection + && typedOther.composing == composing; + } + + @override + int get hashCode => hashValues( + text.hashCode, + selection.hashCode, + composing.hashCode + ); } /// An interface to receive information from [TextInput]. @@ -167,7 +176,7 @@ abstract class TextInputClient { const TextInputClient(); /// Requests that this client update its editing state to the given value. - void updateEditingState(TextEditingState state); + void updateEditingValue(TextEditingValue value); /// Requests that this client perform the given action. void performAction(TextInputAction action); @@ -198,11 +207,11 @@ class TextInputConnection { } /// Requests that the text input control change its internal state to match the given state. - void setEditingState(TextEditingState state) { + void setEditingState(TextEditingValue value) { assert(attached); SystemChannels.textInput.invokeMethod( 'TextInput.setEditingState', - state.toJSON(), + value.toJSON(), ); } @@ -247,7 +256,7 @@ class _TextInputClientHandler { return; switch (method) { case 'TextInputClient.updateEditingState': - _currentConnection._client.updateEditingState(new TextEditingState.fromJSON(args[1])); + _currentConnection._client.updateEditingValue(new TextEditingValue.fromJSON(args[1])); break; case 'TextInputClient.performAction': _currentConnection._client.performAction(_toTextInputAction(args[1])); diff --git a/packages/flutter/lib/src/widgets/editable_text.dart b/packages/flutter/lib/src/widgets/editable_text.dart index 64a7740b173..40e4540d8ce 100644 --- a/packages/flutter/lib/src/widgets/editable_text.dart +++ b/packages/flutter/lib/src/widgets/editable_text.dart @@ -17,37 +17,23 @@ import 'scroll_physics.dart'; import 'scrollable.dart'; import 'text_selection.dart'; -export 'package:flutter/painting.dart' show TextSelection; -export 'package:flutter/services.dart' show TextInputType; +export 'package:flutter/services.dart' show TextSelection, TextInputType; const Duration _kCursorBlinkHalfPeriod = const Duration(milliseconds: 500); -TextSelection _getTextSelectionFromEditingState(TextEditingState state) { - return new TextSelection( - baseOffset: state.selectionBase, - extentOffset: state.selectionExtent, - affinity: state.selectionAffinity, - isDirectional: state.selectionIsDirectional, - ); -} - -InputValue _getInputValueFromEditingState(TextEditingState state) { +InputValue _getInputValueFromEditingValue(TextEditingValue value) { return new InputValue( - text: state.text, - selection: _getTextSelectionFromEditingState(state), - composing: new TextRange(start: state.composingBase, end: state.composingExtent), + text: value.text, + selection: value.selection, + composing: value.composing, ); } -TextEditingState _getTextEditingStateFromInputValue(InputValue value) { - return new TextEditingState( +TextEditingValue _getTextEditingValueFromInputValue(InputValue value) { + return new TextEditingValue( text: value.text, - selectionBase: value.selection.baseOffset, - selectionExtent: value.selection.extentOffset, - selectionAffinity: value.selection.affinity, - selectionIsDirectional: value.selection.isDirectional, - composingBase: value.composing.start, - composingExtent: value.composing.end, + selection: value.selection, + composing: value.composing, ); } @@ -243,7 +229,7 @@ class EditableTextState extends State implements TextInputClient { if (_currentValue != config.value) { _currentValue = config.value; if (_isAttachedToKeyboard) - _textInputConnection.setEditingState(_getTextEditingStateFromInputValue(_currentValue)); + _textInputConnection.setEditingState(_getTextEditingValueFromInputValue(_currentValue)); } } @@ -271,7 +257,7 @@ class EditableTextState extends State implements TextInputClient { void _attachOrDetachKeyboard(bool focused) { if (focused && !_isAttachedToKeyboard && (_requestingFocus || config.autofocus)) { _textInputConnection = TextInput.attach(this, new TextInputConfiguration(inputType: config.keyboardType)) - ..setEditingState(_getTextEditingStateFromInputValue(_currentValue)) + ..setEditingState(_getTextEditingValueFromInputValue(_currentValue)) ..show(); } else if (!focused) { if (_isAttachedToKeyboard) { @@ -308,8 +294,8 @@ class EditableTextState extends State implements TextInputClient { } @override - void updateEditingState(TextEditingState state) { - _currentValue = _getInputValueFromEditingState(state); + void updateEditingValue(TextEditingValue value) { + _currentValue = _getInputValueFromEditingValue(value); if (config.onChanged != null) config.onChanged(_currentValue); if (_currentValue.text != config.value.text) { diff --git a/packages/flutter/lib/src/widgets/text_selection.dart b/packages/flutter/lib/src/widgets/text_selection.dart index 42280582b46..824908a0dc6 100644 --- a/packages/flutter/lib/src/widgets/text_selection.dart +++ b/packages/flutter/lib/src/widgets/text_selection.dart @@ -4,6 +4,7 @@ import 'package:flutter/foundation.dart'; import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; import 'package:flutter/scheduler.dart'; import 'basic.dart'; diff --git a/packages/flutter/test/rendering/paragraph_test.dart b/packages/flutter/test/rendering/paragraph_test.dart index a3579067afd..9c514c14828 100644 --- a/packages/flutter/test/rendering/paragraph_test.dart +++ b/packages/flutter/test/rendering/paragraph_test.dart @@ -5,6 +5,7 @@ import 'dart:ui' as ui show TextBox; import 'package:flutter/rendering.dart'; +import 'package:flutter/services.dart'; import 'package:test/test.dart'; import 'rendering_tester.dart'; diff --git a/packages/flutter/test/widgets/input_test.dart b/packages/flutter/test/widgets/input_test.dart index ae6d986b663..dddf3efc826 100644 --- a/packages/flutter/test/widgets/input_test.dart +++ b/packages/flutter/test/widgets/input_test.dart @@ -155,10 +155,9 @@ void main() { await tester.showKeyboard(find.byType(EditableText)); // Try the test again with a nonempty EditableText. - tester.testTextInput.updateEditingState(const TextEditingState( + tester.testTextInput.updateEditingValue(const TextEditingValue( text: 'X', - selectionBase: 1, - selectionExtent: 1, + selection: const TextSelection.collapsed(offset: 1), )); await checkCursorToggle(); }); @@ -182,10 +181,9 @@ void main() { await tester.showKeyboard(find.byType(EditableText)); const String testValue = 'ABC'; - tester.testTextInput.updateEditingState(const TextEditingState( + tester.testTextInput.updateEditingValue(const TextEditingValue( text: testValue, - selectionBase: testValue.length, - selectionExtent: testValue.length, + selection: const TextSelection.collapsed(offset: testValue.length), )); await tester.pump(); diff --git a/packages/flutter_test/lib/src/test_text_input.dart b/packages/flutter_test/lib/src/test_text_input.dart index 45cb2ab047c..e4c3629b09f 100644 --- a/packages/flutter_test/lib/src/test_text_input.dart +++ b/packages/flutter_test/lib/src/test_text_input.dart @@ -38,14 +38,14 @@ class TestTextInput { } } - void updateEditingState(TextEditingState state) { + void updateEditingValue(TextEditingValue value) { expect(_client, isNonZero); PlatformMessages.handlePlatformMessage( SystemChannels.textInput.name, SystemChannels.textInput.codec.encodeMethodCall( new MethodCall( 'TextInputClient.updateEditingState', - [_client, state.toJSON()], + [_client, value.toJSON()], ), ), (_) {}, @@ -53,10 +53,9 @@ class TestTextInput { } void enterText(String text) { - updateEditingState(new TextEditingState( + updateEditingValue(new TextEditingValue( text: text, - composingBase: 0, - composingExtent: text.length, + composing: new TextRange(start: 0, end: text.length), )); } }