mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Align TextEditingValue and InputValue (#8922)
This patch prepares us to remove InputValue in favor of TextEditingValue.
This commit is contained in:
parent
d35a9db6fe
commit
d443d598c2
@ -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 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<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),
|
||||
));
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user