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';
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';
......
......@@ -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';
......@@ -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';
......
......@@ -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);
......
......@@ -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?
......
......@@ -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';
......
......@@ -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';
......
......@@ -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 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.
/// The selection and composing range must be within the text.
///
/// 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;
/// 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
});
/// Creates an instance of this class from a JSON object.
factory TextEditingState.fromJSON(Map<String, dynamic> encoded) {
return new TextEditingState(
factory TextEditingValue.fromJSON(Map<String, dynamic> 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<String, dynamic> toJSON() {
return <String, dynamic>{
'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]));
......
......@@ -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<EditableText> 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<EditableText> 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<EditableText> 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) {
......
......@@ -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';
......
......@@ -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';
......
......@@ -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();
......
......@@ -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',
<dynamic>[_client, state.toJSON()],
<dynamic>[_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),
));
}
}
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