Unverified Commit ee30499a authored by xster's avatar xster Committed by GitHub

Stop using SelectionChangedCause internally to show the text selection toolbar (#27534)

parent c920cb7c
......@@ -456,15 +456,18 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
}
void _handleForcePressStarted(ForcePressDetails details) {
// The cause is not keyboard press but we would still like to just
// highlight the word without showing any handles or toolbar.
_renderEditable.selectWordsInRange(from: details.globalPosition, cause: SelectionChangedCause.keyboard);
_renderEditable.selectWordsInRange(
from: details.globalPosition,
cause: SelectionChangedCause.forcePress,
);
}
void _handleForcePressEnded(ForcePressDetails details) {
// The cause is not technically double tap, but we would still like to show
// the toolbar and handles.
_renderEditable.selectWordsInRange(from: details.globalPosition, cause: SelectionChangedCause.doubleTap);
_renderEditable.selectWordsInRange(
from: details.globalPosition,
cause: SelectionChangedCause.forcePress,
);
_editableTextKey.currentState.showToolbar();
}
void _handleSingleTapUp(TapUpDetails details) {
......@@ -474,10 +477,12 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with AutomaticK
void _handleSingleLongTapDown() {
_renderEditable.selectPosition(cause: SelectionChangedCause.longPress);
_editableTextKey.currentState.showToolbar();
}
void _handleDoubleTapDown(TapDownDetails details) {
_renderEditable.selectWord(cause: SelectionChangedCause.doubleTap);
_renderEditable.selectWord(cause: SelectionChangedCause.tap);
_editableTextKey.currentState.showToolbar();
}
@override
......
......@@ -582,11 +582,6 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
_editableTextKey.currentState?.requestKeyboard();
}
void _handleSelectionChanged(TextSelection selection, SelectionChangedCause cause) {
if (cause == SelectionChangedCause.longPress)
Feedback.forLongPress(context);
}
InteractiveInkFeature _createInkFeature(TapDownDetails details) {
final MaterialInkController inkController = Material.of(context);
final ThemeData themeData = Theme.of(context);
......@@ -630,9 +625,11 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
void _handleForcePressStarted(ForcePressDetails details) {
if (widget.selectionEnabled) {
// The cause is not technically double tap, but we would like to show
// the toolbar.
_renderEditable.selectWordsInRange(from: details.globalPosition, cause: SelectionChangedCause.doubleTap);
_renderEditable.selectWordsInRange(
from: details.globalPosition,
cause: SelectionChangedCause.forcePress,
);
_editableTextKey.currentState.showToolbar();
}
}
......@@ -667,14 +664,19 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
case TargetPlatform.android:
case TargetPlatform.fuchsia:
_renderEditable.selectWord(cause: SelectionChangedCause.longPress);
Feedback.forLongPress(context);
break;
}
_editableTextKey.currentState.showToolbar();
}
_confirmCurrentSplash();
}
void _handleDoubleTapDown(TapDownDetails details) {
_renderEditable.selectWord(cause: SelectionChangedCause.doubleTap);
if (widget.selectionEnabled) {
_renderEditable.selectWord(cause: SelectionChangedCause.doubleTap);
_editableTextKey.currentState.showToolbar();
}
}
void _startSplash(TapDownDetails details) {
......@@ -784,7 +786,6 @@ class _TextFieldState extends State<TextField> with AutomaticKeepAliveClientMixi
onChanged: widget.onChanged,
onEditingComplete: widget.onEditingComplete,
onSubmitted: widget.onSubmitted,
onSelectionChanged: _handleSelectionChanged,
inputFormatters: formatters,
rendererIgnoresPointer: true,
cursorWidth: widget.cursorWidth,
......
......@@ -45,6 +45,10 @@ enum SelectionChangedCause {
/// location of the cursor) to change.
longPress,
/// The user force-pressed the text and that caused the selection (or the
/// location of the cursor) to change.
forcePress,
/// The user used the keyboard to change the selection or the location of the
/// cursor.
///
......@@ -737,12 +741,12 @@ class RenderEditable extends RenderBox {
markNeedsLayout();
}
///{@template flutter.rendering.editable.paintCursorOnTop}
/// {@template flutter.rendering.editable.paintCursorOnTop}
/// If the cursor should be painted on top of the text or underneath it.
///
/// By default, the cursor should be painted on top for iOS platforms and
/// underneath for Android platforms.
/// {@end template}
/// {@endtemplate}
bool get paintCursorAboveText => _paintCursorOnTop;
bool _paintCursorOnTop;
set paintCursorAboveText(bool value) {
......@@ -759,7 +763,7 @@ class RenderEditable extends RenderBox {
/// (-[cursorWidth] * 0.5, 0.0) on iOS platforms and (0, 0) on Android
/// platforms. The origin from where the offset is applied to is the arbitrary
/// location where the cursor ends up being rendered from by default.
/// {@end template}
/// {@endtemplate}
Offset get cursorOffset => _cursorOffset;
Offset _cursorOffset;
set cursorOffset(Offset value) {
......@@ -1233,6 +1237,15 @@ class RenderEditable extends RenderBox {
}
/// Move selection to the location of the last tap down.
///
/// {@template flutter.rendering.editable.select}
/// This method is mainly used to translate user inputs in global positions
/// into a [TextSelection]. When used in conjunction with a [EditableText],
/// the selection change is fed back into [TextEditingController.selection].
///
/// If you have a [TextEditingController], it's generally easier to
/// programmatically manipulate its `value` or `selection` directly.
/// {@endtemplate}
void selectPosition({@required SelectionChangedCause cause}) {
assert(cause != null);
_layoutText(constraints.maxWidth);
......@@ -1244,6 +1257,8 @@ class RenderEditable extends RenderBox {
}
/// Select a word around the location of the last tap down.
///
/// {@macro flutter.rendering.editable.select}
void selectWord({@required SelectionChangedCause cause}) {
selectWordsInRange(from: _lastTapDownPosition, cause: cause);
}
......@@ -1252,6 +1267,8 @@ class RenderEditable extends RenderBox {
///
/// The first and last endpoints of the selection will always be at the
/// beginning and end of a word respectively.
///
/// {@macro flutter.rendering.editable.select}
void selectWordsInRange({@required Offset from, Offset to, @required SelectionChangedCause cause}) {
assert(cause != null);
_layoutText(constraints.maxWidth);
......@@ -1272,6 +1289,8 @@ class RenderEditable extends RenderBox {
}
/// Move the selection to the beginning or end of a word.
///
/// {@macro flutter.rendering.editable.select}
void selectWordEdge({@required SelectionChangedCause cause}) {
assert(cause != null);
_layoutText(constraints.maxWidth);
......
......@@ -745,7 +745,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
renderEditable.setFloatingCursor(FloatingCursorDragState.End, finalPosition, _lastTextPosition);
if (_lastTextPosition.offset != renderEditable.selection.baseOffset)
// The cause is technically the force cursor, but the cause is listed as tap as the desired functionality is the same.
_handleSelectionChanged(TextSelection.collapsed(offset: _lastTextPosition.offset), renderEditable, SelectionChangedCause.tap);
_handleSelectionChanged(TextSelection.collapsed(offset: _lastTextPosition.offset), renderEditable, SelectionChangedCause.forcePress);
_startCaretRect = null;
_lastTextPosition = null;
_pointOffsetOrigin = null;
......@@ -923,8 +923,6 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
final bool longPress = cause == SelectionChangedCause.longPress;
if (cause != SelectionChangedCause.keyboard && (_value.text.isNotEmpty || longPress))
_selectionOverlay.showHandles();
if (longPress || cause == SelectionChangedCause.doubleTap)
_selectionOverlay.showToolbar();
if (widget.onSelectionChanged != null)
widget.onSelectionChanged(selection, cause);
}
......@@ -1150,6 +1148,18 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
_scrollController.jumpTo(_getScrollOffsetForCaret(renderEditable.getLocalRectForCaret(position)));
}
/// Shows the selection toolbar at the location of the current cursor.
///
/// Returns `false` if a toolbar couldn't be shown such as when no text
/// selection currently exists.
bool showToolbar() {
if (_selectionOverlay == null)
return false;
_selectionOverlay.showToolbar();
return true;
}
@override
void hideToolbar() {
_selectionOverlay?.hide();
......
......@@ -71,6 +71,7 @@ void main() {
// Long-press to bring up the text editing controls.
final Finder textFinder = find.byKey(editableTextKey);
await tester.longPress(textFinder);
tester.state<EditableTextState>(textFinder).showToolbar();
await tester.pump();
await tester.pump(const Duration(milliseconds: 500));
await tester.pump(const Duration(milliseconds: 500));
......@@ -125,6 +126,7 @@ void main() {
// Long-press to bring up the text editing controls.
final Finder textFinder = find.byKey(editableTextKey);
await tester.longPress(textFinder);
tester.state<EditableTextState>(textFinder).showToolbar();
await tester.pump();
await tester.tap(find.text('PASTE'));
......
......@@ -478,6 +478,45 @@ void main() {
equals('TextInputAction.done'));
});
testWidgets('can only show toolbar when there is text and a selection',
(WidgetTester tester) async {
await tester.pumpWidget(
MaterialApp(
home: EditableText(
backgroundCursorColor: Colors.grey,
controller: controller,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
selectionControls: materialTextSelectionControls,
),
),
);
final EditableTextState state =
tester.state<EditableTextState>(find.byType(EditableText));
expect(state.showToolbar(), false);
await tester.pump();
expect(find.text('PASTE'), findsNothing);
controller.text = 'blah';
await tester.pump();
expect(state.showToolbar(), false);
await tester.pump();
expect(find.text('PASTE'), findsNothing);
// Select something. Doesn't really matter what.
state.renderEditable.selectWordsInRange(
from: const Offset(0, 0),
cause: SelectionChangedCause.tap,
);
await tester.pump();
expect(state.showToolbar(), true);
await tester.pump();
expect(find.text('PASTE'), findsOneWidget);
});
testWidgets('Fires onChanged when text changes via TextSelectionOverlay',
(WidgetTester tester) async {
final GlobalKey<EditableTextState> editableTextKey =
......@@ -513,6 +552,7 @@ void main() {
// Long-press to bring up the text editing controls.
final Finder textFinder = find.byKey(editableTextKey);
await tester.longPress(textFinder);
tester.state<EditableTextState>(textFinder).showToolbar();
await tester.pump();
await tester.tap(find.text('PASTE'));
......
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