mirror of
https://github.com/flutter/flutter.git
synced 2025-06-03 00:51:18 +00:00
Windows/Linux keyboard shortcuts at a wordwrap (#96323)
This commit is contained in:
parent
ac708f1cb8
commit
c42b36f6b4
@ -317,8 +317,10 @@ class DefaultTextEditingShortcuts extends Shortcuts {
|
||||
const SingleActivator(LogicalKeyboardKey.arrowUp, shift: true, meta: true): const ExtendSelectionToDocumentBoundaryIntent(forward: false, collapseSelection: false),
|
||||
const SingleActivator(LogicalKeyboardKey.arrowDown, shift: true, meta: true): const ExtendSelectionToDocumentBoundaryIntent(forward: true, collapseSelection: false),
|
||||
|
||||
const SingleActivator(LogicalKeyboardKey.home, shift: true): const ExtendSelectionToDocumentBoundaryIntent(forward: false, collapseSelection: false),
|
||||
const SingleActivator(LogicalKeyboardKey.end, shift: true): const ExtendSelectionToDocumentBoundaryIntent(forward: true, collapseSelection: false),
|
||||
const SingleActivator(LogicalKeyboardKey.home): const ScrollToDocumentBoundaryIntent(forward: false),
|
||||
const SingleActivator(LogicalKeyboardKey.end): const ScrollToDocumentBoundaryIntent(forward: true),
|
||||
const SingleActivator(LogicalKeyboardKey.home, shift: true): const ExpandSelectionToDocumentBoundaryIntent(forward: false),
|
||||
const SingleActivator(LogicalKeyboardKey.end, shift: true): const ExpandSelectionToDocumentBoundaryIntent(forward: true),
|
||||
|
||||
const SingleActivator(LogicalKeyboardKey.keyX, meta: true): const CopySelectionTextIntent.cut(SelectionChangedCause.keyboard),
|
||||
const SingleActivator(LogicalKeyboardKey.keyC, meta: true): CopySelectionTextIntent.copy,
|
||||
@ -349,10 +351,10 @@ class DefaultTextEditingShortcuts extends Shortcuts {
|
||||
// * Meta + backspace
|
||||
static final Map<ShortcutActivator, Intent> _windowsShortcuts = <ShortcutActivator, Intent>{
|
||||
..._commonShortcuts,
|
||||
const SingleActivator(LogicalKeyboardKey.home): const ExtendSelectionToLineBreakIntent(forward: false, collapseSelection: true),
|
||||
const SingleActivator(LogicalKeyboardKey.end): const ExtendSelectionToLineBreakIntent(forward: true, collapseSelection: true),
|
||||
const SingleActivator(LogicalKeyboardKey.home, shift: true): const ExtendSelectionToLineBreakIntent(forward: false, collapseSelection: false),
|
||||
const SingleActivator(LogicalKeyboardKey.end, shift: true): const ExtendSelectionToLineBreakIntent(forward: true, collapseSelection: false),
|
||||
const SingleActivator(LogicalKeyboardKey.home): const ExtendSelectionToLineBreakIntent(forward: false, collapseSelection: true, continuesAtWrap: true),
|
||||
const SingleActivator(LogicalKeyboardKey.end): const ExtendSelectionToLineBreakIntent(forward: true, collapseSelection: true, continuesAtWrap: true),
|
||||
const SingleActivator(LogicalKeyboardKey.home, shift: true): const ExtendSelectionToLineBreakIntent(forward: false, collapseSelection: false, continuesAtWrap: true),
|
||||
const SingleActivator(LogicalKeyboardKey.end, shift: true): const ExtendSelectionToLineBreakIntent(forward: true, collapseSelection: false, continuesAtWrap: true),
|
||||
const SingleActivator(LogicalKeyboardKey.home, control: true): const ExtendSelectionToDocumentBoundaryIntent(forward: false, collapseSelection: true),
|
||||
const SingleActivator(LogicalKeyboardKey.end, control: true): const ExtendSelectionToDocumentBoundaryIntent(forward: true, collapseSelection: true),
|
||||
const SingleActivator(LogicalKeyboardKey.home, shift: true, control: true): const ExtendSelectionToDocumentBoundaryIntent(forward: false, collapseSelection: false),
|
||||
|
@ -3069,7 +3069,18 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
}
|
||||
late final Action<ReplaceTextIntent> _replaceTextAction = CallbackAction<ReplaceTextIntent>(onInvoke: _replaceText);
|
||||
|
||||
// Scrolls either to the beginning or end of the document depending on the
|
||||
// intent's `forward` parameter.
|
||||
void _scrollToDocumentBoundary(ScrollToDocumentBoundaryIntent intent) {
|
||||
if (intent.forward) {
|
||||
bringIntoView(TextPosition(offset: _value.text.length));
|
||||
} else {
|
||||
bringIntoView(const TextPosition(offset: 0));
|
||||
}
|
||||
}
|
||||
|
||||
void _updateSelection(UpdateSelectionIntent intent) {
|
||||
bringIntoView(intent.newSelection.extent);
|
||||
userUpdateTextEditingValue(
|
||||
intent.currentTextEditingValue.copyWith(selection: intent.newSelection),
|
||||
intent.cause,
|
||||
@ -3079,28 +3090,38 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
|
||||
late final _UpdateTextSelectionToAdjacentLineAction<ExtendSelectionVerticallyToAdjacentLineIntent> _adjacentLineAction = _UpdateTextSelectionToAdjacentLineAction<ExtendSelectionVerticallyToAdjacentLineIntent>(this);
|
||||
|
||||
void _expandSelection(ExpandSelectionToLineBreakIntent intent) {
|
||||
void _expandSelectionToDocumentBoundary(ExpandSelectionToDocumentBoundaryIntent intent) {
|
||||
final _TextBoundary textBoundary = _documentBoundary(intent);
|
||||
_expandSelection(intent.forward, textBoundary, true);
|
||||
}
|
||||
|
||||
void _expandSelectionToLinebreak(ExpandSelectionToLineBreakIntent intent) {
|
||||
final _TextBoundary textBoundary = _linebreak(intent);
|
||||
_expandSelection(intent.forward, textBoundary);
|
||||
}
|
||||
|
||||
void _expandSelection(bool forward, _TextBoundary textBoundary, [bool extentAtIndex = false]) {
|
||||
final TextSelection textBoundarySelection = textBoundary.textEditingValue.selection;
|
||||
if (!textBoundarySelection.isValid) {
|
||||
return;
|
||||
}
|
||||
|
||||
final bool inOrder = textBoundarySelection.baseOffset <= textBoundarySelection.extentOffset;
|
||||
final bool towardsExtent = intent.forward == inOrder;
|
||||
final bool towardsExtent = forward == inOrder;
|
||||
final TextPosition position = towardsExtent
|
||||
? textBoundarySelection.extent
|
||||
: textBoundarySelection.base;
|
||||
|
||||
final TextPosition newExtent = intent.forward
|
||||
final TextPosition newExtent = forward
|
||||
? textBoundary.getTrailingTextBoundaryAt(position)
|
||||
: textBoundary.getLeadingTextBoundaryAt(position);
|
||||
|
||||
final TextSelection newSelection = textBoundarySelection.expandTo(newExtent, textBoundarySelection.isCollapsed);
|
||||
final TextSelection newSelection = textBoundarySelection.expandTo(newExtent, textBoundarySelection.isCollapsed || extentAtIndex);
|
||||
userUpdateTextEditingValue(
|
||||
_value.copyWith(selection: newSelection),
|
||||
SelectionChangedCause.keyboard,
|
||||
);
|
||||
bringIntoView(newSelection.extent);
|
||||
}
|
||||
|
||||
late final Map<Type, Action<Intent>> _actions = <Type, Action<Intent>>{
|
||||
@ -3118,10 +3139,12 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
|
||||
ExtendSelectionByCharacterIntent: _makeOverridable(_UpdateTextSelectionAction<ExtendSelectionByCharacterIntent>(this, false, _characterBoundary,)),
|
||||
ExtendSelectionToNextWordBoundaryIntent: _makeOverridable(_UpdateTextSelectionAction<ExtendSelectionToNextWordBoundaryIntent>(this, true, _nextWordBoundary)),
|
||||
ExtendSelectionToLineBreakIntent: _makeOverridable(_UpdateTextSelectionAction<ExtendSelectionToLineBreakIntent>(this, true, _linebreak)),
|
||||
ExpandSelectionToLineBreakIntent: _makeOverridable(CallbackAction<ExpandSelectionToLineBreakIntent>(onInvoke: _expandSelection)),
|
||||
ExpandSelectionToLineBreakIntent: _makeOverridable(CallbackAction<ExpandSelectionToLineBreakIntent>(onInvoke: _expandSelectionToLinebreak)),
|
||||
ExpandSelectionToDocumentBoundaryIntent: _makeOverridable(CallbackAction<ExpandSelectionToDocumentBoundaryIntent>(onInvoke: _expandSelectionToDocumentBoundary)),
|
||||
ExtendSelectionVerticallyToAdjacentLineIntent: _makeOverridable(_adjacentLineAction),
|
||||
ExtendSelectionToDocumentBoundaryIntent: _makeOverridable(_UpdateTextSelectionAction<ExtendSelectionToDocumentBoundaryIntent>(this, true, _documentBoundary)),
|
||||
ExtendSelectionToNextWordBoundaryOrCaretLocationIntent: _makeOverridable(_ExtendSelectionOrCaretPositionAction(this, _nextWordBoundary)),
|
||||
ScrollToDocumentBoundaryIntent: _makeOverridable(CallbackAction<ScrollToDocumentBoundaryIntent>(onInvoke: _scrollToDocumentBoundary)),
|
||||
|
||||
// Copy Paste
|
||||
SelectAllTextIntent: _makeOverridable(_SelectAllAction(this)),
|
||||
@ -3763,7 +3786,10 @@ class _WordBoundary extends _TextBoundary {
|
||||
// interpreted as caret locations because [TextPainter.getLineAtOffset] is
|
||||
// text-affinity-aware.
|
||||
class _LineBreak extends _TextBoundary {
|
||||
const _LineBreak(this.textLayout, this.textEditingValue);
|
||||
const _LineBreak(
|
||||
this.textLayout,
|
||||
this.textEditingValue,
|
||||
);
|
||||
|
||||
final TextLayoutMetrics textLayout;
|
||||
|
||||
@ -3776,6 +3802,7 @@ class _LineBreak extends _TextBoundary {
|
||||
offset: textLayout.getLineAtOffset(position).start,
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
TextPosition getTrailingTextBoundaryAt(TextPosition position) {
|
||||
return TextPosition(
|
||||
@ -3945,12 +3972,39 @@ class _DeleteTextAction<T extends DirectionalTextEditingIntent> extends ContextA
|
||||
}
|
||||
|
||||
class _UpdateTextSelectionAction<T extends DirectionalCaretMovementIntent> extends ContextAction<T> {
|
||||
_UpdateTextSelectionAction(this.state, this.ignoreNonCollapsedSelection, this.getTextBoundariesForIntent);
|
||||
_UpdateTextSelectionAction(
|
||||
this.state,
|
||||
this.ignoreNonCollapsedSelection,
|
||||
this.getTextBoundariesForIntent,
|
||||
);
|
||||
|
||||
final EditableTextState state;
|
||||
final bool ignoreNonCollapsedSelection;
|
||||
final _TextBoundary Function(T intent) getTextBoundariesForIntent;
|
||||
|
||||
static const int NEWLINE_CODE_UNIT = 10;
|
||||
|
||||
// Returns true iff the given position is at a wordwrap boundary in the
|
||||
// upstream position.
|
||||
bool _isAtWordwrapUpstream(TextPosition position) {
|
||||
final TextPosition end = TextPosition(
|
||||
offset: state.renderEditable.getLineAtOffset(position).end,
|
||||
affinity: TextAffinity.upstream,
|
||||
);
|
||||
return end == position && end.offset != state.textEditingValue.text.length
|
||||
&& state.textEditingValue.text.codeUnitAt(position.offset) != NEWLINE_CODE_UNIT;
|
||||
}
|
||||
|
||||
// Returns true iff the given position at a wordwrap boundary in the
|
||||
// downstream position.
|
||||
bool _isAtWordwrapDownstream(TextPosition position) {
|
||||
final TextPosition start = TextPosition(
|
||||
offset: state.renderEditable.getLineAtOffset(position).start,
|
||||
);
|
||||
return start == position && start.offset != 0
|
||||
&& state.textEditingValue.text.codeUnitAt(position.offset - 1) != NEWLINE_CODE_UNIT;
|
||||
}
|
||||
|
||||
@override
|
||||
Object? invoke(T intent, [BuildContext? context]) {
|
||||
final TextSelection selection = state._value.selection;
|
||||
@ -3986,7 +4040,23 @@ class _UpdateTextSelectionAction<T extends DirectionalCaretMovementIntent> exten
|
||||
);
|
||||
}
|
||||
|
||||
final TextPosition extent = textBoundarySelection.extent;
|
||||
TextPosition extent = textBoundarySelection.extent;
|
||||
|
||||
// If continuesAtWrap is true extent and is at the relevant wordwrap, then
|
||||
// move it just to the other side of the wordwrap.
|
||||
if (intent.continuesAtWrap) {
|
||||
if (intent.forward && _isAtWordwrapUpstream(extent)) {
|
||||
extent = TextPosition(
|
||||
offset: extent.offset,
|
||||
);
|
||||
} else if (!intent.forward && _isAtWordwrapDownstream(extent)) {
|
||||
extent = TextPosition(
|
||||
offset: extent.offset,
|
||||
affinity: TextAffinity.upstream,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
final TextPosition newExtent = intent.forward
|
||||
? textBoundary.getTrailingTextBoundaryAt(extent)
|
||||
: textBoundary.getLeadingTextBoundaryAt(extent);
|
||||
|
@ -20,7 +20,9 @@ class DoNothingAndStopPropagationTextIntent extends Intent {
|
||||
/// direction of the current caret location.
|
||||
abstract class DirectionalTextEditingIntent extends Intent {
|
||||
/// Creates a [DirectionalTextEditingIntent].
|
||||
const DirectionalTextEditingIntent(this.forward);
|
||||
const DirectionalTextEditingIntent(
|
||||
this.forward,
|
||||
);
|
||||
|
||||
/// Whether the input field, if applicable, should perform the text editing
|
||||
/// operation from the current caret location towards the end of the document.
|
||||
@ -65,7 +67,10 @@ abstract class DirectionalCaretMovementIntent extends DirectionalTextEditingInte
|
||||
const DirectionalCaretMovementIntent(
|
||||
bool forward,
|
||||
this.collapseSelection,
|
||||
[this.collapseAtReversal = false]
|
||||
[
|
||||
this.collapseAtReversal = false,
|
||||
this.continuesAtWrap = false,
|
||||
]
|
||||
) : assert(!collapseSelection || !collapseAtReversal),
|
||||
super(forward);
|
||||
|
||||
@ -90,6 +95,14 @@ abstract class DirectionalCaretMovementIntent extends DirectionalTextEditingInte
|
||||
///
|
||||
/// Cannot be true when collapseSelection is true.
|
||||
final bool collapseAtReversal;
|
||||
|
||||
/// Whether or not to continue to the next line at a wordwrap.
|
||||
///
|
||||
/// If true, when an [Intent] to go to the beginning/end of a wordwrapped line
|
||||
/// is received and the selection is already at the beginning/end of the line,
|
||||
/// then the selection will be moved to the next/previous line. If false, the
|
||||
/// selection will remain at the wordwrap.
|
||||
final bool continuesAtWrap;
|
||||
}
|
||||
|
||||
/// Extends, or moves the current selection from the current
|
||||
@ -132,6 +145,23 @@ class ExtendSelectionToNextWordBoundaryOrCaretLocationIntent extends Directional
|
||||
}) : super(forward);
|
||||
}
|
||||
|
||||
/// Expands the current selection to the document boundary in the direction
|
||||
/// given by [forward].
|
||||
///
|
||||
/// Unlike [ExpandSelectionToLineBreakIntent], the extent will be moved, which
|
||||
/// matches the behavior on MacOS.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// [ExtendSelectionToDocumentBoundaryIntent], which is similar but always
|
||||
/// moves the extent.
|
||||
class ExpandSelectionToDocumentBoundaryIntent extends DirectionalTextEditingIntent {
|
||||
/// Creates an [ExpandSelectionToDocumentBoundaryIntent].
|
||||
const ExpandSelectionToDocumentBoundaryIntent({
|
||||
required bool forward,
|
||||
}) : super(forward);
|
||||
}
|
||||
|
||||
/// Expands the current selection to the closest line break in the direction
|
||||
/// given by [forward].
|
||||
///
|
||||
@ -165,8 +195,9 @@ class ExtendSelectionToLineBreakIntent extends DirectionalCaretMovementIntent {
|
||||
required bool forward,
|
||||
required bool collapseSelection,
|
||||
bool collapseAtReversal = false,
|
||||
bool continuesAtWrap = false,
|
||||
}) : assert(!collapseSelection || !collapseAtReversal),
|
||||
super(forward, collapseSelection, collapseAtReversal);
|
||||
super(forward, collapseSelection, collapseAtReversal, continuesAtWrap);
|
||||
}
|
||||
|
||||
/// Extends, or moves the current selection from the current
|
||||
@ -182,6 +213,11 @@ class ExtendSelectionVerticallyToAdjacentLineIntent extends DirectionalCaretMove
|
||||
|
||||
/// Extends, or moves the current selection from the current
|
||||
/// [TextSelection.extent] position to the start or the end of the document.
|
||||
///
|
||||
/// See also:
|
||||
///
|
||||
/// [ExtendSelectionToDocumentBoundaryIntent], which is similar but always
|
||||
/// increases the size of the selection.
|
||||
class ExtendSelectionToDocumentBoundaryIntent extends DirectionalCaretMovementIntent {
|
||||
/// Creates an [ExtendSelectionToDocumentBoundaryIntent].
|
||||
const ExtendSelectionToDocumentBoundaryIntent({
|
||||
@ -190,6 +226,15 @@ class ExtendSelectionToDocumentBoundaryIntent extends DirectionalCaretMovementIn
|
||||
}) : super(forward, collapseSelection);
|
||||
}
|
||||
|
||||
/// Scrolls to the beginning or end of the document depending on the [forward]
|
||||
/// parameter.
|
||||
class ScrollToDocumentBoundaryIntent extends DirectionalTextEditingIntent {
|
||||
/// Creates a [ScrollToDocumentBoundaryIntent].
|
||||
const ScrollToDocumentBoundaryIntent({
|
||||
required bool forward,
|
||||
}) : super(forward);
|
||||
}
|
||||
|
||||
/// An [Intent] to select everything in the field.
|
||||
class SelectAllTextIntent extends Intent {
|
||||
/// Creates an instance of [SelectAllTextIntent].
|
||||
|
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user