Unverified Commit 944a16a7 authored by YeungKC's avatar YeungKC Committed by GitHub

Migrate RenderEditable shortcuts from RawKeyboard listeners (#85381)

No more RawKeyboard listeners in RenderEditable, use Shortcuts instead.
parent d2fe3aff
...@@ -11,7 +11,6 @@ import 'package:flutter/foundation.dart'; ...@@ -11,7 +11,6 @@ import 'package:flutter/foundation.dart';
import 'package:flutter/gestures.dart'; import 'package:flutter/gestures.dart';
import 'package:flutter/semantics.dart'; import 'package:flutter/semantics.dart';
import 'package:flutter/services.dart'; import 'package:flutter/services.dart';
import 'package:vector_math/vector_math_64.dart'; import 'package:vector_math/vector_math_64.dart';
import 'box.dart'; import 'box.dart';
...@@ -636,77 +635,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, ...@@ -636,77 +635,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
onSelectionChanged?.call(nextSelection, this, cause); onSelectionChanged?.call(nextSelection, this, cause);
} }
static final Set<LogicalKeyboardKey> _movementKeys = <LogicalKeyboardKey>{
LogicalKeyboardKey.arrowRight,
LogicalKeyboardKey.arrowLeft,
LogicalKeyboardKey.arrowUp,
LogicalKeyboardKey.arrowDown,
};
static final Set<LogicalKeyboardKey> _shortcutKeys = <LogicalKeyboardKey>{
LogicalKeyboardKey.keyC,
LogicalKeyboardKey.keyV,
LogicalKeyboardKey.keyX,
LogicalKeyboardKey.delete,
LogicalKeyboardKey.backspace,
};
static final Set<LogicalKeyboardKey> _nonModifierKeys = <LogicalKeyboardKey>{
..._shortcutKeys,
..._movementKeys,
};
static final Set<LogicalKeyboardKey> _modifierKeys = <LogicalKeyboardKey>{
LogicalKeyboardKey.shift,
LogicalKeyboardKey.control,
LogicalKeyboardKey.alt,
};
static final Set<LogicalKeyboardKey> _macOsModifierKeys = <LogicalKeyboardKey>{
LogicalKeyboardKey.shift,
LogicalKeyboardKey.meta,
LogicalKeyboardKey.alt,
};
static final Set<LogicalKeyboardKey> _interestingKeys = <LogicalKeyboardKey>{
..._modifierKeys,
..._macOsModifierKeys,
..._nonModifierKeys,
};
void _handleKeyEvent(RawKeyEvent keyEvent) {
if (kIsWeb) {
// On web platform, we should ignore the key because it's processed already.
return;
}
if (keyEvent is! RawKeyDownEvent)
return;
final Set<LogicalKeyboardKey> keysPressed = LogicalKeyboardKey.collapseSynonyms(RawKeyboard.instance.keysPressed);
final LogicalKeyboardKey key = keyEvent.logicalKey;
final bool isMacOS = keyEvent.data is RawKeyEventDataMacOs;
if (!_nonModifierKeys.contains(key) ||
keysPressed.difference(isMacOS ? _macOsModifierKeys : _modifierKeys).length > 1 ||
keysPressed.difference(_interestingKeys).isNotEmpty) {
// If the most recently pressed key isn't a non-modifier key, or more than
// one non-modifier key is down, or keys other than the ones we're interested in
// are pressed, just ignore the keypress.
return;
}
// TODO(ianh): It seems to be entirely possible for the selection to be null here, but
// all the keyboard handling functions assume it is not.
assert(selection != null);
final bool isShortcutModifierPressed = isMacOS ? keyEvent.isMetaPressed : keyEvent.isControlPressed;
if (isShortcutModifierPressed && _shortcutKeys.contains(key)) {
// _handleShortcuts depends on being started in the same stack invocation
// as the _handleKeyEvent method
_handleShortcuts(key);
}
}
/// Returns the index into the string of the next character boundary after the /// Returns the index into the string of the next character boundary after the
/// given index. /// given index.
/// ///
...@@ -2244,45 +2172,62 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, ...@@ -2244,45 +2172,62 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
); );
} }
// Handles shortcut functionality including cut, copy, paste using /// Copy current [selection] to [Clipboard].
// using control/command + (X, C, V). void copySelection() {
Future<void> _handleShortcuts(LogicalKeyboardKey key) async {
final TextSelection selection = textSelectionDelegate.textEditingValue.selection; final TextSelection selection = textSelectionDelegate.textEditingValue.selection;
final String text = textSelectionDelegate.textEditingValue.text; final String text = textSelectionDelegate.textEditingValue.text;
assert(selection != null); assert(selection != null);
assert(_shortcutKeys.contains(key), 'shortcut key $key not recognized.'); if (!selection.isCollapsed) {
if (key == LogicalKeyboardKey.keyC) { Clipboard.setData(ClipboardData(text: selection.textInside(text)));
if (!selection.isCollapsed) { }
Clipboard.setData(ClipboardData(text: selection.textInside(text))); }
}
/// Cut current [selection] to Clipboard.
///
/// {@macro flutter.rendering.RenderEditable.cause}
void cutSelection(SelectionChangedCause cause) {
if (_readOnly) {
return; return;
} }
TextEditingValue? value; final TextSelection selection = textSelectionDelegate.textEditingValue.selection;
if (key == LogicalKeyboardKey.keyX && !_readOnly) { final String text = textSelectionDelegate.textEditingValue.text;
if (!selection.isCollapsed) { assert(selection != null);
Clipboard.setData(ClipboardData(text: selection.textInside(text))); if (!selection.isCollapsed) {
value = TextEditingValue( Clipboard.setData(ClipboardData(text: selection.textInside(text)));
_setTextEditingValue(
TextEditingValue(
text: selection.textBefore(text) + selection.textAfter(text), text: selection.textBefore(text) + selection.textAfter(text),
selection: TextSelection.collapsed(offset: math.min(selection.start, selection.end)), selection: TextSelection.collapsed(offset: math.min(selection.start, selection.end)),
); ),
} cause,
} else if (key == LogicalKeyboardKey.keyV && !_readOnly) { );
// Snapshot the input before using `await`. }
// See https://github.com/flutter/flutter/issues/11427 }
final ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain);
if (data != null && selection.isValid) { /// Paste text from [Clipboard].
value = TextEditingValue( ///
text: selection.textBefore(text) + data.text! + selection.textAfter(text), /// If there is currently a selection, it will be replaced.
selection: TextSelection.collapsed( ///
offset: math.min(selection.start, selection.end) + data.text!.length, /// {@macro flutter.rendering.RenderEditable.cause}
), Future<void> pasteText(SelectionChangedCause cause) async {
); if (_readOnly) {
} return;
} }
if (value != null) { final TextSelection selection = textSelectionDelegate.textEditingValue.selection;
final String text = textSelectionDelegate.textEditingValue.text;
assert(selection != null);
// Snapshot the input before using `await`.
// See https://github.com/flutter/flutter/issues/11427
final ClipboardData? data = await Clipboard.getData(Clipboard.kTextPlain);
if (data != null && selection.isValid) {
_setTextEditingValue( _setTextEditingValue(
value, TextEditingValue(
SelectionChangedCause.keyboard, text: selection.textBefore(text) + data.text! + selection.textAfter(text),
selection: TextSelection.collapsed(
offset: math.min(selection.start, selection.end) + data.text!.length,
),
),
cause,
); );
} }
} }
...@@ -2441,32 +2386,12 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, ...@@ -2441,32 +2386,12 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
/// Whether the editable is currently focused. /// Whether the editable is currently focused.
bool get hasFocus => _hasFocus; bool get hasFocus => _hasFocus;
bool _hasFocus = false; bool _hasFocus = false;
bool _listenerAttached = false;
set hasFocus(bool value) { set hasFocus(bool value) {
assert(value != null); assert(value != null);
if (_hasFocus == value) if (_hasFocus == value)
return; return;
_hasFocus = value; _hasFocus = value;
markNeedsSemanticsUpdate(); markNeedsSemanticsUpdate();
if (!attached) {
assert(!_listenerAttached);
return;
}
if (_hasFocus) {
assert(!_listenerAttached);
// TODO(justinmc): This listener should be ported to Actions and removed.
// https://github.com/flutter/flutter/issues/75004
RawKeyboard.instance.addListener(_handleKeyEvent);
_listenerAttached = true;
} else {
assert(_listenerAttached);
// TODO(justinmc): This listener should be ported to Actions and removed.
// https://github.com/flutter/flutter/issues/75004
RawKeyboard.instance.removeListener(_handleKeyEvent);
_listenerAttached = false;
}
} }
/// Whether this rendering object will take a full line regardless the text width. /// Whether this rendering object will take a full line regardless the text width.
...@@ -3073,11 +2998,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, ...@@ -3073,11 +2998,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
_offset.addListener(markNeedsPaint); _offset.addListener(markNeedsPaint);
_showHideCursor(); _showHideCursor();
_showCursor.addListener(_showHideCursor); _showCursor.addListener(_showHideCursor);
assert(!_listenerAttached);
if (_hasFocus) {
RawKeyboard.instance.addListener(_handleKeyEvent);
_listenerAttached = true;
}
} }
@override @override
...@@ -3086,12 +3006,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin, ...@@ -3086,12 +3006,6 @@ class RenderEditable extends RenderBox with RelayoutWhenSystemFontsChangeMixin,
_longPress.dispose(); _longPress.dispose();
_offset.removeListener(markNeedsPaint); _offset.removeListener(markNeedsPaint);
_showCursor.removeListener(_showHideCursor); _showCursor.removeListener(_showHideCursor);
// TODO(justinmc): This listener should be ported to Actions and removed.
// https://github.com/flutter/flutter/issues/75004
if (_listenerAttached) {
RawKeyboard.instance.removeListener(_handleKeyEvent);
_listenerAttached = false;
}
super.detach(); super.detach();
_foregroundRenderObject?.detach(); _foregroundRenderObject?.detach();
_backgroundRenderObject?.detach(); _backgroundRenderObject?.detach();
......
...@@ -67,6 +67,9 @@ class DefaultTextEditingActions extends Actions{ ...@@ -67,6 +67,9 @@ class DefaultTextEditingActions extends Actions{
MoveSelectionToStartTextIntent: _MoveSelectionToStartTextAction(), MoveSelectionToStartTextIntent: _MoveSelectionToStartTextAction(),
MoveSelectionUpTextIntent: _MoveSelectionUpTextAction(), MoveSelectionUpTextIntent: _MoveSelectionUpTextAction(),
SelectAllTextIntent: _SelectAllTextAction(), SelectAllTextIntent: _SelectAllTextAction(),
CopySelectionTextIntent: _CopySelectionTextAction(),
CutSelectionTextIntent: _CutSelectionTextAction(),
PasteTextIntent: _PasteTextAction(),
}; };
} }
...@@ -293,9 +296,31 @@ class _MoveSelectionToStartTextAction extends TextEditingAction<MoveSelectionToS ...@@ -293,9 +296,31 @@ class _MoveSelectionToStartTextAction extends TextEditingAction<MoveSelectionToS
} }
} }
class _SelectAllTextAction extends TextEditingAction<SelectAllTextIntent> { class _SelectAllTextAction extends TextEditingAction<SelectAllTextIntent> {
@override @override
Object? invoke(SelectAllTextIntent intent, [BuildContext? context]) { Object? invoke(SelectAllTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.renderEditable.selectAll(SelectionChangedCause.keyboard); textEditingActionTarget!.renderEditable.selectAll(SelectionChangedCause.keyboard);
} }
} }
class _CopySelectionTextAction extends TextEditingAction<CopySelectionTextIntent> {
@override
Object? invoke(CopySelectionTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.renderEditable.copySelection();
}
}
class _CutSelectionTextAction extends TextEditingAction<CutSelectionTextIntent> {
@override
Object? invoke(CutSelectionTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.renderEditable.cutSelection(SelectionChangedCause.keyboard);
}
}
class _PasteTextAction extends TextEditingAction<PasteTextIntent> {
@override
Object? invoke(PasteTextIntent intent, [BuildContext? context]) {
textEditingActionTarget!.renderEditable.pasteText(SelectionChangedCause.keyboard);
}
}
...@@ -187,11 +187,17 @@ class DefaultTextEditingShortcuts extends Shortcuts { ...@@ -187,11 +187,17 @@ class DefaultTextEditingShortcuts extends Shortcuts {
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): ExtendSelectionLeftTextIntent(), SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): ExtendSelectionLeftTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): ExtendSelectionRightTextIntent(), SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): ExtendSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(), SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(),
SingleActivator(LogicalKeyboardKey.keyX, control: true): CutSelectionTextIntent(),
SingleActivator(LogicalKeyboardKey.keyC, control: true): CopySelectionTextIntent(),
SingleActivator(LogicalKeyboardKey.keyV, control: true): PasteTextIntent(),
SingleActivator(LogicalKeyboardKey.keyA, control: true): SelectAllTextIntent(), SingleActivator(LogicalKeyboardKey.keyA, control: true): SelectAllTextIntent(),
// The following key combinations have no effect on text editing on this // The following key combinations have no effect on text editing on this
// platform: // platform:
// * End // * End
// * Home // * Home
// * Meta + X
// * Meta + C
// * Meta + V
// * Meta + A // * Meta + A
// * Meta + arrow down // * Meta + arrow down
// * Meta + arrow left // * Meta + arrow left
...@@ -234,11 +240,17 @@ class DefaultTextEditingShortcuts extends Shortcuts { ...@@ -234,11 +240,17 @@ class DefaultTextEditingShortcuts extends Shortcuts {
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): ExtendSelectionLeftTextIntent(), SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): ExtendSelectionLeftTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): ExtendSelectionRightTextIntent(), SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): ExtendSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(), SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(),
SingleActivator(LogicalKeyboardKey.keyX, control: true): CutSelectionTextIntent(),
SingleActivator(LogicalKeyboardKey.keyC, control: true): CopySelectionTextIntent(),
SingleActivator(LogicalKeyboardKey.keyV, control: true): PasteTextIntent(),
SingleActivator(LogicalKeyboardKey.keyA, control: true): SelectAllTextIntent(), SingleActivator(LogicalKeyboardKey.keyA, control: true): SelectAllTextIntent(),
// The following key combinations have no effect on text editing on this // The following key combinations have no effect on text editing on this
// platform: // platform:
// * End // * End
// * Home // * Home
// * Meta + X
// * Meta + C
// * Meta + V
// * Meta + A // * Meta + A
// * Meta + arrow down // * Meta + arrow down
// * Meta + arrow left // * Meta + arrow left
...@@ -281,11 +293,17 @@ class DefaultTextEditingShortcuts extends Shortcuts { ...@@ -281,11 +293,17 @@ class DefaultTextEditingShortcuts extends Shortcuts {
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): ExtendSelectionLeftTextIntent(), SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): ExtendSelectionLeftTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): ExtendSelectionRightTextIntent(), SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): ExtendSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(), SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(),
SingleActivator(LogicalKeyboardKey.keyX, control: true): CutSelectionTextIntent(),
SingleActivator(LogicalKeyboardKey.keyC, control: true): CopySelectionTextIntent(),
SingleActivator(LogicalKeyboardKey.keyV, control: true): PasteTextIntent(),
SingleActivator(LogicalKeyboardKey.keyA, control: true): SelectAllTextIntent(), SingleActivator(LogicalKeyboardKey.keyA, control: true): SelectAllTextIntent(),
// The following key combinations have no effect on text editing on this // The following key combinations have no effect on text editing on this
// platform: // platform:
// * End // * End
// * Home // * Home
// * Meta + X
// * Meta + C
// * Meta + V
// * Meta + A // * Meta + A
// * Meta + arrow down // * Meta + arrow down
// * Meta + arrow left // * Meta + arrow left
...@@ -328,11 +346,17 @@ class DefaultTextEditingShortcuts extends Shortcuts { ...@@ -328,11 +346,17 @@ class DefaultTextEditingShortcuts extends Shortcuts {
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): ExtendSelectionLeftTextIntent(), SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): ExtendSelectionLeftTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): ExtendSelectionRightTextIntent(), SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): ExtendSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(), SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(),
SingleActivator(LogicalKeyboardKey.keyX, control: true): CutSelectionTextIntent(),
SingleActivator(LogicalKeyboardKey.keyC, control: true): CopySelectionTextIntent(),
SingleActivator(LogicalKeyboardKey.keyV, control: true): PasteTextIntent(),
SingleActivator(LogicalKeyboardKey.keyA, control: true): SelectAllTextIntent(), SingleActivator(LogicalKeyboardKey.keyA, control: true): SelectAllTextIntent(),
// The following key combinations have no effect on text editing on this // The following key combinations have no effect on text editing on this
// platform: // platform:
// * End // * End
// * Home // * Home
// * Meta + X
// * Meta + C
// * Meta + V
// * Meta + A // * Meta + A
// * Meta + arrow down // * Meta + arrow down
// * Meta + arrow left // * Meta + arrow left
...@@ -381,9 +405,15 @@ class DefaultTextEditingShortcuts extends Shortcuts { ...@@ -381,9 +405,15 @@ class DefaultTextEditingShortcuts extends Shortcuts {
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(), SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(),
SingleActivator(LogicalKeyboardKey.end, shift: true): ExpandSelectionToEndTextIntent(), SingleActivator(LogicalKeyboardKey.end, shift: true): ExpandSelectionToEndTextIntent(),
SingleActivator(LogicalKeyboardKey.home, shift: true): ExpandSelectionToStartTextIntent(), SingleActivator(LogicalKeyboardKey.home, shift: true): ExpandSelectionToStartTextIntent(),
SingleActivator(LogicalKeyboardKey.keyX, meta: true): CutSelectionTextIntent(),
SingleActivator(LogicalKeyboardKey.keyC, meta: true): CopySelectionTextIntent(),
SingleActivator(LogicalKeyboardKey.keyV, meta: true): PasteTextIntent(),
SingleActivator(LogicalKeyboardKey.keyA, meta: true): SelectAllTextIntent(), SingleActivator(LogicalKeyboardKey.keyA, meta: true): SelectAllTextIntent(),
// The following key combinations have no effect on text editing on this // The following key combinations have no effect on text editing on this
// platform: // platform:
// * Control + X
// * Control + C
// * Control + V
// * Control + A // * Control + A
// * Control + arrow left // * Control + arrow left
// * Control + arrow right // * Control + arrow right
...@@ -426,9 +456,15 @@ class DefaultTextEditingShortcuts extends Shortcuts { ...@@ -426,9 +456,15 @@ class DefaultTextEditingShortcuts extends Shortcuts {
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(), SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): ExtendSelectionUpTextIntent(),
SingleActivator(LogicalKeyboardKey.end, shift: true): ExpandSelectionRightByLineTextIntent(), SingleActivator(LogicalKeyboardKey.end, shift: true): ExpandSelectionRightByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.home, shift: true): ExpandSelectionLeftByLineTextIntent(), SingleActivator(LogicalKeyboardKey.home, shift: true): ExpandSelectionLeftByLineTextIntent(),
SingleActivator(LogicalKeyboardKey.keyX, control: true): CutSelectionTextIntent(),
SingleActivator(LogicalKeyboardKey.keyC, control: true): CopySelectionTextIntent(),
SingleActivator(LogicalKeyboardKey.keyV, control: true): PasteTextIntent(),
SingleActivator(LogicalKeyboardKey.keyA, control: true): SelectAllTextIntent(), SingleActivator(LogicalKeyboardKey.keyA, control: true): SelectAllTextIntent(),
// The following key combinations have no effect on text editing on this // The following key combinations have no effect on text editing on this
// platform: // platform:
// * Meta + X
// * Meta + C
// * Meta + V
// * Meta + A // * Meta + A
// * Meta + arrow down // * Meta + arrow down
// * Meta + arrow left // * Meta + arrow left
...@@ -486,6 +522,12 @@ class DefaultTextEditingShortcuts extends Shortcuts { ...@@ -486,6 +522,12 @@ class DefaultTextEditingShortcuts extends Shortcuts {
SingleActivator(LogicalKeyboardKey.end, shift: true): DoNothingAndStopPropagationTextIntent(), SingleActivator(LogicalKeyboardKey.end, shift: true): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.home, shift: true): DoNothingAndStopPropagationTextIntent(), SingleActivator(LogicalKeyboardKey.home, shift: true): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.space): DoNothingAndStopPropagationTextIntent(), SingleActivator(LogicalKeyboardKey.space): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.keyX, control: true): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.keyX, meta: true): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.keyC, control: true): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.keyC, meta: true): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.keyV, control: true): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.keyV, meta: true): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.keyA, control: true): DoNothingAndStopPropagationTextIntent(), SingleActivator(LogicalKeyboardKey.keyA, control: true): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.keyA, meta: true): DoNothingAndStopPropagationTextIntent(), SingleActivator(LogicalKeyboardKey.keyA, meta: true): DoNothingAndStopPropagationTextIntent(),
}; };
......
...@@ -274,3 +274,27 @@ class SelectAllTextIntent extends Intent{ ...@@ -274,3 +274,27 @@ class SelectAllTextIntent extends Intent{
/// Creates an instance of SelectAllTextIntent. /// Creates an instance of SelectAllTextIntent.
const SelectAllTextIntent(); const SelectAllTextIntent();
} }
/// An [Intent] to copy selection in the field.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class CopySelectionTextIntent extends Intent{
/// Creates an instance of CopyTextIntent.
const CopySelectionTextIntent();
}
/// An [Intent] to cut selection in the field.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class CutSelectionTextIntent extends Intent{
/// Creates an instance of CutTextIntent.
const CutSelectionTextIntent();
}
/// An [Intent] to paste text from [Clipboard] to the field.
///
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
class PasteTextIntent extends Intent{
/// Creates an instance of PasteTextIntent.
const PasteTextIntent();
}
...@@ -7215,6 +7215,13 @@ void main() { ...@@ -7215,6 +7215,13 @@ void main() {
}); });
testWidgets('can change behavior by overriding text editing shortcuts', (WidgetTester tester) async { testWidgets('can change behavior by overriding text editing shortcuts', (WidgetTester tester) async {
const Map<SingleActivator, Intent> testShortcuts = <SingleActivator, Intent>{
SingleActivator(LogicalKeyboardKey.arrowLeft): MoveSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.keyX, control: true): MoveSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.keyC, control: true): MoveSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.keyV, control: true): MoveSelectionRightTextIntent(),
SingleActivator(LogicalKeyboardKey.keyA, control: true): MoveSelectionRightTextIntent(),
};
final TextEditingController controller = TextEditingController(text: testText); final TextEditingController controller = TextEditingController(text: testText);
controller.selection = const TextSelection( controller.selection = const TextSelection(
baseOffset: 0, baseOffset: 0,
...@@ -7227,9 +7234,7 @@ void main() { ...@@ -7227,9 +7234,7 @@ void main() {
child: SizedBox( child: SizedBox(
width: 400, width: 400,
child: Shortcuts( child: Shortcuts(
shortcuts: const <ShortcutActivator, Intent>{ shortcuts: testShortcuts,
SingleActivator(LogicalKeyboardKey.arrowLeft): MoveSelectionRightTextIntent(),
},
child: EditableText( child: EditableText(
maxLines: 10, maxLines: 10,
controller: controller, controller: controller,
...@@ -7256,72 +7261,20 @@ void main() { ...@@ -7256,72 +7261,20 @@ void main() {
expect(controller.selection.isCollapsed, isTrue); expect(controller.selection.isCollapsed, isTrue);
expect(controller.selection.baseOffset, 1); expect(controller.selection.baseOffset, 1);
// And the left arrow also moves to the right due to the Shortcuts override. // And the testShortcuts also moves to the right due to the Shortcuts override.
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft); for (final SingleActivator singleActivator in testShortcuts.keys) {
await tester.pump(); controller.selection = const TextSelection.collapsed(offset: 0);
expect(controller.selection.isCollapsed, isTrue); await tester.pump();
expect(controller.selection.baseOffset, 2);
// On web, using keyboard for selection is handled by the browser.
}, skip: kIsWeb);
testWidgets('can override select all via Shortcuts', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController(text: testText);
controller.selection = const TextSelection(
baseOffset: 0,
extentOffset: 0,
affinity: TextAffinity.upstream,
);
await tester.pumpWidget(MaterialApp(
home: Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 400,
child: Shortcuts(
shortcuts: const <ShortcutActivator, Intent>{
SingleActivator(LogicalKeyboardKey.keyA, control: true): MoveSelectionRightTextIntent(),
},
child: EditableText(
maxLines: 10,
controller: controller,
showSelectionHandles: true,
autofocus: true,
focusNode: focusNode,
style: Typography.material2018(platform: TargetPlatform.android).black.subtitle1!,
cursorColor: Colors.blue,
backgroundCursorColor: Colors.grey,
selectionControls: materialTextSelectionControls,
keyboardType: TextInputType.text,
textAlign: TextAlign.right,
),
),
),
),
));
await tester.pump(); // Wait for autofocus to take effect.
expect(controller.selection.isCollapsed, isTrue);
expect(controller.selection.baseOffset, 0);
// meta + A moves the cursor right instead of doing select all. await sendKeys(
await sendKeys( tester,
tester, <LogicalKeyboardKey>[singleActivator.trigger],
<LogicalKeyboardKey>[LogicalKeyboardKey.keyA], shortcutModifier: singleActivator.control,
shortcutModifier: true, targetPlatform: defaultTargetPlatform,
targetPlatform: defaultTargetPlatform, );
); expect(controller.selection.isCollapsed, isTrue);
await tester.pump(); expect(controller.selection.baseOffset, 1);
expect(controller.selection.isCollapsed, isTrue); }
expect(controller.selection.baseOffset, 1);
await sendKeys(
tester,
<LogicalKeyboardKey>[LogicalKeyboardKey.keyA],
shortcutModifier: true,
targetPlatform: defaultTargetPlatform,
);
await tester.pump();
expect(controller.selection.isCollapsed, isTrue);
expect(controller.selection.baseOffset, 2);
// On web, using keyboard for selection is handled by the browser. // On web, using keyboard for selection is handled by the browser.
}, skip: kIsWeb); }, skip: kIsWeb);
...@@ -7555,20 +7508,35 @@ void main() { ...@@ -7555,20 +7508,35 @@ void main() {
extentOffset: 0, extentOffset: 0,
affinity: TextAffinity.upstream, affinity: TextAffinity.upstream,
); );
late final bool myIntentWasCalled; bool myIntentWasCalled = false;
final _MyMoveSelectionRightTextAction myMoveSelectionRightTextAction =
_MyMoveSelectionRightTextAction(
onInvoke: () {
myIntentWasCalled = true;
},
);
const Iterable<SingleActivator> testSingleActivators = <SingleActivator>{
SingleActivator(LogicalKeyboardKey.arrowLeft),
SingleActivator(LogicalKeyboardKey.keyX, control: true),
SingleActivator(LogicalKeyboardKey.keyC, control: true),
SingleActivator(LogicalKeyboardKey.keyV, control: true),
SingleActivator(LogicalKeyboardKey.keyA, control: true),
};
final Map<Type, Action<Intent>> testActions = <Type, Action<Intent>>{
MoveSelectionLeftTextIntent: myMoveSelectionRightTextAction,
CutSelectionTextIntent: myMoveSelectionRightTextAction,
CopySelectionTextIntent: myMoveSelectionRightTextAction,
PasteTextIntent: myMoveSelectionRightTextAction,
SelectAllTextIntent: myMoveSelectionRightTextAction,
};
await tester.pumpWidget(MaterialApp( await tester.pumpWidget(MaterialApp(
home: Align( home: Align(
alignment: Alignment.topLeft, alignment: Alignment.topLeft,
child: SizedBox( child: SizedBox(
width: 400, width: 400,
child: Actions( child: Actions(
actions: <Type, Action<Intent>>{ actions: testActions,
MoveSelectionLeftTextIntent: _MyMoveSelectionRightTextAction(
onInvoke: () {
myIntentWasCalled = true;
},
),
},
child: EditableText( child: EditableText(
maxLines: 10, maxLines: 10,
controller: controller, controller: controller,
...@@ -7594,80 +7562,24 @@ void main() { ...@@ -7594,80 +7562,24 @@ void main() {
await tester.pump(); await tester.pump();
expect(controller.selection.isCollapsed, isTrue); expect(controller.selection.isCollapsed, isTrue);
expect(controller.selection.baseOffset, 1); expect(controller.selection.baseOffset, 1);
expect(myIntentWasCalled, isFalse);
// And the left arrow also moves to the right due to the Actions override. // And the testSingleActivators also moves to the right due to the Shortcuts override.
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft); for (final SingleActivator singleActivator in testSingleActivators) {
await tester.pump(); myIntentWasCalled = false;
expect(controller.selection.isCollapsed, isTrue); controller.selection = const TextSelection.collapsed(offset: 0);
expect(controller.selection.baseOffset, 2); await tester.pump();
expect(myIntentWasCalled, isTrue);
// On web, using keyboard for selection is handled by the browser.
}, skip: kIsWeb);
testWidgets('can override select all via Actions', (WidgetTester tester) async {
final TextEditingController controller = TextEditingController(text: testText);
controller.selection = const TextSelection(
baseOffset: 0,
extentOffset: 0,
affinity: TextAffinity.upstream,
);
late bool myIntentWasCalled;
await tester.pumpWidget(MaterialApp(
home: Align(
alignment: Alignment.topLeft,
child: SizedBox(
width: 400,
child: Actions(
actions: <Type, Action<Intent>>{
SelectAllTextIntent: _MyMoveSelectionRightTextAction(
onInvoke: () {
myIntentWasCalled = true;
},
),
},
child: EditableText(
maxLines: 10,
controller: controller,
showSelectionHandles: true,
autofocus: true,
focusNode: focusNode,
style: Typography.material2018(platform: TargetPlatform.android).black.subtitle1!,
cursorColor: Colors.blue,
backgroundCursorColor: Colors.grey,
selectionControls: materialTextSelectionControls,
keyboardType: TextInputType.text,
textAlign: TextAlign.right,
),
),
),
),
));
await tester.pump(); // Wait for autofocus to take effect.
expect(controller.selection.isCollapsed, isTrue);
expect(controller.selection.baseOffset, 0);
// meta + A moves the cursor right instead of doing select all. await sendKeys(
await sendKeys( tester,
tester, <LogicalKeyboardKey>[singleActivator.trigger],
<LogicalKeyboardKey>[LogicalKeyboardKey.keyA], shortcutModifier: singleActivator.control,
shortcutModifier: true, targetPlatform: defaultTargetPlatform,
targetPlatform: defaultTargetPlatform, );
); expect(controller.selection.isCollapsed, isTrue);
await tester.pump(); expect(controller.selection.baseOffset, 1);
expect(controller.selection.isCollapsed, isTrue); expect(myIntentWasCalled, isTrue);
expect(controller.selection.baseOffset, 1); }
expect(myIntentWasCalled, isTrue);
await sendKeys(
tester,
<LogicalKeyboardKey>[LogicalKeyboardKey.keyA],
shortcutModifier: true,
targetPlatform: defaultTargetPlatform,
);
await tester.pump();
expect(controller.selection.isCollapsed, isTrue);
expect(controller.selection.baseOffset, 2);
// On web, using keyboard for selection is handled by the browser. // On web, using keyboard for selection is handled by the browser.
}, skip: kIsWeb); }, skip: kIsWeb);
......
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