Commit d443d598 authored by Adam Barth's avatar Adam Barth Committed by GitHub

Align TextEditingValue and InputValue (#8922)

This patch prepares us to remove InputValue in favor of TextEditingValue.
parent d35a9db6
...@@ -25,7 +25,6 @@ export 'src/painting/decoration.dart'; ...@@ -25,7 +25,6 @@ export 'src/painting/decoration.dart';
export 'src/painting/edge_insets.dart'; export 'src/painting/edge_insets.dart';
export 'src/painting/flutter_logo.dart'; export 'src/painting/flutter_logo.dart';
export 'src/painting/fractional_offset.dart'; export 'src/painting/fractional_offset.dart';
export 'src/painting/text_editing.dart';
export 'src/painting/text_painter.dart'; export 'src/painting/text_painter.dart';
export 'src/painting/text_span.dart'; export 'src/painting/text_span.dart';
export 'src/painting/text_style.dart'; export 'src/painting/text_style.dart';
......
...@@ -30,5 +30,6 @@ export 'src/services/system_channels.dart'; ...@@ -30,5 +30,6 @@ export 'src/services/system_channels.dart';
export 'src/services/system_chrome.dart'; export 'src/services/system_chrome.dart';
export 'src/services/system_navigator.dart'; export 'src/services/system_navigator.dart';
export 'src/services/system_sound.dart'; export 'src/services/system_sound.dart';
export 'src/services/text_editing.dart';
export 'src/services/text_input.dart'; export 'src/services/text_input.dart';
export 'src/services/url_launcher.dart'; export 'src/services/url_launcher.dart';
...@@ -12,7 +12,6 @@ import 'basic_types.dart'; ...@@ -12,7 +12,6 @@ import 'basic_types.dart';
import 'box_fit.dart'; import 'box_fit.dart';
import 'decoration.dart'; import 'decoration.dart';
import 'fractional_offset.dart'; import 'fractional_offset.dart';
import 'text_editing.dart';
import 'text_painter.dart'; import 'text_painter.dart';
import 'text_span.dart'; import 'text_span.dart';
import 'text_style.dart'; import 'text_style.dart';
......
...@@ -4,11 +4,11 @@ ...@@ -4,11 +4,11 @@
import 'dart:ui' as ui show Paragraph, ParagraphBuilder, ParagraphConstraints, ParagraphStyle, TextBox; import 'dart:ui' as ui show Paragraph, ParagraphBuilder, ParagraphConstraints, ParagraphStyle, TextBox;
import 'package:flutter/gestures.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/services.dart';
import 'basic_types.dart'; import 'basic_types.dart';
import 'text_editing.dart';
import 'text_span.dart'; import 'text_span.dart';
final String _kZeroWidthSpace = new String.fromCharCode(0x200B); final String _kZeroWidthSpace = new String.fromCharCode(0x200B);
......
...@@ -4,11 +4,11 @@ ...@@ -4,11 +4,11 @@
import 'dart:ui' as ui show ParagraphBuilder; import 'dart:ui' as ui show ParagraphBuilder;
import 'package:flutter/gestures.dart';
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart';
import 'package:flutter/services.dart';
import 'basic_types.dart'; import 'basic_types.dart';
import 'text_editing.dart';
import 'text_style.dart'; import 'text_style.dart';
// TODO(abarth): Should this be somewhere more general? // TODO(abarth): Should this be somewhere more general?
......
...@@ -6,6 +6,7 @@ import 'dart:math' as math; ...@@ -6,6 +6,7 @@ import 'dart:math' as math;
import 'dart:ui' as ui show TextBox; import 'dart:ui' as ui show TextBox;
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/services.dart';
import 'package:meta/meta.dart'; import 'package:meta/meta.dart';
import 'box.dart'; import 'box.dart';
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'dart:ui' as ui; import 'dart:ui' as ui;
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/services.dart';
import 'box.dart'; import 'box.dart';
import 'debug.dart'; import 'debug.dart';
......
...@@ -3,11 +3,12 @@ ...@@ -3,11 +3,12 @@
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async'; import 'dart:async';
import 'dart:ui' show TextAffinity; import 'dart:ui' show TextAffinity, hashValues;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'system_channels.dart'; import 'system_channels.dart';
import 'text_editing.dart';
import 'message_codec.dart'; import 'message_codec.dart';
export 'dart:ui' show TextAffinity; export 'dart:ui' show TextAffinity;
...@@ -73,72 +74,33 @@ TextAffinity _toTextAffinity(String affinity) { ...@@ -73,72 +74,33 @@ TextAffinity _toTextAffinity(String affinity) {
} }
/// The current text, selection, and composing state for editing a run of text. /// The current text, selection, and composing state for editing a run of text.
class TextEditingState { class TextEditingValue {
/// Creates state for text editing. /// Creates information for editing a run of text.
/// ///
/// The [selectionBase], [selectionExtent], [selectionAffinity], /// The selection and composing range must be within the text.
/// [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 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]. /// The [text], [selection], and [composing] arguments must not be null but
final int composingBase; /// each have default values.
const TextEditingValue({
/// The offset in [text] at which the selection terminates. this.text: '',
/// this.selection: const TextSelection.collapsed(offset: -1),
/// Always larger than, or equal to, [composingBase]. this.composing: TextRange.empty
final int composingExtent; });
/// Creates an instance of this class from a JSON object. /// Creates an instance of this class from a JSON object.
factory TextEditingState.fromJSON(Map<String, dynamic> encoded) { factory TextEditingValue.fromJSON(Map<String, dynamic> encoded) {
return new TextEditingState( return new TextEditingValue(
text: encoded['text'], text: encoded['text'],
selectionBase: encoded['selectionBase'] ?? -1, selection: new TextSelection(
selectionExtent: encoded['selectionExtent'] ?? -1, baseOffset: encoded['selectionBase'] ?? -1,
selectionIsDirectional: encoded['selectionIsDirectional'] ?? false, extentOffset: encoded['selectionExtent'] ?? -1,
selectionAffinity: _toTextAffinity(encoded['selectionAffinity']) ?? TextAffinity.downstream, affinity: _toTextAffinity(encoded['selectionAffinity']) ?? TextAffinity.downstream,
composingBase: encoded['composingBase'] ?? -1, isDirectional: encoded['selectionIsDirectional'] ?? false,
composingExtent: encoded['composingExtent'] ?? -1, ),
composing: new TextRange(
start: encoded['composingBase'] ?? -1,
end: encoded['composingExtent'] ?? -1,
),
); );
} }
...@@ -146,14 +108,61 @@ class TextEditingState { ...@@ -146,14 +108,61 @@ class TextEditingState {
Map<String, dynamic> toJSON() { Map<String, dynamic> toJSON() {
return <String, dynamic>{ return <String, dynamic>{
'text': text, 'text': text,
'selectionBase': selectionBase, 'selectionBase': selection.baseOffset,
'selectionExtent': selectionExtent, 'selectionExtent': selection.extentOffset,
'selectionAffinity': selectionAffinity.toString(), 'selectionAffinity': selection.affinity.toString(),
'selectionIsDirectional': selectionIsDirectional, 'selectionIsDirectional': selection.isDirectional,
'composingBase': composingBase, 'composingBase': composing.start,
'composingExtent': composingExtent, '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]. /// An interface to receive information from [TextInput].
...@@ -167,7 +176,7 @@ abstract class TextInputClient { ...@@ -167,7 +176,7 @@ abstract class TextInputClient {
const TextInputClient(); const TextInputClient();
/// Requests that this client update its editing state to the given value. /// 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. /// Requests that this client perform the given action.
void performAction(TextInputAction action); void performAction(TextInputAction action);
...@@ -198,11 +207,11 @@ class TextInputConnection { ...@@ -198,11 +207,11 @@ class TextInputConnection {
} }
/// Requests that the text input control change its internal state to match the given state. /// 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); assert(attached);
SystemChannels.textInput.invokeMethod( SystemChannels.textInput.invokeMethod(
'TextInput.setEditingState', 'TextInput.setEditingState',
state.toJSON(), value.toJSON(),
); );
} }
...@@ -247,7 +256,7 @@ class _TextInputClientHandler { ...@@ -247,7 +256,7 @@ class _TextInputClientHandler {
return; return;
switch (method) { switch (method) {
case 'TextInputClient.updateEditingState': case 'TextInputClient.updateEditingState':
_currentConnection._client.updateEditingState(new TextEditingState.fromJSON(args[1])); _currentConnection._client.updateEditingValue(new TextEditingValue.fromJSON(args[1]));
break; break;
case 'TextInputClient.performAction': case 'TextInputClient.performAction':
_currentConnection._client.performAction(_toTextInputAction(args[1])); _currentConnection._client.performAction(_toTextInputAction(args[1]));
......
...@@ -17,37 +17,23 @@ import 'scroll_physics.dart'; ...@@ -17,37 +17,23 @@ import 'scroll_physics.dart';
import 'scrollable.dart'; import 'scrollable.dart';
import 'text_selection.dart'; import 'text_selection.dart';
export 'package:flutter/painting.dart' show TextSelection; export 'package:flutter/services.dart' show TextSelection, TextInputType;
export 'package:flutter/services.dart' show TextInputType;
const Duration _kCursorBlinkHalfPeriod = const Duration(milliseconds: 500); const Duration _kCursorBlinkHalfPeriod = const Duration(milliseconds: 500);
TextSelection _getTextSelectionFromEditingState(TextEditingState state) { InputValue _getInputValueFromEditingValue(TextEditingValue value) {
return new TextSelection(
baseOffset: state.selectionBase,
extentOffset: state.selectionExtent,
affinity: state.selectionAffinity,
isDirectional: state.selectionIsDirectional,
);
}
InputValue _getInputValueFromEditingState(TextEditingState state) {
return new InputValue( return new InputValue(
text: state.text, text: value.text,
selection: _getTextSelectionFromEditingState(state), selection: value.selection,
composing: new TextRange(start: state.composingBase, end: state.composingExtent), composing: value.composing,
); );
} }
TextEditingState _getTextEditingStateFromInputValue(InputValue value) { TextEditingValue _getTextEditingValueFromInputValue(InputValue value) {
return new TextEditingState( return new TextEditingValue(
text: value.text, text: value.text,
selectionBase: value.selection.baseOffset, selection: value.selection,
selectionExtent: value.selection.extentOffset, composing: value.composing,
selectionAffinity: value.selection.affinity,
selectionIsDirectional: value.selection.isDirectional,
composingBase: value.composing.start,
composingExtent: value.composing.end,
); );
} }
...@@ -243,7 +229,7 @@ class EditableTextState extends State<EditableText> implements TextInputClient { ...@@ -243,7 +229,7 @@ class EditableTextState extends State<EditableText> implements TextInputClient {
if (_currentValue != config.value) { if (_currentValue != config.value) {
_currentValue = config.value; _currentValue = config.value;
if (_isAttachedToKeyboard) if (_isAttachedToKeyboard)
_textInputConnection.setEditingState(_getTextEditingStateFromInputValue(_currentValue)); _textInputConnection.setEditingState(_getTextEditingValueFromInputValue(_currentValue));
} }
} }
...@@ -271,7 +257,7 @@ class EditableTextState extends State<EditableText> implements TextInputClient { ...@@ -271,7 +257,7 @@ class EditableTextState extends State<EditableText> implements TextInputClient {
void _attachOrDetachKeyboard(bool focused) { void _attachOrDetachKeyboard(bool focused) {
if (focused && !_isAttachedToKeyboard && (_requestingFocus || config.autofocus)) { if (focused && !_isAttachedToKeyboard && (_requestingFocus || config.autofocus)) {
_textInputConnection = TextInput.attach(this, new TextInputConfiguration(inputType: config.keyboardType)) _textInputConnection = TextInput.attach(this, new TextInputConfiguration(inputType: config.keyboardType))
..setEditingState(_getTextEditingStateFromInputValue(_currentValue)) ..setEditingState(_getTextEditingValueFromInputValue(_currentValue))
..show(); ..show();
} else if (!focused) { } else if (!focused) {
if (_isAttachedToKeyboard) { if (_isAttachedToKeyboard) {
...@@ -308,8 +294,8 @@ class EditableTextState extends State<EditableText> implements TextInputClient { ...@@ -308,8 +294,8 @@ class EditableTextState extends State<EditableText> implements TextInputClient {
} }
@override @override
void updateEditingState(TextEditingState state) { void updateEditingValue(TextEditingValue value) {
_currentValue = _getInputValueFromEditingState(state); _currentValue = _getInputValueFromEditingValue(value);
if (config.onChanged != null) if (config.onChanged != null)
config.onChanged(_currentValue); config.onChanged(_currentValue);
if (_currentValue.text != config.value.text) { if (_currentValue.text != config.value.text) {
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter/scheduler.dart'; import 'package:flutter/scheduler.dart';
import 'basic.dart'; import 'basic.dart';
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'dart:ui' as ui show TextBox; import 'dart:ui' as ui show TextBox;
import 'package:flutter/rendering.dart'; import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
import 'rendering_tester.dart'; import 'rendering_tester.dart';
......
...@@ -155,10 +155,9 @@ void main() { ...@@ -155,10 +155,9 @@ void main() {
await tester.showKeyboard(find.byType(EditableText)); await tester.showKeyboard(find.byType(EditableText));
// Try the test again with a nonempty EditableText. // Try the test again with a nonempty EditableText.
tester.testTextInput.updateEditingState(const TextEditingState( tester.testTextInput.updateEditingValue(const TextEditingValue(
text: 'X', text: 'X',
selectionBase: 1, selection: const TextSelection.collapsed(offset: 1),
selectionExtent: 1,
)); ));
await checkCursorToggle(); await checkCursorToggle();
}); });
...@@ -182,10 +181,9 @@ void main() { ...@@ -182,10 +181,9 @@ void main() {
await tester.showKeyboard(find.byType(EditableText)); await tester.showKeyboard(find.byType(EditableText));
const String testValue = 'ABC'; const String testValue = 'ABC';
tester.testTextInput.updateEditingState(const TextEditingState( tester.testTextInput.updateEditingValue(const TextEditingValue(
text: testValue, text: testValue,
selectionBase: testValue.length, selection: const TextSelection.collapsed(offset: testValue.length),
selectionExtent: testValue.length,
)); ));
await tester.pump(); await tester.pump();
......
...@@ -38,14 +38,14 @@ class TestTextInput { ...@@ -38,14 +38,14 @@ class TestTextInput {
} }
} }
void updateEditingState(TextEditingState state) { void updateEditingValue(TextEditingValue value) {
expect(_client, isNonZero); expect(_client, isNonZero);
PlatformMessages.handlePlatformMessage( PlatformMessages.handlePlatformMessage(
SystemChannels.textInput.name, SystemChannels.textInput.name,
SystemChannels.textInput.codec.encodeMethodCall( SystemChannels.textInput.codec.encodeMethodCall(
new MethodCall( new MethodCall(
'TextInputClient.updateEditingState', 'TextInputClient.updateEditingState',
<dynamic>[_client, state.toJSON()], <dynamic>[_client, value.toJSON()],
), ),
), ),
(_) {}, (_) {},
...@@ -53,10 +53,9 @@ class TestTextInput { ...@@ -53,10 +53,9 @@ class TestTextInput {
} }
void enterText(String text) { void enterText(String text) {
updateEditingState(new TextEditingState( updateEditingValue(new TextEditingValue(
text: text, text: text,
composingBase: 0, composing: new TextRange(start: 0, end: text.length),
composingExtent: text.length,
)); ));
} }
} }
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