Text Editing Movement Keys via Shortcuts (#75032)
Text editing shortcuts involving the arrow keys are no longer handled by RenderEditable's RawKeyboardListener, they use the new Shortcuts setup. First PR in a plan to port all text editing keyboard handling to shortcuts.
This commit is contained in:
parent
57dc5f294b
commit
541bff4058
@ -4,7 +4,7 @@
|
|||||||
|
|
||||||
import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle;
|
import 'dart:ui' as ui show BoxHeightStyle, BoxWidthStyle;
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart' show defaultTargetPlatform, kIsWeb;
|
import 'package:flutter/foundation.dart' show defaultTargetPlatform;
|
||||||
import 'package:flutter/gestures.dart';
|
import 'package:flutter/gestures.dart';
|
||||||
import 'package:flutter/rendering.dart';
|
import 'package:flutter/rendering.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
@ -1202,7 +1202,7 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with Restoratio
|
|||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
final Widget child = Semantics(
|
return Semantics(
|
||||||
enabled: enabled,
|
enabled: enabled,
|
||||||
onTap: !enabled || widget.readOnly ? null : () {
|
onTap: !enabled || widget.readOnly ? null : () {
|
||||||
if (!controller.selection.isValid) {
|
if (!controller.selection.isValid) {
|
||||||
@ -1226,13 +1226,5 @@ class _CupertinoTextFieldState extends State<CupertinoTextField> with Restoratio
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (kIsWeb) {
|
|
||||||
return Shortcuts(
|
|
||||||
shortcuts: scrollShortcutOverrides,
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return child;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -884,7 +884,7 @@ class _MaterialAppState extends State<MaterialApp> {
|
|||||||
child: HeroControllerScope(
|
child: HeroControllerScope(
|
||||||
controller: _heroController,
|
controller: _heroController,
|
||||||
child: result,
|
child: result,
|
||||||
)
|
),
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1290,7 +1290,7 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
|
|||||||
semanticsMaxValueLength = null;
|
semanticsMaxValueLength = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
child = MouseRegion(
|
return MouseRegion(
|
||||||
cursor: effectiveMouseCursor,
|
cursor: effectiveMouseCursor,
|
||||||
onEnter: (PointerEnterEvent event) => _handleHover(true),
|
onEnter: (PointerEnterEvent event) => _handleHover(true),
|
||||||
onExit: (PointerExitEvent event) => _handleHover(false),
|
onExit: (PointerExitEvent event) => _handleHover(false),
|
||||||
@ -1317,13 +1317,5 @@ class _TextFieldState extends State<TextField> with RestorationMixin implements
|
|||||||
),
|
),
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
if (kIsWeb) {
|
|
||||||
return Shortcuts(
|
|
||||||
shortcuts: scrollShortcutOverrides,
|
|
||||||
child: child,
|
|
||||||
);
|
|
||||||
}
|
|
||||||
return child;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -2,7 +2,6 @@
|
|||||||
// Use of this source code is governed by a BSD-style license that can be
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
// found in the LICENSE file.
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
|
||||||
import 'dart:ui' show hashValues, TextAffinity, TextPosition, TextRange;
|
import 'dart:ui' show hashValues, TextAffinity, TextPosition, TextRange;
|
||||||
|
|
||||||
import 'package:flutter/foundation.dart';
|
import 'package:flutter/foundation.dart';
|
||||||
|
@ -12,6 +12,8 @@ import 'actions.dart';
|
|||||||
import 'banner.dart';
|
import 'banner.dart';
|
||||||
import 'basic.dart';
|
import 'basic.dart';
|
||||||
import 'binding.dart';
|
import 'binding.dart';
|
||||||
|
import 'default_text_editing_actions.dart';
|
||||||
|
import 'default_text_editing_shortcuts.dart';
|
||||||
import 'focus_traversal.dart';
|
import 'focus_traversal.dart';
|
||||||
import 'framework.dart';
|
import 'framework.dart';
|
||||||
import 'localizations.dart';
|
import 'localizations.dart';
|
||||||
@ -861,6 +863,9 @@ class WidgetsApp extends StatefulWidget {
|
|||||||
/// The default map of keyboard shortcuts to intents for the application.
|
/// The default map of keyboard shortcuts to intents for the application.
|
||||||
///
|
///
|
||||||
/// By default, this is set to [WidgetsApp.defaultShortcuts].
|
/// By default, this is set to [WidgetsApp.defaultShortcuts].
|
||||||
|
///
|
||||||
|
/// Passing this will not replace [DefaultTextEditingShortcuts]. These can be
|
||||||
|
/// overridden by using a [Shortcuts] widget lower in the widget tree.
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
///
|
///
|
||||||
/// {@tool snippet}
|
/// {@tool snippet}
|
||||||
@ -910,6 +915,9 @@ class WidgetsApp extends StatefulWidget {
|
|||||||
/// the [actions] for this app. You may also add to the bindings, or override
|
/// the [actions] for this app. You may also add to the bindings, or override
|
||||||
/// specific bindings for a widget subtree, by adding your own [Actions]
|
/// specific bindings for a widget subtree, by adding your own [Actions]
|
||||||
/// widget.
|
/// widget.
|
||||||
|
///
|
||||||
|
/// Passing this will not replace [DefaultTextEditingActions]. These can be
|
||||||
|
/// overridden by placing an [Actions] widget lower in the widget tree.
|
||||||
/// {@endtemplate}
|
/// {@endtemplate}
|
||||||
///
|
///
|
||||||
/// {@tool snippet}
|
/// {@tool snippet}
|
||||||
@ -1621,20 +1629,27 @@ class _WidgetsAppState extends State<WidgetsApp> with WidgetsBindingObserver {
|
|||||||
: _locale!;
|
: _locale!;
|
||||||
|
|
||||||
assert(_debugCheckLocalizations(appLocale));
|
assert(_debugCheckLocalizations(appLocale));
|
||||||
|
|
||||||
return RootRestorationScope(
|
return RootRestorationScope(
|
||||||
restorationId: widget.restorationScopeId,
|
restorationId: widget.restorationScopeId,
|
||||||
child: Shortcuts(
|
child: Shortcuts(
|
||||||
shortcuts: widget.shortcuts ?? WidgetsApp.defaultShortcuts,
|
|
||||||
debugLabel: '<Default WidgetsApp Shortcuts>',
|
debugLabel: '<Default WidgetsApp Shortcuts>',
|
||||||
child: Actions(
|
shortcuts: widget.shortcuts ?? WidgetsApp.defaultShortcuts,
|
||||||
actions: widget.actions ?? WidgetsApp.defaultActions,
|
// DefaultTextEditingShortcuts is nested inside Shortcuts so that it can
|
||||||
child: FocusTraversalGroup(
|
// fall through to the defaultShortcuts.
|
||||||
policy: ReadingOrderTraversalPolicy(),
|
child: DefaultTextEditingShortcuts(
|
||||||
child: _MediaQueryFromWindow(
|
child: Actions(
|
||||||
child: Localizations(
|
actions: widget.actions ?? WidgetsApp.defaultActions,
|
||||||
locale: appLocale,
|
child: DefaultTextEditingActions(
|
||||||
delegates: _localizationsDelegates.toList(),
|
child: FocusTraversalGroup(
|
||||||
child: title,
|
policy: ReadingOrderTraversalPolicy(),
|
||||||
|
child: _MediaQueryFromWindow(
|
||||||
|
child: Localizations(
|
||||||
|
locale: appLocale,
|
||||||
|
delegates: _localizationsDelegates.toList(),
|
||||||
|
child: title,
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -0,0 +1,231 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
import 'actions.dart';
|
||||||
|
import 'editable_text.dart';
|
||||||
|
import 'framework.dart';
|
||||||
|
import 'text_editing_action.dart';
|
||||||
|
import 'text_editing_intents.dart';
|
||||||
|
|
||||||
|
/// An [Actions] widget that handles the default text editing behavior for
|
||||||
|
/// Flutter on the current platform.
|
||||||
|
///
|
||||||
|
/// This default behavior can be overridden by placing an [Actions] widget lower
|
||||||
|
/// in the widget tree than this. See [DefaultTextEditingShortcuts] for an example of
|
||||||
|
/// remapping keyboard keys to an existing text editing [Intent].
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [DefaultTextEditingShortcuts], which maps keyboard keys to many of the
|
||||||
|
/// [Intent]s that are handled here.
|
||||||
|
/// * [WidgetsApp], which creates a DefaultTextEditingShortcuts.
|
||||||
|
class DefaultTextEditingActions extends Actions{
|
||||||
|
/// Creates an instance of DefaultTextEditingActions.
|
||||||
|
DefaultTextEditingActions({
|
||||||
|
Key? key,
|
||||||
|
required Widget child,
|
||||||
|
}) : super(
|
||||||
|
key: key,
|
||||||
|
actions: _shortcutsActions,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
|
||||||
|
// These Intents are triggered by DefaultTextEditingShortcuts. They are included
|
||||||
|
// regardless of the platform; it's up to DefaultTextEditingShortcuts to decide which
|
||||||
|
// are called on which platform.
|
||||||
|
static final Map<Type, Action<Intent>> _shortcutsActions = <Type, Action<Intent>>{
|
||||||
|
DoNothingAndStopPropagationTextIntent: _DoNothingAndStopPropagationTextAction(),
|
||||||
|
ExtendSelectionDownTextIntent: _ExtendSelectionDownTextAction(),
|
||||||
|
ExtendSelectionLeftByLineTextIntent: _ExtendSelectionLeftByLineTextAction(),
|
||||||
|
ExtendSelectionLeftByWordTextIntent: _ExtendSelectionLeftByWordTextAction(),
|
||||||
|
ExtendSelectionLeftTextIntent: _ExtendSelectionLeftTextAction(),
|
||||||
|
ExtendSelectionRightByWordTextIntent: _ExtendSelectionRightByWordTextAction(),
|
||||||
|
ExtendSelectionRightByLineTextIntent: _ExtendSelectionRightByLineTextAction(),
|
||||||
|
ExtendSelectionRightTextIntent: _ExtendSelectionRightTextAction(),
|
||||||
|
ExtendSelectionUpTextIntent: _ExtendSelectionUpTextAction(),
|
||||||
|
ExpandSelectionLeftByLineTextIntent: _ExpandSelectionLeftByLineTextAction(),
|
||||||
|
ExpandSelectionRightByLineTextIntent: _ExpandSelectionRightByLineTextAction(),
|
||||||
|
ExpandSelectionToEndTextIntent: _ExpandSelectionToEndTextAction(),
|
||||||
|
ExpandSelectionToStartTextIntent: _ExpandSelectionToStartTextAction(),
|
||||||
|
MoveSelectionDownTextIntent: _MoveSelectionDownTextAction(),
|
||||||
|
MoveSelectionLeftByLineTextIntent: _MoveSelectionLeftByLineTextAction(),
|
||||||
|
MoveSelectionLeftByWordTextIntent: _MoveSelectionLeftByWordTextAction(),
|
||||||
|
MoveSelectionLeftTextIntent: _MoveSelectionLeftTextAction(),
|
||||||
|
MoveSelectionRightByLineTextIntent: _MoveSelectionRightByLineTextAction(),
|
||||||
|
MoveSelectionRightByWordTextIntent: _MoveSelectionRightByWordTextAction(),
|
||||||
|
MoveSelectionRightTextIntent: _MoveSelectionRightTextAction(),
|
||||||
|
MoveSelectionToEndTextIntent: _MoveSelectionToEndTextAction(),
|
||||||
|
MoveSelectionToStartTextIntent: _MoveSelectionToStartTextAction(),
|
||||||
|
MoveSelectionUpTextIntent: _MoveSelectionUpTextAction(),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// This allows the web engine to handle text editing events natively while using
|
||||||
|
// the same TextEditingAction logic to only handle events from a
|
||||||
|
// TextEditingTarget.
|
||||||
|
class _DoNothingAndStopPropagationTextAction extends TextEditingAction<DoNothingAndStopPropagationTextIntent> {
|
||||||
|
_DoNothingAndStopPropagationTextAction();
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool consumesKey(Intent intent) => true;
|
||||||
|
|
||||||
|
@override
|
||||||
|
void invoke(DoNothingAndStopPropagationTextIntent intent, [BuildContext? context]) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ExpandSelectionLeftByLineTextAction extends TextEditingAction<ExpandSelectionLeftByLineTextIntent> {
|
||||||
|
@override
|
||||||
|
Object? invoke(ExpandSelectionLeftByLineTextIntent intent, [BuildContext? context]) {
|
||||||
|
textEditingActionTarget!.renderEditable.expandSelectionLeftByLine(SelectionChangedCause.keyboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ExpandSelectionRightByLineTextAction extends TextEditingAction<ExpandSelectionRightByLineTextIntent> {
|
||||||
|
@override
|
||||||
|
Object? invoke(ExpandSelectionRightByLineTextIntent intent, [BuildContext? context]) {
|
||||||
|
textEditingActionTarget!.renderEditable.expandSelectionRightByLine(SelectionChangedCause.keyboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ExpandSelectionToEndTextAction extends TextEditingAction<ExpandSelectionToEndTextIntent> {
|
||||||
|
@override
|
||||||
|
Object? invoke(ExpandSelectionToEndTextIntent intent, [BuildContext? context]) {
|
||||||
|
textEditingActionTarget!.renderEditable.expandSelectionToEnd(SelectionChangedCause.keyboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ExpandSelectionToStartTextAction extends TextEditingAction<ExpandSelectionToStartTextIntent> {
|
||||||
|
@override
|
||||||
|
Object? invoke(ExpandSelectionToStartTextIntent intent, [BuildContext? context]) {
|
||||||
|
textEditingActionTarget!.renderEditable.expandSelectionToStart(SelectionChangedCause.keyboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ExtendSelectionDownTextAction extends TextEditingAction<ExtendSelectionDownTextIntent> {
|
||||||
|
@override
|
||||||
|
Object? invoke(ExtendSelectionDownTextIntent intent, [BuildContext? context]) {
|
||||||
|
textEditingActionTarget!.renderEditable.extendSelectionDown(SelectionChangedCause.keyboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ExtendSelectionLeftByLineTextAction extends TextEditingAction<ExtendSelectionLeftByLineTextIntent> {
|
||||||
|
@override
|
||||||
|
Object? invoke(ExtendSelectionLeftByLineTextIntent intent, [BuildContext? context]) {
|
||||||
|
textEditingActionTarget!.renderEditable.extendSelectionLeftByLine(SelectionChangedCause.keyboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ExtendSelectionLeftByWordTextAction extends TextEditingAction<ExtendSelectionLeftByWordTextIntent> {
|
||||||
|
@override
|
||||||
|
Object? invoke(ExtendSelectionLeftByWordTextIntent intent, [BuildContext? context]) {
|
||||||
|
textEditingActionTarget!.renderEditable.extendSelectionLeftByWord(SelectionChangedCause.keyboard, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ExtendSelectionLeftTextAction extends TextEditingAction<ExtendSelectionLeftTextIntent> {
|
||||||
|
@override
|
||||||
|
Object? invoke(ExtendSelectionLeftTextIntent intent, [BuildContext? context]) {
|
||||||
|
textEditingActionTarget!.renderEditable.extendSelectionLeft(SelectionChangedCause.keyboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ExtendSelectionRightByLineTextAction extends TextEditingAction<ExtendSelectionRightByLineTextIntent> {
|
||||||
|
@override
|
||||||
|
Object? invoke(ExtendSelectionRightByLineTextIntent intent, [BuildContext? context]) {
|
||||||
|
textEditingActionTarget!.renderEditable.extendSelectionRightByLine(SelectionChangedCause.keyboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ExtendSelectionRightByWordTextAction extends TextEditingAction<ExtendSelectionRightByWordTextIntent> {
|
||||||
|
@override
|
||||||
|
Object? invoke(ExtendSelectionRightByWordTextIntent intent, [BuildContext? context]) {
|
||||||
|
textEditingActionTarget!.renderEditable.extendSelectionRightByWord(SelectionChangedCause.keyboard, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ExtendSelectionRightTextAction extends TextEditingAction<ExtendSelectionRightTextIntent> {
|
||||||
|
@override
|
||||||
|
Object? invoke(ExtendSelectionRightTextIntent intent, [BuildContext? context]) {
|
||||||
|
textEditingActionTarget!.renderEditable.extendSelectionRight(SelectionChangedCause.keyboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _ExtendSelectionUpTextAction extends TextEditingAction<ExtendSelectionUpTextIntent> {
|
||||||
|
@override
|
||||||
|
Object? invoke(ExtendSelectionUpTextIntent intent, [BuildContext? context]) {
|
||||||
|
textEditingActionTarget!.renderEditable.extendSelectionUp(SelectionChangedCause.keyboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MoveSelectionDownTextAction extends TextEditingAction<MoveSelectionDownTextIntent> {
|
||||||
|
@override
|
||||||
|
Object? invoke(MoveSelectionDownTextIntent intent, [BuildContext? context]) {
|
||||||
|
textEditingActionTarget!.renderEditable.moveSelectionDown(SelectionChangedCause.keyboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MoveSelectionLeftTextAction extends TextEditingAction<MoveSelectionLeftTextIntent> {
|
||||||
|
@override
|
||||||
|
Object? invoke(MoveSelectionLeftTextIntent intent, [BuildContext? context]) {
|
||||||
|
textEditingActionTarget!.renderEditable.moveSelectionLeft(SelectionChangedCause.keyboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MoveSelectionRightTextAction extends TextEditingAction<MoveSelectionRightTextIntent> {
|
||||||
|
@override
|
||||||
|
Object? invoke(MoveSelectionRightTextIntent intent, [BuildContext? context]) {
|
||||||
|
textEditingActionTarget!.renderEditable.moveSelectionRight(SelectionChangedCause.keyboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MoveSelectionUpTextAction extends TextEditingAction<MoveSelectionUpTextIntent> {
|
||||||
|
@override
|
||||||
|
Object? invoke(MoveSelectionUpTextIntent intent, [BuildContext? context]) {
|
||||||
|
textEditingActionTarget!.renderEditable.moveSelectionUp(SelectionChangedCause.keyboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MoveSelectionLeftByLineTextAction extends TextEditingAction<MoveSelectionLeftByLineTextIntent> {
|
||||||
|
@override
|
||||||
|
Object? invoke(MoveSelectionLeftByLineTextIntent intent, [BuildContext? context]) {
|
||||||
|
textEditingActionTarget!.renderEditable.moveSelectionLeftByLine(SelectionChangedCause.keyboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MoveSelectionLeftByWordTextAction extends TextEditingAction<MoveSelectionLeftByWordTextIntent> {
|
||||||
|
@override
|
||||||
|
Object? invoke(MoveSelectionLeftByWordTextIntent intent, [BuildContext? context]) {
|
||||||
|
textEditingActionTarget!.renderEditable.moveSelectionLeftByWord(SelectionChangedCause.keyboard, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MoveSelectionRightByLineTextAction extends TextEditingAction<MoveSelectionRightByLineTextIntent> {
|
||||||
|
@override
|
||||||
|
Object? invoke(MoveSelectionRightByLineTextIntent intent, [BuildContext? context]) {
|
||||||
|
textEditingActionTarget!.renderEditable.moveSelectionRightByLine(SelectionChangedCause.keyboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MoveSelectionRightByWordTextAction extends TextEditingAction<MoveSelectionRightByWordTextIntent> {
|
||||||
|
@override
|
||||||
|
Object? invoke(MoveSelectionRightByWordTextIntent intent, [BuildContext? context]) {
|
||||||
|
textEditingActionTarget!.renderEditable.moveSelectionRightByWord(SelectionChangedCause.keyboard, false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MoveSelectionToEndTextAction extends TextEditingAction<MoveSelectionToEndTextIntent> {
|
||||||
|
@override
|
||||||
|
Object? invoke(MoveSelectionToEndTextIntent intent, [BuildContext? context]) {
|
||||||
|
textEditingActionTarget!.renderEditable.moveSelectionToEnd(SelectionChangedCause.keyboard);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class _MoveSelectionToStartTextAction extends TextEditingAction<MoveSelectionToStartTextIntent> {
|
||||||
|
@override
|
||||||
|
Object? invoke(MoveSelectionToStartTextIntent intent, [BuildContext? context]) {
|
||||||
|
textEditingActionTarget!.renderEditable.moveSelectionToStart(SelectionChangedCause.keyboard);
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,443 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/services.dart';
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
|
||||||
|
import 'actions.dart';
|
||||||
|
import 'framework.dart';
|
||||||
|
import 'shortcuts.dart';
|
||||||
|
import 'text_editing_intents.dart';
|
||||||
|
|
||||||
|
/// A [Shortcuts] widget with the shortcuts used for the default text editing
|
||||||
|
/// behavior.
|
||||||
|
///
|
||||||
|
/// This default behavior can be overridden by placing a [Shortcuts] widget
|
||||||
|
/// lower in the widget tree than this. See [DefaultTextEditingActions] for an example
|
||||||
|
/// of remapping a text editing [Intent] to a custom [Action].
|
||||||
|
///
|
||||||
|
/// {@tool snippet}
|
||||||
|
///
|
||||||
|
/// This example shows how to use an additional [Shortcuts] widget to override
|
||||||
|
/// some default text editing keyboard shortcuts to have new behavior. Instead
|
||||||
|
/// of moving the cursor, alt + up/down will change the focused widget.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// @override
|
||||||
|
/// Widget build(BuildContext context) {
|
||||||
|
/// // If using WidgetsApp or its descendents MaterialApp or CupertinoApp,
|
||||||
|
/// // then DefaultTextEditingShortcuts is already being inserted into the
|
||||||
|
/// // widget tree.
|
||||||
|
/// return DefaultTextEditingShortcuts(
|
||||||
|
/// child: Center(
|
||||||
|
/// child: Shortcuts(
|
||||||
|
/// shortcuts: <LogicalKeySet, Intent>{
|
||||||
|
/// LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowDown): NextFocusIntent(),
|
||||||
|
/// LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowUp): PreviousFocusIntent(),
|
||||||
|
/// },
|
||||||
|
/// child: Column(
|
||||||
|
/// children: <Widget>[
|
||||||
|
/// TextField(
|
||||||
|
/// decoration: InputDecoration(
|
||||||
|
/// hintText: 'alt + down moves to the next field.',
|
||||||
|
/// ),
|
||||||
|
/// ),
|
||||||
|
/// TextField(
|
||||||
|
/// decoration: InputDecoration(
|
||||||
|
/// hintText: 'And alt + up moves to the previous.',
|
||||||
|
/// ),
|
||||||
|
/// ),
|
||||||
|
/// ],
|
||||||
|
/// ),
|
||||||
|
/// ),
|
||||||
|
/// ),
|
||||||
|
/// );
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// {@end-tool}
|
||||||
|
///
|
||||||
|
/// {@tool snippet}
|
||||||
|
///
|
||||||
|
/// This example shows how to use an additional [Shortcuts] widget to override
|
||||||
|
/// default text editing shortcuts to have completely custom behavior defined by
|
||||||
|
/// a custom Intent and Action. Here, the up/down arrow keys increment/decrement
|
||||||
|
/// a counter instead of moving the cursor.
|
||||||
|
///
|
||||||
|
/// ```dart
|
||||||
|
/// class IncrementCounterIntent extends Intent {}
|
||||||
|
/// class DecrementCounterIntent extends Intent {}
|
||||||
|
///
|
||||||
|
/// class MyWidget extends StatefulWidget {
|
||||||
|
/// MyWidget({ Key? key }) : super(key: key);
|
||||||
|
///
|
||||||
|
/// @override
|
||||||
|
/// MyWidgetState createState() => MyWidgetState();
|
||||||
|
/// }
|
||||||
|
///
|
||||||
|
/// class MyWidgetState extends State<MyWidget> {
|
||||||
|
///
|
||||||
|
/// int _counter = 0;
|
||||||
|
///
|
||||||
|
/// @override
|
||||||
|
/// Widget build(BuildContext context) {
|
||||||
|
/// // If using WidgetsApp or its descendents MaterialApp or CupertinoApp,
|
||||||
|
/// // then DefaultTextEditingShortcuts is already being inserted into the
|
||||||
|
/// // widget tree.
|
||||||
|
/// return DefaultTextEditingShortcuts(
|
||||||
|
/// child: Center(
|
||||||
|
/// child: Column(
|
||||||
|
/// mainAxisAlignment: MainAxisAlignment.center,
|
||||||
|
/// children: <Widget>[
|
||||||
|
/// Text(
|
||||||
|
/// 'You have pushed the button this many times:',
|
||||||
|
/// ),
|
||||||
|
/// Text(
|
||||||
|
/// '$_counter',
|
||||||
|
/// style: Theme.of(context).textTheme.headline4,
|
||||||
|
/// ),
|
||||||
|
/// Shortcuts(
|
||||||
|
/// shortcuts: <LogicalKeySet, Intent>{
|
||||||
|
/// LogicalKeySet(LogicalKeyboardKey.arrowUp): IncrementCounterIntent(),
|
||||||
|
/// LogicalKeySet(LogicalKeyboardKey.arrowDown): DecrementCounterIntent(),
|
||||||
|
/// },
|
||||||
|
/// child: Actions(
|
||||||
|
/// actions: <Type, Action<Intent>>{
|
||||||
|
/// IncrementCounterIntent: CallbackAction<IncrementCounterIntent>(
|
||||||
|
/// onInvoke: (IncrementCounterIntent intent) {
|
||||||
|
/// setState(() {
|
||||||
|
/// _counter++;
|
||||||
|
/// });
|
||||||
|
/// },
|
||||||
|
/// ),
|
||||||
|
/// DecrementCounterIntent: CallbackAction<DecrementCounterIntent>(
|
||||||
|
/// onInvoke: (DecrementCounterIntent intent) {
|
||||||
|
/// setState(() {
|
||||||
|
/// _counter--;
|
||||||
|
/// });
|
||||||
|
/// },
|
||||||
|
/// ),
|
||||||
|
/// },
|
||||||
|
/// child: TextField(
|
||||||
|
/// maxLines: 2,
|
||||||
|
/// decoration: InputDecoration(
|
||||||
|
/// hintText: 'Up/down increment/decrement here.',
|
||||||
|
/// ),
|
||||||
|
/// ),
|
||||||
|
/// ),
|
||||||
|
/// ),
|
||||||
|
/// TextField(
|
||||||
|
/// maxLines: 2,
|
||||||
|
/// decoration: InputDecoration(
|
||||||
|
/// hintText: 'Up/down behave normally here.',
|
||||||
|
/// ),
|
||||||
|
/// ),
|
||||||
|
/// ],
|
||||||
|
/// ),
|
||||||
|
/// ),
|
||||||
|
/// );
|
||||||
|
/// }
|
||||||
|
/// }
|
||||||
|
/// ```
|
||||||
|
/// {@end-tool}
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [DefaultTextEditingActions], which contains all of the [Action]s that
|
||||||
|
/// respond to the [Intent]s in these shortcuts with the default text editing
|
||||||
|
/// behavior.
|
||||||
|
/// * [WidgetsApp], which creates a DefaultTextEditingShortcuts.
|
||||||
|
class DefaultTextEditingShortcuts extends Shortcuts {
|
||||||
|
/// Creates a [Shortcuts] widget that provides the default text editing
|
||||||
|
/// shortcuts on the current platform.
|
||||||
|
DefaultTextEditingShortcuts({
|
||||||
|
Key? key,
|
||||||
|
required Widget child,
|
||||||
|
}) : super(
|
||||||
|
key: key,
|
||||||
|
debugLabel: '<Default Text Editing Shortcuts>',
|
||||||
|
shortcuts: _shortcuts,
|
||||||
|
child: child,
|
||||||
|
);
|
||||||
|
|
||||||
|
static final Map<LogicalKeySet, Intent> _androidShortcuts = <LogicalKeySet, Intent>{
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowDown): const MoveSelectionToEndTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowLeft): const MoveSelectionLeftByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowRight): const MoveSelectionRightByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowUp): const MoveSelectionToStartTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowDown): const ExpandSelectionToEndTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowLeft): const ExpandSelectionLeftByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowRight): const ExpandSelectionRightByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowUp): const ExpandSelectionToStartTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowDown): const MoveSelectionDownTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowLeft): const MoveSelectionLeftTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowRight): const MoveSelectionRightTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowUp): const MoveSelectionUpTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.arrowLeft): const MoveSelectionLeftByWordTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.arrowRight): const MoveSelectionRightByWordTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowLeft): const ExtendSelectionLeftByWordTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowRight): const ExtendSelectionRightByWordTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowDown): const ExtendSelectionDownTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowLeft): const ExtendSelectionLeftTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowRight): const ExtendSelectionRightTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowUp): const ExtendSelectionUpTextIntent(),
|
||||||
|
// The following key combinations have no effect on text edition on this
|
||||||
|
// platform:
|
||||||
|
// * End
|
||||||
|
// * Home
|
||||||
|
// * Meta + arrow down
|
||||||
|
// * Meta + arrow left
|
||||||
|
// * Meta + arrow right
|
||||||
|
// * Meta + arrow up
|
||||||
|
// * Meta + shift + arrow down
|
||||||
|
// * Meta + shift + arrow left
|
||||||
|
// * Meta + shift + arrow right
|
||||||
|
// * Meta + shift + arrow up
|
||||||
|
// * Shift + end
|
||||||
|
// * Shift + home
|
||||||
|
};
|
||||||
|
|
||||||
|
static final Map<LogicalKeySet, Intent> _fuchsiaShortcuts = <LogicalKeySet, Intent>{
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowDown): const MoveSelectionToEndTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowLeft): const MoveSelectionLeftByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowRight): const MoveSelectionRightByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowUp): const MoveSelectionToStartTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowDown): const ExpandSelectionToEndTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowLeft): const ExpandSelectionLeftByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowRight): const ExpandSelectionRightByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowUp): const ExpandSelectionToStartTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowDown): const MoveSelectionDownTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowLeft): const MoveSelectionLeftTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowRight): const MoveSelectionRightTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowUp): const MoveSelectionUpTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.arrowLeft): const MoveSelectionLeftByWordTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.arrowRight): const MoveSelectionRightByWordTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowLeft): const ExtendSelectionLeftByWordTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowRight): const ExtendSelectionRightByWordTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowDown): const ExtendSelectionDownTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowLeft): const ExtendSelectionLeftTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowRight): const ExtendSelectionRightTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowUp): const ExtendSelectionUpTextIntent(),
|
||||||
|
// The following key combinations have no effect on text edition on this
|
||||||
|
// platform:
|
||||||
|
// * Meta + arrow down
|
||||||
|
// * End
|
||||||
|
// * Home
|
||||||
|
// * Meta + arrow left
|
||||||
|
// * Meta + arrow right
|
||||||
|
// * Meta + arrow up
|
||||||
|
// * Meta + shift + arrow down
|
||||||
|
// * Meta + shift + arrow left
|
||||||
|
// * Meta + shift + arrow right
|
||||||
|
// * Meta + shift + arrow up
|
||||||
|
// * Shift + end
|
||||||
|
// * Shift + home
|
||||||
|
};
|
||||||
|
|
||||||
|
static final Map<LogicalKeySet, Intent> _iOSShortcuts = <LogicalKeySet, Intent>{
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowDown): const MoveSelectionToEndTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowLeft): const MoveSelectionLeftByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowRight): const MoveSelectionRightByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowUp): const MoveSelectionToStartTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowDown): const ExpandSelectionToEndTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowLeft): const ExpandSelectionLeftByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowRight): const ExpandSelectionRightByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowUp): const ExpandSelectionToStartTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowDown): const MoveSelectionDownTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowLeft): const MoveSelectionLeftTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowRight): const MoveSelectionRightTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowUp): const MoveSelectionUpTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.arrowRight): const MoveSelectionRightByWordTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.arrowLeft): const MoveSelectionLeftByWordTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowLeft): const ExtendSelectionLeftByWordTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowRight): const ExtendSelectionRightByWordTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowDown): const ExtendSelectionDownTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowLeft): const ExtendSelectionLeftTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowRight): const ExtendSelectionRightTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowUp): const ExtendSelectionUpTextIntent(),
|
||||||
|
// The following key combinations have no effect on text edition on this
|
||||||
|
// platform:
|
||||||
|
// * Meta + arrow down
|
||||||
|
// * End
|
||||||
|
// * Home
|
||||||
|
// * Meta + arrow left
|
||||||
|
// * Meta + arrow right
|
||||||
|
// * Meta + arrow up
|
||||||
|
// * Meta + shift + arrow down
|
||||||
|
// * Meta + shift + arrow left
|
||||||
|
// * Meta + shift + arrow right
|
||||||
|
// * Meta + shift + arrow up
|
||||||
|
// * Shift + end
|
||||||
|
// * Shift + home
|
||||||
|
};
|
||||||
|
|
||||||
|
static final Map<LogicalKeySet, Intent> _linuxShortcuts = <LogicalKeySet, Intent>{
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowDown): const MoveSelectionToEndTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowLeft): const MoveSelectionLeftByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowRight): const MoveSelectionRightByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowUp): const MoveSelectionToStartTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowDown): const ExpandSelectionToEndTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowLeft): const ExpandSelectionLeftByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowRight): const ExpandSelectionRightByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowUp): const ExpandSelectionToStartTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowDown): const MoveSelectionDownTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowLeft): const MoveSelectionLeftTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowRight): const MoveSelectionRightTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowUp): const MoveSelectionUpTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.arrowLeft): const MoveSelectionLeftByWordTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.arrowRight): const MoveSelectionRightByWordTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowLeft): const ExtendSelectionLeftByWordTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowRight): const ExtendSelectionRightByWordTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowDown): const ExtendSelectionDownTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowLeft): const ExtendSelectionLeftTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowRight): const ExtendSelectionRightTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowUp): const ExtendSelectionUpTextIntent(),
|
||||||
|
// The following key combinations have no effect on text edition on this
|
||||||
|
// platform:
|
||||||
|
// * Meta + arrow down
|
||||||
|
// * End
|
||||||
|
// * Home
|
||||||
|
// * Meta + arrow left
|
||||||
|
// * Meta + arrow right
|
||||||
|
// * Meta + arrow up
|
||||||
|
// * Meta + shift + arrow down
|
||||||
|
// * Meta + shift + arrow left
|
||||||
|
// * Meta + shift + arrow right
|
||||||
|
// * Meta + shift + arrow up
|
||||||
|
// * Shift + end
|
||||||
|
// * Shift + home
|
||||||
|
};
|
||||||
|
|
||||||
|
static final Map<LogicalKeySet, Intent> _macShortcuts = <LogicalKeySet, Intent>{
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowDown): const MoveSelectionRightByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowLeft): const MoveSelectionLeftByWordTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowRight): const MoveSelectionRightByWordTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowUp): const MoveSelectionLeftByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowDown): const ExtendSelectionRightByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowLeft): const ExtendSelectionLeftByWordTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowRight): const ExtendSelectionRightByWordTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowUp): const ExtendSelectionLeftByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowDown): const MoveSelectionDownTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowLeft): const MoveSelectionLeftTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowRight): const MoveSelectionRightTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowUp): const MoveSelectionUpTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.arrowDown): const MoveSelectionToEndTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.arrowLeft): const MoveSelectionLeftByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.arrowRight): const MoveSelectionRightByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.arrowUp): const MoveSelectionToStartTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowDown): const ExpandSelectionToEndTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowLeft): const ExpandSelectionLeftByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowRight): const ExpandSelectionRightByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowUp): const ExpandSelectionToStartTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowDown): const ExtendSelectionDownTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowLeft): const ExtendSelectionLeftTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowRight): const ExtendSelectionRightTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowUp): const ExtendSelectionUpTextIntent(),
|
||||||
|
// The following key combinations have no effect on text edition on this
|
||||||
|
// platform:
|
||||||
|
// * Control + arrow left
|
||||||
|
// * Control + arrow right
|
||||||
|
// * Control + shift + arrow left
|
||||||
|
// * Control + shift + arrow right
|
||||||
|
// * End
|
||||||
|
// * Home
|
||||||
|
// * Shift + end
|
||||||
|
// * Shift + home
|
||||||
|
};
|
||||||
|
|
||||||
|
static final Map<LogicalKeySet, Intent> _windowsShortcuts = <LogicalKeySet, Intent>{
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowDown): const MoveSelectionToEndTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowLeft): const MoveSelectionLeftByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowRight): const MoveSelectionRightByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowUp): const MoveSelectionToStartTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowDown): const ExpandSelectionToEndTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowLeft): const ExpandSelectionLeftByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowRight): const ExpandSelectionRightByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowUp): const ExpandSelectionToStartTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowDown): const MoveSelectionDownTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowLeft): const MoveSelectionLeftTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowRight): const MoveSelectionRightTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowUp): const MoveSelectionUpTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.arrowLeft): const MoveSelectionLeftByWordTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.arrowRight): const MoveSelectionRightByWordTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowLeft): const ExtendSelectionLeftByWordTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowRight): const ExtendSelectionRightByWordTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.end): const MoveSelectionRightByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.home): const MoveSelectionLeftByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowDown): const ExtendSelectionDownTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowLeft): const ExtendSelectionLeftTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowRight): const ExtendSelectionRightTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowUp): const ExtendSelectionUpTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.end): const ExpandSelectionRightByLineTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.home): const ExpandSelectionLeftByLineTextIntent(),
|
||||||
|
// The following key combinations have no effect on text edition on this
|
||||||
|
// platform:
|
||||||
|
// * Meta + arrow down
|
||||||
|
// * Meta + arrow left
|
||||||
|
// * Meta + arrow right
|
||||||
|
// * Meta + arrow up
|
||||||
|
// * Meta + shift + arrow down
|
||||||
|
// * Meta + shift + arrow left
|
||||||
|
// * Meta + shift + arrow right
|
||||||
|
// * Meta + shift + arrow up
|
||||||
|
};
|
||||||
|
|
||||||
|
// Web handles its text selection natively and doesn't use any of these
|
||||||
|
// shortcuts in Flutter.
|
||||||
|
static final Map<LogicalKeySet, Intent> _webShortcuts = <LogicalKeySet, Intent>{
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowDown): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowLeft): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowRight): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.arrowUp): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowDown): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowLeft): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowRight): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.alt, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowUp): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowDown): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowLeft): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowRight): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowUp): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.arrowLeft): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.arrowRight): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowLeft): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.control, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowRight): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.end): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.home): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.arrowDown): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.arrowLeft): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.arrowRight): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.arrowUp): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowDown): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowLeft): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowRight): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.meta, LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowUp): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowDown): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowLeft): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowRight): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.arrowUp): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.end): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.shift, LogicalKeyboardKey.home): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.space): const DoNothingAndStopPropagationTextIntent(),
|
||||||
|
};
|
||||||
|
|
||||||
|
static Map<LogicalKeySet, Intent> get _shortcuts {
|
||||||
|
if (kIsWeb) {
|
||||||
|
return _webShortcuts;
|
||||||
|
}
|
||||||
|
|
||||||
|
switch (defaultTargetPlatform) {
|
||||||
|
case TargetPlatform.android:
|
||||||
|
return _androidShortcuts;
|
||||||
|
case TargetPlatform.fuchsia:
|
||||||
|
return _fuchsiaShortcuts;
|
||||||
|
case TargetPlatform.iOS:
|
||||||
|
return _iOSShortcuts;
|
||||||
|
case TargetPlatform.linux:
|
||||||
|
return _linuxShortcuts;
|
||||||
|
case TargetPlatform.macOS:
|
||||||
|
return _macShortcuts;
|
||||||
|
case TargetPlatform.windows:
|
||||||
|
return _windowsShortcuts;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -12,7 +12,6 @@ import 'package:flutter/rendering.dart';
|
|||||||
import 'package:flutter/scheduler.dart';
|
import 'package:flutter/scheduler.dart';
|
||||||
import 'package:flutter/services.dart';
|
import 'package:flutter/services.dart';
|
||||||
|
|
||||||
import 'actions.dart';
|
|
||||||
import 'autofill.dart';
|
import 'autofill.dart';
|
||||||
import 'automatic_keep_alive.dart';
|
import 'automatic_keep_alive.dart';
|
||||||
import 'basic.dart';
|
import 'basic.dart';
|
||||||
@ -27,8 +26,8 @@ import 'media_query.dart';
|
|||||||
import 'scroll_controller.dart';
|
import 'scroll_controller.dart';
|
||||||
import 'scroll_physics.dart';
|
import 'scroll_physics.dart';
|
||||||
import 'scrollable.dart';
|
import 'scrollable.dart';
|
||||||
import 'shortcuts.dart';
|
|
||||||
import 'text.dart';
|
import 'text.dart';
|
||||||
|
import 'text_editing_action.dart';
|
||||||
import 'text_selection.dart';
|
import 'text_selection.dart';
|
||||||
import 'ticker_provider.dart';
|
import 'ticker_provider.dart';
|
||||||
|
|
||||||
@ -54,17 +53,6 @@ const Duration _kCursorBlinkWaitForStart = Duration(milliseconds: 150);
|
|||||||
// is shown in an obscured text field.
|
// is shown in an obscured text field.
|
||||||
const int _kObscureShowLatestCharCursorTicks = 3;
|
const int _kObscureShowLatestCharCursorTicks = 3;
|
||||||
|
|
||||||
/// A map used to disable scrolling shortcuts in text fields.
|
|
||||||
///
|
|
||||||
/// This is a temporary fix for: https://github.com/flutter/flutter/issues/74191
|
|
||||||
final Map<LogicalKeySet, Intent> scrollShortcutOverrides = <LogicalKeySet, Intent>{
|
|
||||||
LogicalKeySet(LogicalKeyboardKey.space): DoNothingAndStopPropagationIntent(),
|
|
||||||
LogicalKeySet(LogicalKeyboardKey.arrowUp): DoNothingAndStopPropagationIntent(),
|
|
||||||
LogicalKeySet(LogicalKeyboardKey.arrowDown): DoNothingAndStopPropagationIntent(),
|
|
||||||
LogicalKeySet(LogicalKeyboardKey.arrowLeft): DoNothingAndStopPropagationIntent(),
|
|
||||||
LogicalKeySet(LogicalKeyboardKey.arrowRight): DoNothingAndStopPropagationIntent(),
|
|
||||||
};
|
|
||||||
|
|
||||||
/// A controller for an editable text field.
|
/// A controller for an editable text field.
|
||||||
///
|
///
|
||||||
/// Whenever the user modifies a text field with an associated
|
/// Whenever the user modifies a text field with an associated
|
||||||
@ -1491,7 +1479,7 @@ class EditableText extends StatefulWidget {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/// State for a [EditableText].
|
/// State for a [EditableText].
|
||||||
class EditableTextState extends State<EditableText> with AutomaticKeepAliveClientMixin<EditableText>, WidgetsBindingObserver, TickerProviderStateMixin<EditableText>, TextSelectionDelegate implements TextInputClient, AutofillClient {
|
class EditableTextState extends State<EditableText> with AutomaticKeepAliveClientMixin<EditableText>, WidgetsBindingObserver, TickerProviderStateMixin<EditableText>, TextSelectionDelegate implements TextInputClient, AutofillClient, TextEditingActionTarget {
|
||||||
Timer? _cursorTimer;
|
Timer? _cursorTimer;
|
||||||
bool _targetCursorVisibility = false;
|
bool _targetCursorVisibility = false;
|
||||||
final ValueNotifier<bool> _cursorVisibilityNotifier = ValueNotifier<bool>(true);
|
final ValueNotifier<bool> _cursorVisibilityNotifier = ValueNotifier<bool>(true);
|
||||||
@ -2472,6 +2460,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
|||||||
///
|
///
|
||||||
/// This property is typically used to notify the renderer of input gestures
|
/// This property is typically used to notify the renderer of input gestures
|
||||||
/// when [RenderEditable.ignorePointer] is true.
|
/// when [RenderEditable.ignorePointer] is true.
|
||||||
|
@override
|
||||||
RenderEditable get renderEditable => _editableKey.currentContext!.findRenderObject()! as RenderEditable;
|
RenderEditable get renderEditable => _editableKey.currentContext!.findRenderObject()! as RenderEditable;
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
68
packages/flutter/lib/src/widgets/text_editing_action.dart
Normal file
68
packages/flutter/lib/src/widgets/text_editing_action.dart
Normal file
@ -0,0 +1,68 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'package:flutter/foundation.dart';
|
||||||
|
import 'package:flutter/rendering.dart' show RenderEditable;
|
||||||
|
|
||||||
|
import 'actions.dart';
|
||||||
|
import 'editable_text.dart';
|
||||||
|
import 'focus_manager.dart';
|
||||||
|
import 'framework.dart';
|
||||||
|
|
||||||
|
/// The recipient of a [TextEditingAction].
|
||||||
|
///
|
||||||
|
/// TextEditingActions will only be enabled when an implementer of this class is
|
||||||
|
/// focused.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [EditableTextState], which implements this and is the most typical
|
||||||
|
/// target of a TextEditingAction.
|
||||||
|
abstract class TextEditingActionTarget {
|
||||||
|
/// The renderer that handles [TextEditingAction]s.
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [EditableTextState.renderEditable], which overrides this.
|
||||||
|
RenderEditable get renderEditable;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Action] related to editing text.
|
||||||
|
///
|
||||||
|
/// Enables itself only when a [TextEditingActionTarget], e.g. [EditableText],
|
||||||
|
/// is currently focused. The result of this is that when a
|
||||||
|
/// TextEditingActionTarget is not focused, it will fall through to any
|
||||||
|
/// non-TextEditingAction that handles the same shortcut. For example,
|
||||||
|
/// overriding the tab key in [Shortcuts] with a TextEditingAction will only
|
||||||
|
/// invoke your TextEditingAction when a TextEditingActionTarget is focused,
|
||||||
|
/// otherwise the default tab behavior will apply.
|
||||||
|
///
|
||||||
|
/// The currently focused TextEditingActionTarget is available in the [invoke]
|
||||||
|
/// method via [textEditingActionTarget].
|
||||||
|
///
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [CallbackAction], which is a similar Action type but unrelated to text
|
||||||
|
/// editing.
|
||||||
|
abstract class TextEditingAction<T extends Intent> extends ContextAction<T> {
|
||||||
|
/// Returns the currently focused [TextEditingAction], or null if none is
|
||||||
|
/// focused.
|
||||||
|
@protected
|
||||||
|
TextEditingActionTarget? get textEditingActionTarget {
|
||||||
|
// If a TextEditingActionTarget is not focused, then ignore this action.
|
||||||
|
if (primaryFocus?.context == null
|
||||||
|
|| primaryFocus!.context! is! StatefulElement
|
||||||
|
|| ((primaryFocus!.context! as StatefulElement).state is! TextEditingActionTarget)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (primaryFocus!.context! as StatefulElement).state as TextEditingActionTarget;
|
||||||
|
}
|
||||||
|
|
||||||
|
@override
|
||||||
|
bool isEnabled(T intent) {
|
||||||
|
// The Action is disabled if there is no focused TextEditingActionTarget, or
|
||||||
|
// if the platform is web, because web lets the browser handle text editing.
|
||||||
|
return !kIsWeb && textEditingActionTarget != null;
|
||||||
|
}
|
||||||
|
}
|
200
packages/flutter/lib/src/widgets/text_editing_intents.dart
Normal file
200
packages/flutter/lib/src/widgets/text_editing_intents.dart
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
// Copyright 2014 The Flutter Authors. All rights reserved.
|
||||||
|
// Use of this source code is governed by a BSD-style license that can be
|
||||||
|
// found in the LICENSE file.
|
||||||
|
|
||||||
|
import 'actions.dart';
|
||||||
|
|
||||||
|
/// An [Intent] to send the event straight to the engine, but only if a
|
||||||
|
/// TextEditingTarget is focused.
|
||||||
|
///
|
||||||
|
/// {@template flutter.widgets.TextEditingIntents.seeAlso}
|
||||||
|
/// See also:
|
||||||
|
///
|
||||||
|
/// * [DefaultTextEditingActions], which responds to this [Intent].
|
||||||
|
/// * [DefaultTextEditingShortcuts], which triggers this [Intent].
|
||||||
|
/// {@endtemplate}
|
||||||
|
class DoNothingAndStopPropagationTextIntent extends Intent{
|
||||||
|
/// Creates an instance of DoNothingAndStopPropagationTextIntent.
|
||||||
|
const DoNothingAndStopPropagationTextIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Intent] to expand the selection left to the start/end of the current
|
||||||
|
/// line.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
|
||||||
|
class ExpandSelectionLeftByLineTextIntent extends Intent {
|
||||||
|
/// Creates an instance of ExpandSelectionLeftByLineTextIntent.
|
||||||
|
const ExpandSelectionLeftByLineTextIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Intent] to expand the selection right to the start/end of the current
|
||||||
|
/// field.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
|
||||||
|
class ExpandSelectionRightByLineTextIntent extends Intent{
|
||||||
|
/// Creates an instance of ExpandSelectionRightByLineTextIntent.
|
||||||
|
const ExpandSelectionRightByLineTextIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Intent] to expand the selection to the end of the field.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
|
||||||
|
class ExpandSelectionToEndTextIntent extends Intent{
|
||||||
|
/// Creates an instance of ExpandSelectionToEndTextIntent.
|
||||||
|
const ExpandSelectionToEndTextIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Intent] to expand the selection to the start of the field.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
|
||||||
|
class ExpandSelectionToStartTextIntent extends Intent{
|
||||||
|
/// Creates an instance of ExpandSelectionToStartTextIntent.
|
||||||
|
const ExpandSelectionToStartTextIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Intent] to extend the selection down by one line.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
|
||||||
|
class ExtendSelectionDownTextIntent extends Intent{
|
||||||
|
/// Creates an instance of ExtendSelectionDownTextIntent.
|
||||||
|
const ExtendSelectionDownTextIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Intent] to extend the selection left to the start/end of the current
|
||||||
|
/// line.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
|
||||||
|
class ExtendSelectionLeftByLineTextIntent extends Intent{
|
||||||
|
/// Creates an instance of ExtendSelectionLeftByLineTextIntent.
|
||||||
|
const ExtendSelectionLeftByLineTextIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Intent] to extend the selection left past the nearest word.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
|
||||||
|
class ExtendSelectionLeftByWordTextIntent extends Intent{
|
||||||
|
/// Creates an instance of ExtendSelectionLeftByWordTextIntent.
|
||||||
|
const ExtendSelectionLeftByWordTextIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Intent] to extend the selection left by one character.
|
||||||
|
/// platform for the shift + arrow-left key event.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
|
||||||
|
class ExtendSelectionLeftTextIntent extends Intent{
|
||||||
|
/// Creates an instance of ExtendSelectionLeftTextIntent.
|
||||||
|
const ExtendSelectionLeftTextIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Intent] to extend the selection right to the start/end of the current
|
||||||
|
/// line.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
|
||||||
|
class ExtendSelectionRightByLineTextIntent extends Intent{
|
||||||
|
/// Creates an instance of ExtendSelectionRightByLineTextIntent.
|
||||||
|
const ExtendSelectionRightByLineTextIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Intent] to extend the selection right past the nearest word.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
|
||||||
|
class ExtendSelectionRightByWordTextIntent extends Intent{
|
||||||
|
/// Creates an instance of ExtendSelectionRightByWordTextIntent.
|
||||||
|
const ExtendSelectionRightByWordTextIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Intent] to extend the selection right by one character.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
|
||||||
|
class ExtendSelectionRightTextIntent extends Intent{
|
||||||
|
/// Creates an instance of ExtendSelectionRightTextIntent.
|
||||||
|
const ExtendSelectionRightTextIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Intent] to extend the selection up by one line.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
|
||||||
|
class ExtendSelectionUpTextIntent extends Intent{
|
||||||
|
/// Creates an instance of ExtendSelectionUpTextIntent.
|
||||||
|
const ExtendSelectionUpTextIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Intent] to move the selection down by one line.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
|
||||||
|
class MoveSelectionDownTextIntent extends Intent{
|
||||||
|
/// Creates an instance of MoveSelectionDownTextIntent.
|
||||||
|
const MoveSelectionDownTextIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Intent] to move the selection left by one line.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
|
||||||
|
class MoveSelectionLeftByLineTextIntent extends Intent{
|
||||||
|
/// Creates an instance of MoveSelectionLeftByLineTextIntent.
|
||||||
|
const MoveSelectionLeftByLineTextIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Intent] to move the selection left past the nearest word.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
|
||||||
|
class MoveSelectionLeftByWordTextIntent extends Intent{
|
||||||
|
/// Creates an instance of MoveSelectionLeftByWordTextIntent.
|
||||||
|
const MoveSelectionLeftByWordTextIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Intent] to move the selection left by one character.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
|
||||||
|
class MoveSelectionLeftTextIntent extends Intent{
|
||||||
|
/// Creates an instance of MoveSelectionLeftTextIntent.
|
||||||
|
const MoveSelectionLeftTextIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Intent] to move the selection to the start of the field.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
|
||||||
|
class MoveSelectionToStartTextIntent extends Intent{
|
||||||
|
/// Creates an instance of MoveSelectionToStartTextIntent.
|
||||||
|
const MoveSelectionToStartTextIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Intent] to move the selection right by one line.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
|
||||||
|
class MoveSelectionRightByLineTextIntent extends Intent{
|
||||||
|
/// Creates an instance of MoveSelectionRightByLineTextIntent.
|
||||||
|
const MoveSelectionRightByLineTextIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Intent] to move the selection right past the nearest word.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
|
||||||
|
class MoveSelectionRightByWordTextIntent extends Intent{
|
||||||
|
/// Creates an instance of MoveSelectionRightByWordTextIntent.
|
||||||
|
const MoveSelectionRightByWordTextIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Intent] to move the selection right by one character.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
|
||||||
|
class MoveSelectionRightTextIntent extends Intent{
|
||||||
|
/// Creates an instance of MoveSelectionRightTextIntent.
|
||||||
|
const MoveSelectionRightTextIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Intent] to move the selection to the end of the field.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
|
||||||
|
class MoveSelectionToEndTextIntent extends Intent{
|
||||||
|
/// Creates an instance of MoveSelectionToEndTextIntent.
|
||||||
|
const MoveSelectionToEndTextIntent();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// An [Intent] to move the selection up by one character.
|
||||||
|
///
|
||||||
|
/// {@macro flutter.widgets.TextEditingIntents.seeAlso}
|
||||||
|
class MoveSelectionUpTextIntent extends Intent{
|
||||||
|
/// Creates an instance of MoveSelectionUpTextIntent.
|
||||||
|
const MoveSelectionUpTextIntent();
|
||||||
|
}
|
@ -194,6 +194,9 @@ abstract class TextSelectionControls {
|
|||||||
return delegate.selectAllEnabled && delegate.textEditingValue.text.isNotEmpty && delegate.textEditingValue.selection.isCollapsed;
|
return delegate.selectAllEnabled && delegate.textEditingValue.text.isNotEmpty && delegate.textEditingValue.selection.isCollapsed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO(justinmc): This and other methods should be ported to Actions and
|
||||||
|
// removed, along with their keyboard shortcut equivalents.
|
||||||
|
// https://github.com/flutter/flutter/issues/75004
|
||||||
/// Copy the current selection of the text field managed by the given
|
/// Copy the current selection of the text field managed by the given
|
||||||
/// `delegate` to the [Clipboard]. Then, remove the selected text from the
|
/// `delegate` to the [Clipboard]. Then, remove the selected text from the
|
||||||
/// text field and hide the toolbar.
|
/// text field and hide the toolbar.
|
||||||
|
@ -33,6 +33,8 @@ export 'src/widgets/bottom_navigation_bar_item.dart';
|
|||||||
export 'src/widgets/color_filter.dart';
|
export 'src/widgets/color_filter.dart';
|
||||||
export 'src/widgets/container.dart';
|
export 'src/widgets/container.dart';
|
||||||
export 'src/widgets/debug.dart';
|
export 'src/widgets/debug.dart';
|
||||||
|
export 'src/widgets/default_text_editing_actions.dart';
|
||||||
|
export 'src/widgets/default_text_editing_shortcuts.dart';
|
||||||
export 'src/widgets/desktop_text_selection_toolbar_layout_delegate.dart';
|
export 'src/widgets/desktop_text_selection_toolbar_layout_delegate.dart';
|
||||||
export 'src/widgets/dismissible.dart';
|
export 'src/widgets/dismissible.dart';
|
||||||
export 'src/widgets/disposable_build_context.dart';
|
export 'src/widgets/disposable_build_context.dart';
|
||||||
@ -116,6 +118,8 @@ export 'src/widgets/spacer.dart';
|
|||||||
export 'src/widgets/status_transitions.dart';
|
export 'src/widgets/status_transitions.dart';
|
||||||
export 'src/widgets/table.dart';
|
export 'src/widgets/table.dart';
|
||||||
export 'src/widgets/text.dart';
|
export 'src/widgets/text.dart';
|
||||||
|
export 'src/widgets/text_editing_action.dart';
|
||||||
|
export 'src/widgets/text_editing_intents.dart';
|
||||||
export 'src/widgets/text_selection.dart';
|
export 'src/widgets/text_selection.dart';
|
||||||
export 'src/widgets/text_selection_toolbar_layout_delegate.dart';
|
export 'src/widgets/text_selection_toolbar_layout_delegate.dart';
|
||||||
export 'src/widgets/texture.dart';
|
export 'src/widgets/texture.dart';
|
||||||
|
@ -186,11 +186,18 @@ void main() {
|
|||||||
' _FocusTraversalGroupMarker\n'
|
' _FocusTraversalGroupMarker\n'
|
||||||
' FocusTraversalGroup\n'
|
' FocusTraversalGroup\n'
|
||||||
' _ActionsMarker\n'
|
' _ActionsMarker\n'
|
||||||
|
' DefaultTextEditingActions\n'
|
||||||
|
' _ActionsMarker\n'
|
||||||
' Actions\n'
|
' Actions\n'
|
||||||
' _ShortcutsMarker\n'
|
' _ShortcutsMarker\n'
|
||||||
' Semantics\n'
|
' Semantics\n'
|
||||||
' _FocusMarker\n'
|
' _FocusMarker\n'
|
||||||
' Focus\n'
|
' Focus\n'
|
||||||
|
' DefaultTextEditingShortcuts\n'
|
||||||
|
' _ShortcutsMarker\n'
|
||||||
|
' Semantics\n'
|
||||||
|
' _FocusMarker\n'
|
||||||
|
' Focus\n'
|
||||||
' Shortcuts\n'
|
' Shortcuts\n'
|
||||||
' UnmanagedRestorationScope\n'
|
' UnmanagedRestorationScope\n'
|
||||||
' RestorationScope\n'
|
' RestorationScope\n'
|
||||||
|
@ -78,14 +78,18 @@ Widget overlayWithEntry(OverlayEntry entry) {
|
|||||||
WidgetsLocalizationsDelegate(),
|
WidgetsLocalizationsDelegate(),
|
||||||
MaterialLocalizationsDelegate(),
|
MaterialLocalizationsDelegate(),
|
||||||
],
|
],
|
||||||
child: Directionality(
|
child: DefaultTextEditingShortcuts(
|
||||||
textDirection: TextDirection.ltr,
|
child: DefaultTextEditingActions(
|
||||||
child: MediaQuery(
|
child: Directionality(
|
||||||
data: const MediaQueryData(size: Size(800.0, 600.0)),
|
textDirection: TextDirection.ltr,
|
||||||
child: Overlay(
|
child: MediaQuery(
|
||||||
initialEntries: <OverlayEntry>[
|
data: const MediaQueryData(size: Size(800.0, 600.0)),
|
||||||
entry,
|
child: Overlay(
|
||||||
],
|
initialEntries: <OverlayEntry>[
|
||||||
|
entry,
|
||||||
|
],
|
||||||
|
),
|
||||||
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
@ -412,58 +412,6 @@ void main() {
|
|||||||
expect(editable, paintsExactlyCountTimes(#drawRect, 1));
|
expect(editable, paintsExactlyCountTimes(#drawRect, 1));
|
||||||
});
|
});
|
||||||
|
|
||||||
test('ignore key event from web platform', () async {
|
|
||||||
final TextSelectionDelegate delegate = FakeEditableTextState();
|
|
||||||
final ViewportOffset viewportOffset = ViewportOffset.zero();
|
|
||||||
late TextSelection currentSelection;
|
|
||||||
final RenderEditable editable = RenderEditable(
|
|
||||||
backgroundCursorColor: Colors.grey,
|
|
||||||
selectionColor: Colors.black,
|
|
||||||
textDirection: TextDirection.ltr,
|
|
||||||
cursorColor: Colors.red,
|
|
||||||
offset: viewportOffset,
|
|
||||||
// This makes the scroll axis vertical.
|
|
||||||
maxLines: 2,
|
|
||||||
textSelectionDelegate: delegate,
|
|
||||||
onSelectionChanged: (TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) {
|
|
||||||
currentSelection = selection;
|
|
||||||
},
|
|
||||||
startHandleLayerLink: LayerLink(),
|
|
||||||
endHandleLayerLink: LayerLink(),
|
|
||||||
text: const TextSpan(
|
|
||||||
text: 'test\ntest',
|
|
||||||
style: TextStyle(
|
|
||||||
height: 1.0, fontSize: 10.0, fontFamily: 'Ahem',
|
|
||||||
),
|
|
||||||
),
|
|
||||||
selection: const TextSelection.collapsed(
|
|
||||||
offset: 4,
|
|
||||||
),
|
|
||||||
);
|
|
||||||
|
|
||||||
layout(editable);
|
|
||||||
editable.hasFocus = true;
|
|
||||||
|
|
||||||
expect(
|
|
||||||
editable,
|
|
||||||
paints..paragraph(offset: Offset.zero),
|
|
||||||
);
|
|
||||||
|
|
||||||
editable.selectPositionAt(from: Offset.zero, cause: SelectionChangedCause.tap);
|
|
||||||
editable.selection = const TextSelection.collapsed(offset: 0);
|
|
||||||
pumpFrame();
|
|
||||||
|
|
||||||
if(kIsWeb) {
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowRight, platform: 'web');
|
|
||||||
expect(currentSelection.isCollapsed, true);
|
|
||||||
expect(currentSelection.baseOffset, 0);
|
|
||||||
} else {
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowRight, platform: 'android');
|
|
||||||
expect(currentSelection.isCollapsed, true);
|
|
||||||
expect(currentSelection.baseOffset, 1);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
test('selects correct place with offsets', () {
|
test('selects correct place with offsets', () {
|
||||||
final TextSelectionDelegate delegate = FakeEditableTextState();
|
final TextSelectionDelegate delegate = FakeEditableTextState();
|
||||||
final ViewportOffset viewportOffset = ViewportOffset.zero();
|
final ViewportOffset viewportOffset = ViewportOffset.zero();
|
||||||
@ -748,14 +696,16 @@ void main() {
|
|||||||
expect(editable.maxScrollExtent, equals(10));
|
expect(editable.maxScrollExtent, equals(10));
|
||||||
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/42772
|
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/42772
|
||||||
|
|
||||||
test('arrow keys and delete handle simple text correctly', () async {
|
test('moveSelectionLeft/RightByLine stays on the current line', () async {
|
||||||
|
const String text = 'one two three\n\nfour five six';
|
||||||
final TextSelectionDelegate delegate = FakeEditableTextState()
|
final TextSelectionDelegate delegate = FakeEditableTextState()
|
||||||
..textEditingValue = const TextEditingValue(
|
..textEditingValue = const TextEditingValue(
|
||||||
text: 'test',
|
text: text,
|
||||||
selection: TextSelection.collapsed(offset: 0),
|
selection: TextSelection.collapsed(offset: 0),
|
||||||
);
|
);
|
||||||
final ViewportOffset viewportOffset = ViewportOffset.zero();
|
final ViewportOffset viewportOffset = ViewportOffset.zero();
|
||||||
late TextSelection currentSelection;
|
late TextSelection currentSelection;
|
||||||
|
|
||||||
final RenderEditable editable = RenderEditable(
|
final RenderEditable editable = RenderEditable(
|
||||||
backgroundCursorColor: Colors.grey,
|
backgroundCursorColor: Colors.grey,
|
||||||
selectionColor: Colors.black,
|
selectionColor: Colors.black,
|
||||||
@ -764,6 +714,88 @@ void main() {
|
|||||||
offset: viewportOffset,
|
offset: viewportOffset,
|
||||||
textSelectionDelegate: delegate,
|
textSelectionDelegate: delegate,
|
||||||
onSelectionChanged: (TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) {
|
onSelectionChanged: (TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) {
|
||||||
|
renderObject.selection = selection;
|
||||||
|
currentSelection = selection;
|
||||||
|
},
|
||||||
|
startHandleLayerLink: LayerLink(),
|
||||||
|
endHandleLayerLink: LayerLink(),
|
||||||
|
text: const TextSpan(
|
||||||
|
text: text,
|
||||||
|
style: TextStyle(
|
||||||
|
height: 1.0, fontSize: 10.0, fontFamily: 'Ahem',
|
||||||
|
),
|
||||||
|
),
|
||||||
|
selection: const TextSelection.collapsed(
|
||||||
|
offset: 0,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
|
||||||
|
layout(editable);
|
||||||
|
editable.hasFocus = true;
|
||||||
|
|
||||||
|
editable.selectPositionAt(from: Offset.zero, cause: SelectionChangedCause.tap);
|
||||||
|
editable.selection = const TextSelection.collapsed(offset: 0);
|
||||||
|
pumpFrame();
|
||||||
|
|
||||||
|
// Move to the end of the first line.
|
||||||
|
editable.moveSelectionRightByLine(SelectionChangedCause.keyboard);
|
||||||
|
expect(currentSelection.isCollapsed, true);
|
||||||
|
expect(currentSelection.baseOffset, 13);
|
||||||
|
// RenderEditable relies on its parent that passes onSelectionChanged to set
|
||||||
|
// the selection.
|
||||||
|
|
||||||
|
// Try moveSelectionRightByLine again and nothing happens because we're
|
||||||
|
// already at the end of a line.
|
||||||
|
editable.moveSelectionRightByLine(SelectionChangedCause.keyboard);
|
||||||
|
expect(currentSelection.isCollapsed, true);
|
||||||
|
expect(currentSelection.baseOffset, 13);
|
||||||
|
|
||||||
|
// Move back to the start of the line.
|
||||||
|
editable.moveSelectionLeftByLine(SelectionChangedCause.keyboard);
|
||||||
|
expect(currentSelection.isCollapsed, true);
|
||||||
|
expect(currentSelection.baseOffset, 0);
|
||||||
|
|
||||||
|
// Trying moveSelectionLeftByLine does nothing at the leftmost of the field.
|
||||||
|
editable.moveSelectionLeftByLine(SelectionChangedCause.keyboard);
|
||||||
|
expect(currentSelection.isCollapsed, true);
|
||||||
|
expect(currentSelection.baseOffset, 0);
|
||||||
|
|
||||||
|
// Move the selection to the empty line.
|
||||||
|
editable.moveSelectionRightByLine(SelectionChangedCause.keyboard);
|
||||||
|
expect(currentSelection.isCollapsed, true);
|
||||||
|
expect(currentSelection.baseOffset, 13);
|
||||||
|
editable.moveSelectionRight(SelectionChangedCause.keyboard);
|
||||||
|
expect(currentSelection.isCollapsed, true);
|
||||||
|
expect(currentSelection.baseOffset, 14);
|
||||||
|
|
||||||
|
// Neither moveSelectionLeftByLine nor moveSelectionRightByLine do anything
|
||||||
|
// here, because we're at both the beginning and end of the line.
|
||||||
|
editable.moveSelectionLeftByLine(SelectionChangedCause.keyboard);
|
||||||
|
expect(currentSelection.isCollapsed, true);
|
||||||
|
expect(currentSelection.baseOffset, 14);
|
||||||
|
editable.moveSelectionRightByLine(SelectionChangedCause.keyboard);
|
||||||
|
expect(currentSelection.isCollapsed, true);
|
||||||
|
expect(currentSelection.baseOffset, 14);
|
||||||
|
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/61021
|
||||||
|
|
||||||
|
test('arrow keys and delete handle simple text correctly', () async {
|
||||||
|
final TextSelectionDelegate delegate = FakeEditableTextState()
|
||||||
|
..textEditingValue = const TextEditingValue(
|
||||||
|
text: 'test',
|
||||||
|
selection: TextSelection.collapsed(offset: 0),
|
||||||
|
);
|
||||||
|
final ViewportOffset viewportOffset = ViewportOffset.zero();
|
||||||
|
late TextSelection currentSelection;
|
||||||
|
|
||||||
|
final RenderEditable editable = RenderEditable(
|
||||||
|
backgroundCursorColor: Colors.grey,
|
||||||
|
selectionColor: Colors.black,
|
||||||
|
textDirection: TextDirection.ltr,
|
||||||
|
cursorColor: Colors.red,
|
||||||
|
offset: viewportOffset,
|
||||||
|
textSelectionDelegate: delegate,
|
||||||
|
onSelectionChanged: (TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) {
|
||||||
|
renderObject.selection = selection;
|
||||||
currentSelection = selection;
|
currentSelection = selection;
|
||||||
},
|
},
|
||||||
startHandleLayerLink: LayerLink(),
|
startHandleLayerLink: LayerLink(),
|
||||||
@ -786,13 +818,11 @@ void main() {
|
|||||||
editable.selection = const TextSelection.collapsed(offset: 0);
|
editable.selection = const TextSelection.collapsed(offset: 0);
|
||||||
pumpFrame();
|
pumpFrame();
|
||||||
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowRight, platform: 'android');
|
editable.moveSelectionRight(SelectionChangedCause.keyboard);
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.arrowRight, platform: 'android');
|
|
||||||
expect(currentSelection.isCollapsed, true);
|
expect(currentSelection.isCollapsed, true);
|
||||||
expect(currentSelection.baseOffset, 1);
|
expect(currentSelection.baseOffset, 1);
|
||||||
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowLeft, platform: 'android');
|
editable.moveSelectionLeft(SelectionChangedCause.keyboard);
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.arrowLeft, platform: 'android');
|
|
||||||
expect(currentSelection.isCollapsed, true);
|
expect(currentSelection.isCollapsed, true);
|
||||||
expect(currentSelection.baseOffset, 0);
|
expect(currentSelection.baseOffset, 0);
|
||||||
|
|
||||||
@ -817,6 +847,7 @@ void main() {
|
|||||||
offset: viewportOffset,
|
offset: viewportOffset,
|
||||||
textSelectionDelegate: delegate,
|
textSelectionDelegate: delegate,
|
||||||
onSelectionChanged: (TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) {
|
onSelectionChanged: (TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) {
|
||||||
|
renderObject.selection = selection;
|
||||||
currentSelection = selection;
|
currentSelection = selection;
|
||||||
},
|
},
|
||||||
startHandleLayerLink: LayerLink(),
|
startHandleLayerLink: LayerLink(),
|
||||||
@ -838,17 +869,13 @@ void main() {
|
|||||||
editable.selection = const TextSelection.collapsed(offset: 4);
|
editable.selection = const TextSelection.collapsed(offset: 4);
|
||||||
pumpFrame();
|
pumpFrame();
|
||||||
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowRight, platform: 'android');
|
editable.moveSelectionRight(SelectionChangedCause.keyboard);
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.arrowRight, platform: 'android');
|
|
||||||
expect(currentSelection.isCollapsed, true);
|
expect(currentSelection.isCollapsed, true);
|
||||||
expect(currentSelection.baseOffset, 6);
|
expect(currentSelection.baseOffset, 6);
|
||||||
editable.selection = currentSelection;
|
|
||||||
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowLeft, platform: 'android');
|
editable.moveSelectionLeft(SelectionChangedCause.keyboard);
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.arrowLeft, platform: 'android');
|
|
||||||
expect(currentSelection.isCollapsed, true);
|
expect(currentSelection.isCollapsed, true);
|
||||||
expect(currentSelection.baseOffset, 4);
|
expect(currentSelection.baseOffset, 4);
|
||||||
editable.selection = currentSelection;
|
|
||||||
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.delete, platform: 'android');
|
await simulateKeyDownEvent(LogicalKeyboardKey.delete, platform: 'android');
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.delete, platform: 'android');
|
await simulateKeyUpEvent(LogicalKeyboardKey.delete, platform: 'android');
|
||||||
@ -871,6 +898,7 @@ void main() {
|
|||||||
offset: viewportOffset,
|
offset: viewportOffset,
|
||||||
textSelectionDelegate: delegate,
|
textSelectionDelegate: delegate,
|
||||||
onSelectionChanged: (TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) {
|
onSelectionChanged: (TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) {
|
||||||
|
renderObject.selection = selection;
|
||||||
currentSelection = selection;
|
currentSelection = selection;
|
||||||
},
|
},
|
||||||
startHandleLayerLink: LayerLink(),
|
startHandleLayerLink: LayerLink(),
|
||||||
@ -892,17 +920,13 @@ void main() {
|
|||||||
editable.selection = const TextSelection.collapsed(offset: 4);
|
editable.selection = const TextSelection.collapsed(offset: 4);
|
||||||
pumpFrame();
|
pumpFrame();
|
||||||
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowRight, platform: 'android');
|
editable.moveSelectionRight(SelectionChangedCause.keyboard);
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.arrowRight, platform: 'android');
|
|
||||||
expect(currentSelection.isCollapsed, true);
|
expect(currentSelection.isCollapsed, true);
|
||||||
expect(currentSelection.baseOffset, 12);
|
expect(currentSelection.baseOffset, 12);
|
||||||
editable.selection = currentSelection;
|
|
||||||
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowLeft, platform: 'android');
|
editable.moveSelectionLeft(SelectionChangedCause.keyboard);
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.arrowLeft, platform: 'android');
|
|
||||||
expect(currentSelection.isCollapsed, true);
|
expect(currentSelection.isCollapsed, true);
|
||||||
expect(currentSelection.baseOffset, 4);
|
expect(currentSelection.baseOffset, 4);
|
||||||
editable.selection = currentSelection;
|
|
||||||
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.delete, platform: 'android');
|
await simulateKeyDownEvent(LogicalKeyboardKey.delete, platform: 'android');
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.delete, platform: 'android');
|
await simulateKeyUpEvent(LogicalKeyboardKey.delete, platform: 'android');
|
||||||
@ -921,6 +945,7 @@ void main() {
|
|||||||
offset: viewportOffset,
|
offset: viewportOffset,
|
||||||
textSelectionDelegate: delegate,
|
textSelectionDelegate: delegate,
|
||||||
onSelectionChanged: (TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) {
|
onSelectionChanged: (TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) {
|
||||||
|
renderObject.selection = selection;
|
||||||
currentSelection = selection;
|
currentSelection = selection;
|
||||||
},
|
},
|
||||||
startHandleLayerLink: LayerLink(),
|
startHandleLayerLink: LayerLink(),
|
||||||
@ -943,13 +968,11 @@ void main() {
|
|||||||
editable.selection = const TextSelection.collapsed(offset: 0);
|
editable.selection = const TextSelection.collapsed(offset: 0);
|
||||||
pumpFrame();
|
pumpFrame();
|
||||||
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowRight, platform: 'android');
|
editable.moveSelectionRight(SelectionChangedCause.keyboard);
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.arrowRight, platform: 'android');
|
|
||||||
expect(currentSelection.isCollapsed, true);
|
expect(currentSelection.isCollapsed, true);
|
||||||
expect(currentSelection.baseOffset, 2);
|
expect(currentSelection.baseOffset, 2);
|
||||||
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowLeft, platform: 'android');
|
editable.moveSelectionLeft(SelectionChangedCause.keyboard);
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.arrowLeft, platform: 'android');
|
|
||||||
expect(currentSelection.isCollapsed, true);
|
expect(currentSelection.isCollapsed, true);
|
||||||
expect(currentSelection.baseOffset, 0);
|
expect(currentSelection.baseOffset, 0);
|
||||||
|
|
||||||
@ -998,19 +1021,14 @@ void main() {
|
|||||||
editable.selection = const TextSelection.collapsed(offset: 0);
|
editable.selection = const TextSelection.collapsed(offset: 0);
|
||||||
pumpFrame();
|
pumpFrame();
|
||||||
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowRight, platform: 'android');
|
editable.moveSelectionRight(SelectionChangedCause.keyboard);
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.arrowRight, platform: 'android');
|
editable.moveSelectionRight(SelectionChangedCause.keyboard);
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowRight, platform: 'android');
|
editable.moveSelectionRight(SelectionChangedCause.keyboard);
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.arrowRight, platform: 'android');
|
editable.moveSelectionRight(SelectionChangedCause.keyboard);
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowRight, platform: 'android');
|
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.arrowRight, platform: 'android');
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowRight, platform: 'android');
|
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.arrowRight, platform: 'android');
|
|
||||||
expect(editable.selection?.isCollapsed, true);
|
expect(editable.selection?.isCollapsed, true);
|
||||||
expect(editable.selection?.baseOffset, 4);
|
expect(editable.selection?.baseOffset, 4);
|
||||||
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowLeft, platform: 'android');
|
editable.moveSelectionLeft(SelectionChangedCause.keyboard);
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.arrowLeft, platform: 'android');
|
|
||||||
expect(editable.selection?.isCollapsed, true);
|
expect(editable.selection?.isCollapsed, true);
|
||||||
expect(editable.selection?.baseOffset, 3);
|
expect(editable.selection?.baseOffset, 3);
|
||||||
|
|
||||||
@ -1031,6 +1049,7 @@ void main() {
|
|||||||
offset: viewportOffset,
|
offset: viewportOffset,
|
||||||
textSelectionDelegate: delegate,
|
textSelectionDelegate: delegate,
|
||||||
onSelectionChanged: (TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) {
|
onSelectionChanged: (TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) {
|
||||||
|
renderObject.selection = selection;
|
||||||
currentSelection = selection;
|
currentSelection = selection;
|
||||||
},
|
},
|
||||||
startHandleLayerLink: LayerLink(),
|
startHandleLayerLink: LayerLink(),
|
||||||
@ -1050,32 +1069,28 @@ void main() {
|
|||||||
editable.selection = const TextSelection(baseOffset: 2, extentOffset: 4);
|
editable.selection = const TextSelection(baseOffset: 2, extentOffset: 4);
|
||||||
pumpFrame();
|
pumpFrame();
|
||||||
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowRight);
|
editable.moveSelectionRight(SelectionChangedCause.keyboard);
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.arrowRight);
|
|
||||||
expect(currentSelection.isCollapsed, true);
|
expect(currentSelection.isCollapsed, true);
|
||||||
expect(currentSelection.baseOffset, 4);
|
expect(currentSelection.baseOffset, 4);
|
||||||
|
|
||||||
editable.selection = const TextSelection(baseOffset: 4, extentOffset: 2);
|
editable.selection = const TextSelection(baseOffset: 4, extentOffset: 2);
|
||||||
pumpFrame();
|
pumpFrame();
|
||||||
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowRight);
|
editable.moveSelectionRight(SelectionChangedCause.keyboard);
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.arrowRight);
|
|
||||||
expect(currentSelection.isCollapsed, true);
|
expect(currentSelection.isCollapsed, true);
|
||||||
expect(currentSelection.baseOffset, 4);
|
expect(currentSelection.baseOffset, 4);
|
||||||
|
|
||||||
editable.selection = const TextSelection(baseOffset: 2, extentOffset: 4);
|
editable.selection = const TextSelection(baseOffset: 2, extentOffset: 4);
|
||||||
pumpFrame();
|
pumpFrame();
|
||||||
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowLeft);
|
editable.moveSelectionLeft(SelectionChangedCause.keyboard);
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.arrowLeft);
|
|
||||||
expect(currentSelection.isCollapsed, true);
|
expect(currentSelection.isCollapsed, true);
|
||||||
expect(currentSelection.baseOffset, 2);
|
expect(currentSelection.baseOffset, 2);
|
||||||
|
|
||||||
editable.selection = const TextSelection(baseOffset: 4, extentOffset: 2);
|
editable.selection = const TextSelection(baseOffset: 4, extentOffset: 2);
|
||||||
pumpFrame();
|
pumpFrame();
|
||||||
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowLeft);
|
editable.moveSelectionLeft(SelectionChangedCause.keyboard);
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.arrowLeft);
|
|
||||||
expect(currentSelection.isCollapsed, true);
|
expect(currentSelection.isCollapsed, true);
|
||||||
expect(currentSelection.baseOffset, 2);
|
expect(currentSelection.baseOffset, 2);
|
||||||
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/58068
|
}, skip: isBrowser); // https://github.com/flutter/flutter/issues/58068
|
||||||
@ -1092,6 +1107,7 @@ void main() {
|
|||||||
offset: viewportOffset,
|
offset: viewportOffset,
|
||||||
textSelectionDelegate: delegate,
|
textSelectionDelegate: delegate,
|
||||||
onSelectionChanged: (TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) {
|
onSelectionChanged: (TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) {
|
||||||
|
renderObject.selection = selection;
|
||||||
currentSelection = selection;
|
currentSelection = selection;
|
||||||
},
|
},
|
||||||
startHandleLayerLink: LayerLink(),
|
startHandleLayerLink: LayerLink(),
|
||||||
@ -1111,10 +1127,7 @@ void main() {
|
|||||||
editable.selection = const TextSelection(baseOffset: 2, extentOffset: 4);
|
editable.selection = const TextSelection(baseOffset: 2, extentOffset: 4);
|
||||||
pumpFrame();
|
pumpFrame();
|
||||||
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.shift);
|
editable.extendSelectionRight(SelectionChangedCause.keyboard);
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowRight);
|
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.arrowRight);
|
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.shift);
|
|
||||||
expect(currentSelection.isCollapsed, false);
|
expect(currentSelection.isCollapsed, false);
|
||||||
expect(currentSelection.baseOffset, 2);
|
expect(currentSelection.baseOffset, 2);
|
||||||
expect(currentSelection.extentOffset, 5);
|
expect(currentSelection.extentOffset, 5);
|
||||||
@ -1122,10 +1135,7 @@ void main() {
|
|||||||
editable.selection = const TextSelection(baseOffset: 4, extentOffset: 2);
|
editable.selection = const TextSelection(baseOffset: 4, extentOffset: 2);
|
||||||
pumpFrame();
|
pumpFrame();
|
||||||
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.shift);
|
editable.extendSelectionRight(SelectionChangedCause.keyboard);
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowRight);
|
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.arrowRight);
|
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.shift);
|
|
||||||
expect(currentSelection.isCollapsed, false);
|
expect(currentSelection.isCollapsed, false);
|
||||||
expect(currentSelection.baseOffset, 4);
|
expect(currentSelection.baseOffset, 4);
|
||||||
expect(currentSelection.extentOffset, 3);
|
expect(currentSelection.extentOffset, 3);
|
||||||
@ -1133,10 +1143,7 @@ void main() {
|
|||||||
editable.selection = const TextSelection(baseOffset: 2, extentOffset: 4);
|
editable.selection = const TextSelection(baseOffset: 2, extentOffset: 4);
|
||||||
pumpFrame();
|
pumpFrame();
|
||||||
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.shift);
|
editable.extendSelectionLeft(SelectionChangedCause.keyboard);
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowLeft);
|
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.arrowLeft);
|
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.shift);
|
|
||||||
expect(currentSelection.isCollapsed, false);
|
expect(currentSelection.isCollapsed, false);
|
||||||
expect(currentSelection.baseOffset, 2);
|
expect(currentSelection.baseOffset, 2);
|
||||||
expect(currentSelection.extentOffset, 3);
|
expect(currentSelection.extentOffset, 3);
|
||||||
@ -1144,10 +1151,7 @@ void main() {
|
|||||||
editable.selection = const TextSelection(baseOffset: 4, extentOffset: 2);
|
editable.selection = const TextSelection(baseOffset: 4, extentOffset: 2);
|
||||||
pumpFrame();
|
pumpFrame();
|
||||||
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.shift);
|
editable.extendSelectionLeft(SelectionChangedCause.keyboard);
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowLeft);
|
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.arrowLeft);
|
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.shift);
|
|
||||||
expect(currentSelection.isCollapsed, false);
|
expect(currentSelection.isCollapsed, false);
|
||||||
expect(currentSelection.baseOffset, 4);
|
expect(currentSelection.baseOffset, 4);
|
||||||
expect(currentSelection.extentOffset, 1);
|
expect(currentSelection.extentOffset, 1);
|
||||||
@ -1165,6 +1169,7 @@ void main() {
|
|||||||
offset: viewportOffset,
|
offset: viewportOffset,
|
||||||
textSelectionDelegate: delegate,
|
textSelectionDelegate: delegate,
|
||||||
onSelectionChanged: (TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) {
|
onSelectionChanged: (TextSelection selection, RenderEditable renderObject, SelectionChangedCause cause) {
|
||||||
|
renderObject.selection = selection;
|
||||||
currentSelection = selection;
|
currentSelection = selection;
|
||||||
},
|
},
|
||||||
startHandleLayerLink: LayerLink(),
|
startHandleLayerLink: LayerLink(),
|
||||||
@ -1187,34 +1192,26 @@ void main() {
|
|||||||
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.shift);
|
await simulateKeyDownEvent(LogicalKeyboardKey.shift);
|
||||||
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowRight);
|
editable.moveSelectionRight(SelectionChangedCause.keyboard);
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.arrowRight);
|
|
||||||
expect(currentSelection.isCollapsed, true);
|
expect(currentSelection.isCollapsed, true);
|
||||||
expect(currentSelection.baseOffset, 3);
|
expect(currentSelection.baseOffset, 3);
|
||||||
editable.selection = currentSelection;
|
|
||||||
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowLeft);
|
editable.moveSelectionLeft(SelectionChangedCause.keyboard);
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.arrowLeft);
|
|
||||||
expect(currentSelection.isCollapsed, true);
|
expect(currentSelection.isCollapsed, true);
|
||||||
expect(currentSelection.baseOffset, 2);
|
expect(currentSelection.baseOffset, 2);
|
||||||
editable.selection = currentSelection;
|
|
||||||
|
|
||||||
final LogicalKeyboardKey wordModifier =
|
final LogicalKeyboardKey wordModifier =
|
||||||
Platform.isMacOS ? LogicalKeyboardKey.alt : LogicalKeyboardKey.control;
|
Platform.isMacOS ? LogicalKeyboardKey.alt : LogicalKeyboardKey.control;
|
||||||
|
|
||||||
await simulateKeyDownEvent(wordModifier);
|
await simulateKeyDownEvent(wordModifier);
|
||||||
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowRight);
|
editable.moveSelectionRightByWord(SelectionChangedCause.keyboard);
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.arrowRight);
|
|
||||||
expect(currentSelection.isCollapsed, true);
|
expect(currentSelection.isCollapsed, true);
|
||||||
expect(currentSelection.baseOffset, 6);
|
expect(currentSelection.baseOffset, 6);
|
||||||
editable.selection = currentSelection;
|
|
||||||
|
|
||||||
await simulateKeyDownEvent(LogicalKeyboardKey.arrowLeft);
|
editable.moveSelectionLeftByWord(SelectionChangedCause.keyboard);
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.arrowLeft);
|
|
||||||
expect(currentSelection.isCollapsed, true);
|
expect(currentSelection.isCollapsed, true);
|
||||||
expect(currentSelection.baseOffset, 0);
|
expect(currentSelection.baseOffset, 0);
|
||||||
editable.selection = currentSelection;
|
|
||||||
|
|
||||||
await simulateKeyUpEvent(wordModifier);
|
await simulateKeyUpEvent(wordModifier);
|
||||||
await simulateKeyUpEvent(LogicalKeyboardKey.shift);
|
await simulateKeyUpEvent(LogicalKeyboardKey.shift);
|
||||||
|
@ -39,7 +39,7 @@ void main() {
|
|||||||
expect(find.byKey(key), findsOneWidget);
|
expect(find.byKey(key), findsOneWidget);
|
||||||
});
|
});
|
||||||
|
|
||||||
testWidgets('WidgetsApp can override default key bindings', (WidgetTester tester) async {
|
testWidgets('WidgetsApp default key bindings', (WidgetTester tester) async {
|
||||||
bool? checked = false;
|
bool? checked = false;
|
||||||
final GlobalKey key = GlobalKey();
|
final GlobalKey key = GlobalKey();
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
@ -64,9 +64,12 @@ void main() {
|
|||||||
await tester.pumpAndSettle();
|
await tester.pumpAndSettle();
|
||||||
// Default key mapping worked.
|
// Default key mapping worked.
|
||||||
expect(checked, isTrue);
|
expect(checked, isTrue);
|
||||||
checked = false;
|
});
|
||||||
|
|
||||||
|
testWidgets('WidgetsApp can override default key bindings', (WidgetTester tester) async {
|
||||||
final TestAction action = TestAction();
|
final TestAction action = TestAction();
|
||||||
|
bool? checked = false;
|
||||||
|
final GlobalKey key = GlobalKey();
|
||||||
await tester.pumpWidget(
|
await tester.pumpWidget(
|
||||||
WidgetsApp(
|
WidgetsApp(
|
||||||
key: key,
|
key: key,
|
||||||
|
@ -4181,7 +4181,7 @@ void main() {
|
|||||||
reason: 'on $platform',
|
reason: 'on $platform',
|
||||||
);
|
);
|
||||||
|
|
||||||
// Select to the beginning of the line.
|
// Select to the beginning of the first line.
|
||||||
await sendKeys(
|
await sendKeys(
|
||||||
tester,
|
tester,
|
||||||
<LogicalKeyboardKey>[
|
<LogicalKeyboardKey>[
|
||||||
@ -4196,8 +4196,8 @@ void main() {
|
|||||||
selection,
|
selection,
|
||||||
equals(
|
equals(
|
||||||
const TextSelection(
|
const TextSelection(
|
||||||
baseOffset: 20,
|
baseOffset: 0,
|
||||||
extentOffset: 55,
|
extentOffset: 72,
|
||||||
affinity: TextAffinity.downstream,
|
affinity: TextAffinity.downstream,
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
@ -4562,25 +4562,12 @@ void main() {
|
|||||||
expect(controller.text, isEmpty, reason: 'on $platform');
|
expect(controller.text, isEmpty, reason: 'on $platform');
|
||||||
}
|
}
|
||||||
|
|
||||||
testWidgets('keyboard text selection works as expected on linux', (WidgetTester tester) async {
|
testWidgets('keyboard text selection works', (WidgetTester tester) async {
|
||||||
await testTextEditing(tester, platform: 'linux');
|
final String targetPlatform = defaultTargetPlatform.toString();
|
||||||
|
final String platform = targetPlatform.substring(targetPlatform.indexOf('.') + 1).toLowerCase();
|
||||||
|
await testTextEditing(tester, platform: platform);
|
||||||
// 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, variant: TargetPlatformVariant.all());
|
||||||
|
|
||||||
testWidgets('keyboard text selection works as expected on android', (WidgetTester tester) async {
|
|
||||||
await testTextEditing(tester, platform: 'android');
|
|
||||||
// On web, using keyboard for selection is handled by the browser.
|
|
||||||
}, skip: kIsWeb);
|
|
||||||
|
|
||||||
testWidgets('keyboard text selection works as expected on fuchsia', (WidgetTester tester) async {
|
|
||||||
await testTextEditing(tester, platform: 'fuchsia');
|
|
||||||
// On web, using keyboard for selection is handled by the browser.
|
|
||||||
}, skip: kIsWeb);
|
|
||||||
|
|
||||||
testWidgets('keyboard text selection works as expected on macos', (WidgetTester tester) async {
|
|
||||||
await testTextEditing(tester, platform: 'macos');
|
|
||||||
// On web, using keyboard for selection is handled by the browser.
|
|
||||||
}, skip: kIsWeb);
|
|
||||||
|
|
||||||
testWidgets('keyboard shortcuts respect read-only', (WidgetTester tester) async {
|
testWidgets('keyboard shortcuts respect read-only', (WidgetTester tester) async {
|
||||||
final String platform = describeEnum(defaultTargetPlatform).toLowerCase();
|
final String platform = describeEnum(defaultTargetPlatform).toLowerCase();
|
||||||
@ -7182,6 +7169,172 @@ void main() {
|
|||||||
|
|
||||||
expect(tester.takeException(), null);
|
expect(tester.takeException(), null);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
testWidgets('can change behavior by overriding text editing 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: <LogicalKeySet, Intent>{
|
||||||
|
LogicalKeySet(LogicalKeyboardKey.arrowLeft): const 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.
|
||||||
|
|
||||||
|
// The right arrow key moves to the right as usual.
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
||||||
|
await tester.pump();
|
||||||
|
expect(controller.selection.isCollapsed, isTrue);
|
||||||
|
expect(controller.selection.baseOffset, 1);
|
||||||
|
|
||||||
|
// And the left arrow also moves to the right due to the Shortcuts override.
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
|
||||||
|
await tester.pump();
|
||||||
|
expect(controller.selection.isCollapsed, isTrue);
|
||||||
|
expect(controller.selection.baseOffset, 2);
|
||||||
|
|
||||||
|
// On web, using keyboard for selection is handled by the browser.
|
||||||
|
}, skip: kIsWeb);
|
||||||
|
|
||||||
|
testWidgets('can change behavior by overriding text editing actions', (WidgetTester tester) async {
|
||||||
|
final TextEditingController controller = TextEditingController(text: testText);
|
||||||
|
controller.selection = const TextSelection(
|
||||||
|
baseOffset: 0,
|
||||||
|
extentOffset: 0,
|
||||||
|
affinity: TextAffinity.upstream,
|
||||||
|
);
|
||||||
|
late final bool myIntentWasCalled;
|
||||||
|
await tester.pumpWidget(MaterialApp(
|
||||||
|
home: Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: SizedBox(
|
||||||
|
width: 400,
|
||||||
|
child: Actions(
|
||||||
|
actions: <Type, Action<Intent>>{
|
||||||
|
MoveSelectionLeftTextIntent: _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.
|
||||||
|
|
||||||
|
// The right arrow key moves to the right as usual.
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowRight);
|
||||||
|
await tester.pump();
|
||||||
|
expect(controller.selection.isCollapsed, isTrue);
|
||||||
|
expect(controller.selection.baseOffset, 1);
|
||||||
|
|
||||||
|
// And the left arrow also moves to the right due to the Actions override.
|
||||||
|
await tester.sendKeyEvent(LogicalKeyboardKey.arrowLeft);
|
||||||
|
await tester.pump();
|
||||||
|
expect(controller.selection.isCollapsed, isTrue);
|
||||||
|
expect(controller.selection.baseOffset, 2);
|
||||||
|
expect(myIntentWasCalled, isTrue);
|
||||||
|
|
||||||
|
// On web, using keyboard for selection is handled by the browser.
|
||||||
|
}, skip: kIsWeb);
|
||||||
|
|
||||||
|
testWidgets('ignore key event from web platform', (WidgetTester tester) async {
|
||||||
|
final TextEditingController controller = TextEditingController(
|
||||||
|
text: 'test\ntest',
|
||||||
|
);
|
||||||
|
controller.selection = const TextSelection(
|
||||||
|
baseOffset: 0,
|
||||||
|
extentOffset: 0,
|
||||||
|
affinity: TextAffinity.upstream,
|
||||||
|
);
|
||||||
|
bool myIntentWasCalled = false;
|
||||||
|
await tester.pumpWidget(MaterialApp(
|
||||||
|
home: Align(
|
||||||
|
alignment: Alignment.topLeft,
|
||||||
|
child: SizedBox(
|
||||||
|
width: 400,
|
||||||
|
child: Actions(
|
||||||
|
actions: <Type, Action<Intent>>{
|
||||||
|
MoveSelectionRightTextIntent: _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.
|
||||||
|
|
||||||
|
if (kIsWeb) {
|
||||||
|
await simulateKeyDownEvent(LogicalKeyboardKey.arrowRight, platform: 'web');
|
||||||
|
await tester.pump();
|
||||||
|
expect(myIntentWasCalled, isFalse);
|
||||||
|
expect(controller.selection.isCollapsed, true);
|
||||||
|
expect(controller.selection.baseOffset, 0);
|
||||||
|
} else {
|
||||||
|
await simulateKeyDownEvent(LogicalKeyboardKey.arrowRight, platform: 'android');
|
||||||
|
await tester.pump();
|
||||||
|
expect(myIntentWasCalled, isTrue);
|
||||||
|
expect(controller.selection.isCollapsed, true);
|
||||||
|
expect(controller.selection.baseOffset, 1);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
class UnsettableController extends TextEditingController {
|
class UnsettableController extends TextEditingController {
|
||||||
@ -7410,3 +7563,17 @@ class _AccentColorTextEditingController extends TextEditingController {
|
|||||||
return super.buildTextSpan(context: context, style: TextStyle(color: color), withComposing: withComposing);
|
return super.buildTextSpan(context: context, style: TextStyle(color: color), withComposing: withComposing);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
class _MyMoveSelectionRightTextAction extends TextEditingAction<Intent> {
|
||||||
|
_MyMoveSelectionRightTextAction({
|
||||||
|
required this.onInvoke,
|
||||||
|
}) : super();
|
||||||
|
|
||||||
|
final VoidCallback onInvoke;
|
||||||
|
|
||||||
|
@override
|
||||||
|
Object? invoke(Intent intent, [BuildContext? context]) {
|
||||||
|
textEditingActionTarget!.renderEditable.moveSelectionRight(SelectionChangedCause.keyboard);
|
||||||
|
onInvoke();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user