Unverified Commit 563e0a4a authored by Tomasz Gucio's avatar Tomasz Gucio Committed by GitHub

Page Up / Page Down in text fields (#107602)

parent b375b4ac
...@@ -98,3 +98,4 @@ Elsabe Ros <hello@elsabe.dev> ...@@ -98,3 +98,4 @@ Elsabe Ros <hello@elsabe.dev>
Nguyễn Phúc Lợi <nploi1998@gmail.com> Nguyễn Phúc Lợi <nploi1998@gmail.com>
Jingyi Chen <jingyichen@link.cuhk.edu.cn> Jingyi Chen <jingyichen@link.cuhk.edu.cn>
Junhua Lin <1075209054@qq.com> Junhua Lin <1075209054@qq.com>
Tomasz Gucio <tgucio@gmail.com>
...@@ -108,10 +108,13 @@ class TextSelectionPoint { ...@@ -108,10 +108,13 @@ class TextSelectionPoint {
/// false. Similarly the [moveNext] method moves the caret to the next line, and /// false. Similarly the [moveNext] method moves the caret to the next line, and
/// returns false if the caret is already on the last line. /// returns false if the caret is already on the last line.
/// ///
/// The [moveByOffset] method takes a pixel offset from the current position to move
/// the caret up or down.
///
/// If the underlying paragraph's layout changes, [isValid] becomes false and /// If the underlying paragraph's layout changes, [isValid] becomes false and
/// the [VerticalCaretMovementRun] must not be used. The [isValid] property must /// the [VerticalCaretMovementRun] must not be used. The [isValid] property must
/// be checked before calling [movePrevious] and [moveNext], or accessing /// be checked before calling [movePrevious], [moveNext] and [moveByOffset],
/// [current]. /// or accessing [current].
class VerticalCaretMovementRun extends Iterator<TextPosition> { class VerticalCaretMovementRun extends Iterator<TextPosition> {
VerticalCaretMovementRun._( VerticalCaretMovementRun._(
this._editable, this._editable,
...@@ -134,8 +137,8 @@ class VerticalCaretMovementRun extends Iterator<TextPosition> { ...@@ -134,8 +137,8 @@ class VerticalCaretMovementRun extends Iterator<TextPosition> {
/// A [VerticalCaretMovementRun] run is valid if the underlying text layout /// A [VerticalCaretMovementRun] run is valid if the underlying text layout
/// hasn't changed. /// hasn't changed.
/// ///
/// The [current] value and the [movePrevious] and [moveNext] methods must not /// The [current] value and the [movePrevious], [moveNext] and [moveByOffset]
/// be accessed when [isValid] is false. /// methods must not be accessed when [isValid] is false.
bool get isValid { bool get isValid {
if (!_isValid) { if (!_isValid) {
return false; return false;
...@@ -200,6 +203,30 @@ class VerticalCaretMovementRun extends Iterator<TextPosition> { ...@@ -200,6 +203,30 @@ class VerticalCaretMovementRun extends Iterator<TextPosition> {
_currentTextPosition = position.value; _currentTextPosition = position.value;
return true; return true;
} }
/// Move forward or backward by a number of elements determined
/// by pixel [offset].
///
/// If [offset] is negative, move backward; otherwise move forward.
///
/// Returns true and updates [current] if successful.
bool moveByOffset(double offset) {
final Offset initialOffset = _currentOffset;
if (offset >= 0.0) {
while (_currentOffset.dy < initialOffset.dy + offset) {
if (!moveNext()) {
break;
}
}
} else {
while (_currentOffset.dy > initialOffset.dy + offset) {
if (!movePrevious()) {
break;
}
}
}
return initialOffset != _currentOffset;
}
} }
/// Displays some text in a scrollable container with a potentially blinking /// Displays some text in a scrollable container with a potentially blinking
......
...@@ -171,13 +171,13 @@ class DefaultTextEditingShortcuts extends StatelessWidget { ...@@ -171,13 +171,13 @@ class DefaultTextEditingShortcuts extends StatelessWidget {
SingleActivator(LogicalKeyboardKey.delete, alt: true, shift: pressShift): const DeleteToLineBreakIntent(forward: true), SingleActivator(LogicalKeyboardKey.delete, alt: true, shift: pressShift): const DeleteToLineBreakIntent(forward: true),
}, },
// Arrow: Move Selection. // Arrow: Move selection.
const SingleActivator(LogicalKeyboardKey.arrowLeft): const ExtendSelectionByCharacterIntent(forward: false, collapseSelection: true), const SingleActivator(LogicalKeyboardKey.arrowLeft): const ExtendSelectionByCharacterIntent(forward: false, collapseSelection: true),
const SingleActivator(LogicalKeyboardKey.arrowRight): const ExtendSelectionByCharacterIntent(forward: true, collapseSelection: true), const SingleActivator(LogicalKeyboardKey.arrowRight): const ExtendSelectionByCharacterIntent(forward: true, collapseSelection: true),
const SingleActivator(LogicalKeyboardKey.arrowUp): const ExtendSelectionVerticallyToAdjacentLineIntent(forward: false, collapseSelection: true), const SingleActivator(LogicalKeyboardKey.arrowUp): const ExtendSelectionVerticallyToAdjacentLineIntent(forward: false, collapseSelection: true),
const SingleActivator(LogicalKeyboardKey.arrowDown): const ExtendSelectionVerticallyToAdjacentLineIntent(forward: true, collapseSelection: true), const SingleActivator(LogicalKeyboardKey.arrowDown): const ExtendSelectionVerticallyToAdjacentLineIntent(forward: true, collapseSelection: true),
// Shift + Arrow: Extend Selection. // Shift + Arrow: Extend selection.
const SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): const ExtendSelectionByCharacterIntent(forward: false, collapseSelection: false), const SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): const ExtendSelectionByCharacterIntent(forward: false, collapseSelection: false),
const SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): const ExtendSelectionByCharacterIntent(forward: true, collapseSelection: false), const SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): const ExtendSelectionByCharacterIntent(forward: true, collapseSelection: false),
const SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): const ExtendSelectionVerticallyToAdjacentLineIntent(forward: false, collapseSelection: false), const SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): const ExtendSelectionVerticallyToAdjacentLineIntent(forward: false, collapseSelection: false),
...@@ -199,6 +199,14 @@ class DefaultTextEditingShortcuts extends StatelessWidget { ...@@ -199,6 +199,14 @@ class DefaultTextEditingShortcuts extends StatelessWidget {
const SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, control: true): const ExtendSelectionToNextWordBoundaryIntent(forward: false, collapseSelection: false), const SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, control: true): const ExtendSelectionToNextWordBoundaryIntent(forward: false, collapseSelection: false),
const SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, control: true): const ExtendSelectionToNextWordBoundaryIntent(forward: true, collapseSelection: false), const SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, control: true): const ExtendSelectionToNextWordBoundaryIntent(forward: true, collapseSelection: false),
// Page Up / Down: Move selection by page.
const SingleActivator(LogicalKeyboardKey.pageUp): const ExtendSelectionVerticallyToAdjacentPageIntent(forward: false, collapseSelection: true),
const SingleActivator(LogicalKeyboardKey.pageDown): const ExtendSelectionVerticallyToAdjacentPageIntent(forward: true, collapseSelection: true),
// Shift + Page Up / Down: Extend selection by page.
const SingleActivator(LogicalKeyboardKey.pageUp, shift: true): const ExtendSelectionVerticallyToAdjacentPageIntent(forward: false, collapseSelection: false),
const SingleActivator(LogicalKeyboardKey.pageDown, shift: true): const ExtendSelectionVerticallyToAdjacentPageIntent(forward: true, collapseSelection: false),
const SingleActivator(LogicalKeyboardKey.keyX, control: true): const CopySelectionTextIntent.cut(SelectionChangedCause.keyboard), const SingleActivator(LogicalKeyboardKey.keyX, control: true): const CopySelectionTextIntent.cut(SelectionChangedCause.keyboard),
const SingleActivator(LogicalKeyboardKey.keyC, control: true): CopySelectionTextIntent.copy, const SingleActivator(LogicalKeyboardKey.keyC, control: true): CopySelectionTextIntent.copy,
const SingleActivator(LogicalKeyboardKey.keyV, control: true): const PasteTextIntent(SelectionChangedCause.keyboard), const SingleActivator(LogicalKeyboardKey.keyV, control: true): const PasteTextIntent(SelectionChangedCause.keyboard),
...@@ -258,10 +266,7 @@ class DefaultTextEditingShortcuts extends StatelessWidget { ...@@ -258,10 +266,7 @@ class DefaultTextEditingShortcuts extends StatelessWidget {
// macOS document shortcuts: https://support.apple.com/en-us/HT201236. // macOS document shortcuts: https://support.apple.com/en-us/HT201236.
// The macOS shortcuts uses different word/line modifiers than most other // The macOS shortcuts uses different word/line modifiers than most other
// platforms. // platforms.
static final Map<ShortcutActivator, Intent> _macShortcuts = _iOSShortcuts; static final Map<ShortcutActivator, Intent> _macShortcuts = <ShortcutActivator, Intent>{
// There is no complete documentation of iOS shortcuts.
static final Map<ShortcutActivator, Intent> _iOSShortcuts = <ShortcutActivator, Intent>{
for (final bool pressShift in const <bool>[true, false]) for (final bool pressShift in const <bool>[true, false])
...<SingleActivator, Intent>{ ...<SingleActivator, Intent>{
SingleActivator(LogicalKeyboardKey.backspace, shift: pressShift): const DeleteCharacterIntent(forward: false), SingleActivator(LogicalKeyboardKey.backspace, shift: pressShift): const DeleteCharacterIntent(forward: false),
...@@ -277,7 +282,7 @@ class DefaultTextEditingShortcuts extends StatelessWidget { ...@@ -277,7 +282,7 @@ class DefaultTextEditingShortcuts extends StatelessWidget {
const SingleActivator(LogicalKeyboardKey.arrowUp): const ExtendSelectionVerticallyToAdjacentLineIntent(forward: false, collapseSelection: true), const SingleActivator(LogicalKeyboardKey.arrowUp): const ExtendSelectionVerticallyToAdjacentLineIntent(forward: false, collapseSelection: true),
const SingleActivator(LogicalKeyboardKey.arrowDown): const ExtendSelectionVerticallyToAdjacentLineIntent(forward: true, collapseSelection: true), const SingleActivator(LogicalKeyboardKey.arrowDown): const ExtendSelectionVerticallyToAdjacentLineIntent(forward: true, collapseSelection: true),
// Shift + Arrow: Extend Selection. // Shift + Arrow: Extend selection.
const SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): const ExtendSelectionByCharacterIntent(forward: false, collapseSelection: false), const SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): const ExtendSelectionByCharacterIntent(forward: false, collapseSelection: false),
const SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): const ExtendSelectionByCharacterIntent(forward: true, collapseSelection: false), const SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): const ExtendSelectionByCharacterIntent(forward: true, collapseSelection: false),
const SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): const ExtendSelectionVerticallyToAdjacentLineIntent(forward: false, collapseSelection: false), const SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): const ExtendSelectionVerticallyToAdjacentLineIntent(forward: false, collapseSelection: false),
...@@ -310,6 +315,9 @@ class DefaultTextEditingShortcuts extends StatelessWidget { ...@@ -310,6 +315,9 @@ class DefaultTextEditingShortcuts extends StatelessWidget {
const SingleActivator(LogicalKeyboardKey.home, shift: true): const ExpandSelectionToDocumentBoundaryIntent(forward: false), const SingleActivator(LogicalKeyboardKey.home, shift: true): const ExpandSelectionToDocumentBoundaryIntent(forward: false),
const SingleActivator(LogicalKeyboardKey.end, shift: true): const ExpandSelectionToDocumentBoundaryIntent(forward: true), const SingleActivator(LogicalKeyboardKey.end, shift: true): const ExpandSelectionToDocumentBoundaryIntent(forward: true),
const SingleActivator(LogicalKeyboardKey.pageUp, shift: true): const ExtendSelectionVerticallyToAdjacentPageIntent(forward: false, collapseSelection: false),
const SingleActivator(LogicalKeyboardKey.pageDown, shift: true): const ExtendSelectionVerticallyToAdjacentPageIntent(forward: true, collapseSelection: false),
const SingleActivator(LogicalKeyboardKey.keyX, meta: true): const CopySelectionTextIntent.cut(SelectionChangedCause.keyboard), const SingleActivator(LogicalKeyboardKey.keyX, meta: true): const CopySelectionTextIntent.cut(SelectionChangedCause.keyboard),
const SingleActivator(LogicalKeyboardKey.keyC, meta: true): CopySelectionTextIntent.copy, const SingleActivator(LogicalKeyboardKey.keyC, meta: true): CopySelectionTextIntent.copy,
const SingleActivator(LogicalKeyboardKey.keyV, meta: true): const PasteTextIntent(SelectionChangedCause.keyboard), const SingleActivator(LogicalKeyboardKey.keyV, meta: true): const PasteTextIntent(SelectionChangedCause.keyboard),
...@@ -335,6 +343,8 @@ class DefaultTextEditingShortcuts extends StatelessWidget { ...@@ -335,6 +343,8 @@ class DefaultTextEditingShortcuts extends StatelessWidget {
// * Control + shift? + Z // * Control + shift? + Z
}; };
// There is no complete documentation of iOS shortcuts: use macOS ones.
static final Map<ShortcutActivator, Intent> _iOSShortcuts = _macShortcuts;
// The following key combinations have no effect on text editing on this // The following key combinations have no effect on text editing on this
// platform: // platform:
...@@ -350,6 +360,8 @@ class DefaultTextEditingShortcuts extends StatelessWidget { ...@@ -350,6 +360,8 @@ class DefaultTextEditingShortcuts extends StatelessWidget {
// * Meta + backspace // * Meta + backspace
static final Map<ShortcutActivator, Intent> _windowsShortcuts = <ShortcutActivator, Intent>{ static final Map<ShortcutActivator, Intent> _windowsShortcuts = <ShortcutActivator, Intent>{
..._commonShortcuts, ..._commonShortcuts,
const SingleActivator(LogicalKeyboardKey.pageUp): const ExtendSelectionVerticallyToAdjacentPageIntent(forward: false, collapseSelection: true),
const SingleActivator(LogicalKeyboardKey.pageDown): const ExtendSelectionVerticallyToAdjacentPageIntent(forward: true, collapseSelection: true),
const SingleActivator(LogicalKeyboardKey.home): const ExtendSelectionToLineBreakIntent(forward: false, collapseSelection: true, continuesAtWrap: true), 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.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.home, shift: true): const ExtendSelectionToLineBreakIntent(forward: false, collapseSelection: false, continuesAtWrap: true),
...@@ -385,7 +397,6 @@ class DefaultTextEditingShortcuts extends StatelessWidget { ...@@ -385,7 +397,6 @@ class DefaultTextEditingShortcuts extends StatelessWidget {
const SingleActivator(LogicalKeyboardKey.keyA, meta: true): const DoNothingAndStopPropagationTextIntent(), const SingleActivator(LogicalKeyboardKey.keyA, meta: true): const DoNothingAndStopPropagationTextIntent(),
}; };
static const Map<ShortcutActivator, Intent> _commonDisablingTextShortcuts = <ShortcutActivator, Intent>{ static const Map<ShortcutActivator, Intent> _commonDisablingTextShortcuts = <ShortcutActivator, Intent>{
SingleActivator(LogicalKeyboardKey.arrowDown, alt: true): DoNothingAndStopPropagationTextIntent(), SingleActivator(LogicalKeyboardKey.arrowDown, alt: true): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true): DoNothingAndStopPropagationTextIntent(), SingleActivator(LogicalKeyboardKey.arrowLeft, alt: true): DoNothingAndStopPropagationTextIntent(),
...@@ -407,6 +418,8 @@ class DefaultTextEditingShortcuts extends StatelessWidget { ...@@ -407,6 +418,8 @@ class DefaultTextEditingShortcuts extends StatelessWidget {
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): DoNothingAndStopPropagationTextIntent(), SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): DoNothingAndStopPropagationTextIntent(), SingleActivator(LogicalKeyboardKey.arrowRight, shift: true): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): DoNothingAndStopPropagationTextIntent(), SingleActivator(LogicalKeyboardKey.arrowUp, shift: true): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.pageUp, shift: true): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.pageDown, shift: true): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.end, shift: true): DoNothingAndStopPropagationTextIntent(), SingleActivator(LogicalKeyboardKey.end, shift: true): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.home, shift: true): DoNothingAndStopPropagationTextIntent(), SingleActivator(LogicalKeyboardKey.home, shift: true): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowDown): DoNothingAndStopPropagationTextIntent(), SingleActivator(LogicalKeyboardKey.arrowDown): DoNothingAndStopPropagationTextIntent(),
...@@ -417,6 +430,8 @@ class DefaultTextEditingShortcuts extends StatelessWidget { ...@@ -417,6 +430,8 @@ class DefaultTextEditingShortcuts extends StatelessWidget {
SingleActivator(LogicalKeyboardKey.arrowRight, control: true): DoNothingAndStopPropagationTextIntent(), SingleActivator(LogicalKeyboardKey.arrowRight, control: true): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, control: true): DoNothingAndStopPropagationTextIntent(), SingleActivator(LogicalKeyboardKey.arrowLeft, shift: true, control: true): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, control: true): DoNothingAndStopPropagationTextIntent(), SingleActivator(LogicalKeyboardKey.arrowRight, shift: true, control: true): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.pageUp): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.pageDown): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.end): DoNothingAndStopPropagationTextIntent(), SingleActivator(LogicalKeyboardKey.end): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.home): DoNothingAndStopPropagationTextIntent(), SingleActivator(LogicalKeyboardKey.home): DoNothingAndStopPropagationTextIntent(),
SingleActivator(LogicalKeyboardKey.end, control: true): DoNothingAndStopPropagationTextIntent(), SingleActivator(LogicalKeyboardKey.end, control: true): DoNothingAndStopPropagationTextIntent(),
...@@ -545,8 +560,8 @@ Intent? intentForMacOSSelector(String selectorName) { ...@@ -545,8 +560,8 @@ Intent? intentForMacOSSelector(String selectorName) {
// TODO(knopp): Page Up/Down intents are missing (https://github.com/flutter/flutter/pull/105497) // TODO(knopp): Page Up/Down intents are missing (https://github.com/flutter/flutter/pull/105497)
'scrollPageUp:': ScrollToDocumentBoundaryIntent(forward: false), 'scrollPageUp:': ScrollToDocumentBoundaryIntent(forward: false),
'scrollPageDown:': ScrollToDocumentBoundaryIntent(forward: true), 'scrollPageDown:': ScrollToDocumentBoundaryIntent(forward: true),
'pageUpAndModifySelection': ExpandSelectionToDocumentBoundaryIntent(forward: false), 'pageUpAndModifySelection:': ExtendSelectionVerticallyToAdjacentPageIntent(forward: false, collapseSelection: false),
'pageDownAndModifySelection:': ExpandSelectionToDocumentBoundaryIntent(forward: true), 'pageDownAndModifySelection:': ExtendSelectionVerticallyToAdjacentPageIntent(forward: true, collapseSelection: false),
// Escape key when there's no IME selection popup. // Escape key when there's no IME selection popup.
'cancelOperation:': DismissIntent(), 'cancelOperation:': DismissIntent(),
......
...@@ -479,6 +479,7 @@ class _DiscreteKeyFrameSimulation extends Simulation { ...@@ -479,6 +479,7 @@ class _DiscreteKeyFrameSimulation extends Simulation {
/// | [ExtendSelectionToNextWordBoundaryOrCaretLocationIntent](`collapseSelection: true`) | Collapses the selection to the word boundary before/after the selection's [TextSelection.extent] position, or [TextSelection.base], whichever is closest in the given direction | Moves the caret to the previous/next word boundary. | /// | [ExtendSelectionToNextWordBoundaryOrCaretLocationIntent](`collapseSelection: true`) | Collapses the selection to the word boundary before/after the selection's [TextSelection.extent] position, or [TextSelection.base], whichever is closest in the given direction | Moves the caret to the previous/next word boundary. |
/// | [ExtendSelectionToLineBreakIntent](`collapseSelection: true`) | Collapses the selection to the start/end of the line at the selection's [TextSelection.extent] position | Moves the caret to the start/end of the current line .| /// | [ExtendSelectionToLineBreakIntent](`collapseSelection: true`) | Collapses the selection to the start/end of the line at the selection's [TextSelection.extent] position | Moves the caret to the start/end of the current line .|
/// | [ExtendSelectionVerticallyToAdjacentLineIntent](`collapseSelection: true`) | Collapses the selection to the position closest to the selection's [TextSelection.extent], on the previous/next adjacent line | Moves the caret to the closest position on the previous/next adjacent line. | /// | [ExtendSelectionVerticallyToAdjacentLineIntent](`collapseSelection: true`) | Collapses the selection to the position closest to the selection's [TextSelection.extent], on the previous/next adjacent line | Moves the caret to the closest position on the previous/next adjacent line. |
/// | [ExtendSelectionVerticallyToAdjacentPageIntent](`collapseSelection: true`) | Collapses the selection to the position closest to the selection's [TextSelection.extent], on the previous/next adjacent page | Moves the caret to the closest position on the previous/next adjacent page. |
/// | [ExtendSelectionToDocumentBoundaryIntent](`collapseSelection: true`) | Collapses the selection to the start/end of the document | Moves the caret to the start/end of the document. | /// | [ExtendSelectionToDocumentBoundaryIntent](`collapseSelection: true`) | Collapses the selection to the start/end of the document | Moves the caret to the start/end of the document. |
/// ///
/// #### Intents for Extending the Selection /// #### Intents for Extending the Selection
...@@ -490,6 +491,7 @@ class _DiscreteKeyFrameSimulation extends Simulation { ...@@ -490,6 +491,7 @@ class _DiscreteKeyFrameSimulation extends Simulation {
/// | [ExtendSelectionToNextWordBoundaryOrCaretLocationIntent](`collapseSelection: false`) | Moves the selection's [TextSelection.extent] to the previous/next word boundary, or [TextSelection.base] whichever is closest in the given direction | Moves the selection's [TextSelection.extent] to the previous/next word boundary. | /// | [ExtendSelectionToNextWordBoundaryOrCaretLocationIntent](`collapseSelection: false`) | Moves the selection's [TextSelection.extent] to the previous/next word boundary, or [TextSelection.base] whichever is closest in the given direction | Moves the selection's [TextSelection.extent] to the previous/next word boundary. |
/// | [ExtendSelectionToLineBreakIntent](`collapseSelection: false`) | Moves the selection's [TextSelection.extent] to the start/end of the line | /// | [ExtendSelectionToLineBreakIntent](`collapseSelection: false`) | Moves the selection's [TextSelection.extent] to the start/end of the line |
/// | [ExtendSelectionVerticallyToAdjacentLineIntent](`collapseSelection: false`) | Moves the selection's [TextSelection.extent] to the closest position on the previous/next adjacent line | /// | [ExtendSelectionVerticallyToAdjacentLineIntent](`collapseSelection: false`) | Moves the selection's [TextSelection.extent] to the closest position on the previous/next adjacent line |
/// | [ExtendSelectionVerticallyToAdjacentPageIntent](`collapseSelection: false`) | Moves the selection's [TextSelection.extent] to the closest position on the previous/next adjacent page |
/// | [ExtendSelectionToDocumentBoundaryIntent](`collapseSelection: false`) | Moves the selection's [TextSelection.extent] to the start/end of the document | /// | [ExtendSelectionToDocumentBoundaryIntent](`collapseSelection: false`) | Moves the selection's [TextSelection.extent] to the start/end of the document |
/// | [SelectAllTextIntent] | Selects the entire document | /// | [SelectAllTextIntent] | Selects the entire document |
/// ///
...@@ -3106,7 +3108,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -3106,7 +3108,7 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
// TODO(abarth): Teach RenderEditable about ValueNotifier<TextEditingValue> // TODO(abarth): Teach RenderEditable about ValueNotifier<TextEditingValue>
// to avoid this setState(). // to avoid this setState().
setState(() { /* We use widget.controller.value in build(). */ }); setState(() { /* We use widget.controller.value in build(). */ });
_adjacentLineAction.stopCurrentVerticalRunIfSelectionChanges(); _verticalSelectionUpdateAction.stopCurrentVerticalRunIfSelectionChanges();
} }
void _handleFocusChanged() { void _handleFocusChanged() {
...@@ -3589,7 +3591,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -3589,7 +3591,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
} }
late final Action<UpdateSelectionIntent> _updateSelectionAction = CallbackAction<UpdateSelectionIntent>(onInvoke: _updateSelection); late final Action<UpdateSelectionIntent> _updateSelectionAction = CallbackAction<UpdateSelectionIntent>(onInvoke: _updateSelection);
late final _UpdateTextSelectionToAdjacentLineAction<ExtendSelectionVerticallyToAdjacentLineIntent> _adjacentLineAction = _UpdateTextSelectionToAdjacentLineAction<ExtendSelectionVerticallyToAdjacentLineIntent>(this); late final _UpdateTextSelectionVerticallyAction<DirectionalCaretMovementIntent> _verticalSelectionUpdateAction =
_UpdateTextSelectionVerticallyAction<DirectionalCaretMovementIntent>(this);
void _expandSelectionToDocumentBoundary(ExpandSelectionToDocumentBoundaryIntent intent) { void _expandSelectionToDocumentBoundary(ExpandSelectionToDocumentBoundaryIntent intent) {
final TextBoundary textBoundary = _documentBoundary(intent); final TextBoundary textBoundary = _documentBoundary(intent);
...@@ -3717,7 +3720,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien ...@@ -3717,7 +3720,8 @@ class EditableTextState extends State<EditableText> with AutomaticKeepAliveClien
ExtendSelectionToLineBreakIntent: _makeOverridable(_UpdateTextSelectionAction<ExtendSelectionToLineBreakIntent>(this, true, _linebreak)), ExtendSelectionToLineBreakIntent: _makeOverridable(_UpdateTextSelectionAction<ExtendSelectionToLineBreakIntent>(this, true, _linebreak)),
ExpandSelectionToLineBreakIntent: _makeOverridable(CallbackAction<ExpandSelectionToLineBreakIntent>(onInvoke: _expandSelectionToLinebreak)), ExpandSelectionToLineBreakIntent: _makeOverridable(CallbackAction<ExpandSelectionToLineBreakIntent>(onInvoke: _expandSelectionToLinebreak)),
ExpandSelectionToDocumentBoundaryIntent: _makeOverridable(CallbackAction<ExpandSelectionToDocumentBoundaryIntent>(onInvoke: _expandSelectionToDocumentBoundary)), ExpandSelectionToDocumentBoundaryIntent: _makeOverridable(CallbackAction<ExpandSelectionToDocumentBoundaryIntent>(onInvoke: _expandSelectionToDocumentBoundary)),
ExtendSelectionVerticallyToAdjacentLineIntent: _makeOverridable(_adjacentLineAction), ExtendSelectionVerticallyToAdjacentLineIntent: _makeOverridable(_verticalSelectionUpdateAction),
ExtendSelectionVerticallyToAdjacentPageIntent: _makeOverridable(_verticalSelectionUpdateAction),
ExtendSelectionToDocumentBoundaryIntent: _makeOverridable(_UpdateTextSelectionAction<ExtendSelectionToDocumentBoundaryIntent>(this, true, _documentBoundary)), ExtendSelectionToDocumentBoundaryIntent: _makeOverridable(_UpdateTextSelectionAction<ExtendSelectionToDocumentBoundaryIntent>(this, true, _documentBoundary)),
ExtendSelectionToNextWordBoundaryOrCaretLocationIntent: _makeOverridable(_ExtendSelectionOrCaretPositionAction(this, _nextWordBoundary)), ExtendSelectionToNextWordBoundaryOrCaretLocationIntent: _makeOverridable(_ExtendSelectionOrCaretPositionAction(this, _nextWordBoundary)),
ScrollToDocumentBoundaryIntent: _makeOverridable(CallbackAction<ScrollToDocumentBoundaryIntent>(onInvoke: _scrollToDocumentBoundary)), ScrollToDocumentBoundaryIntent: _makeOverridable(CallbackAction<ScrollToDocumentBoundaryIntent>(onInvoke: _scrollToDocumentBoundary)),
...@@ -4604,8 +4608,8 @@ class _ExtendSelectionOrCaretPositionAction extends ContextAction<ExtendSelectio ...@@ -4604,8 +4608,8 @@ class _ExtendSelectionOrCaretPositionAction extends ContextAction<ExtendSelectio
bool get isActionEnabled => state.widget.selectionEnabled && state._value.selection.isValid; bool get isActionEnabled => state.widget.selectionEnabled && state._value.selection.isValid;
} }
class _UpdateTextSelectionToAdjacentLineAction<T extends DirectionalCaretMovementIntent> extends ContextAction<T> { class _UpdateTextSelectionVerticallyAction<T extends DirectionalCaretMovementIntent> extends ContextAction<T> {
_UpdateTextSelectionToAdjacentLineAction(this.state); _UpdateTextSelectionVerticallyAction(this.state);
final EditableTextState state; final EditableTextState state;
...@@ -4647,10 +4651,12 @@ class _UpdateTextSelectionToAdjacentLineAction<T extends DirectionalCaretMovemen ...@@ -4647,10 +4651,12 @@ class _UpdateTextSelectionToAdjacentLineAction<T extends DirectionalCaretMovemen
final VerticalCaretMovementRun currentRun = _verticalMovementRun final VerticalCaretMovementRun currentRun = _verticalMovementRun
?? state.renderEditable.startVerticalCaretMovement(state.renderEditable.selection!.extent); ?? state.renderEditable.startVerticalCaretMovement(state.renderEditable.selection!.extent);
final bool shouldMove = intent.forward ? currentRun.moveNext() : currentRun.movePrevious(); final bool shouldMove = intent is ExtendSelectionVerticallyToAdjacentPageIntent
? currentRun.moveByOffset((intent.forward ? 1.0 : -1.0) * state.renderEditable.size.height)
: intent.forward ? currentRun.moveNext() : currentRun.movePrevious();
final TextPosition newExtent = shouldMove final TextPosition newExtent = shouldMove
? currentRun.current ? currentRun.current
: (intent.forward ? TextPosition(offset: state._value.text.length) : const TextPosition(offset: 0)); : intent.forward ? TextPosition(offset: state._value.text.length) : const TextPosition(offset: 0);
final TextSelection newSelection = collapseSelection final TextSelection newSelection = collapseSelection
? TextSelection.fromPosition(newExtent) ? TextSelection.fromPosition(newExtent)
: value.selection.extendTo(newExtent); : value.selection.extendTo(newExtent);
......
...@@ -210,6 +210,17 @@ class ExtendSelectionVerticallyToAdjacentLineIntent extends DirectionalCaretMove ...@@ -210,6 +210,17 @@ class ExtendSelectionVerticallyToAdjacentLineIntent extends DirectionalCaretMove
}) : super(forward, collapseSelection); }) : super(forward, collapseSelection);
} }
/// Expands, or moves the current selection from the current
/// [TextSelection.extent] position to the closest position on the adjacent
/// page.
class ExtendSelectionVerticallyToAdjacentPageIntent extends DirectionalCaretMovementIntent {
/// Creates an [ExtendSelectionVerticallyToAdjacentPageIntent].
const ExtendSelectionVerticallyToAdjacentPageIntent({
required bool forward,
required bool collapseSelection,
}) : super(forward, collapseSelection);
}
/// Extends, or moves the current selection from the current /// Extends, or moves the current selection from the current
/// [TextSelection.extent] position to the start or the end of the document. /// [TextSelection.extent] position to the start or the end of the document.
/// ///
......
...@@ -121,6 +121,8 @@ void main() { ...@@ -121,6 +121,8 @@ void main() {
LogicalKeyboardKey.arrowRight, LogicalKeyboardKey.arrowRight,
LogicalKeyboardKey.arrowUp, LogicalKeyboardKey.arrowUp,
LogicalKeyboardKey.arrowDown, LogicalKeyboardKey.arrowDown,
LogicalKeyboardKey.pageUp,
LogicalKeyboardKey.pageDown,
LogicalKeyboardKey.home, LogicalKeyboardKey.home,
LogicalKeyboardKey.end, LogicalKeyboardKey.end,
]; ];
...@@ -1447,6 +1449,18 @@ void main() { ...@@ -1447,6 +1449,18 @@ void main() {
reason: activator.toString(), reason: activator.toString(),
); );
} }
for (final SingleActivator activator in allModifierVariants(LogicalKeyboardKey.pageUp)) {
await sendKeyCombination(tester, activator);
await tester.pump();
expect(controller.text, testText);
expect(
controller.selection,
const TextSelection.collapsed(offset: 0),
reason: activator.toString(),
);
}
}, variant: TargetPlatformVariant.all()); }, variant: TargetPlatformVariant.all());
testWidgets('at end', (WidgetTester tester) async { testWidgets('at end', (WidgetTester tester) async {
...@@ -1465,6 +1479,15 @@ void main() { ...@@ -1465,6 +1479,15 @@ void main() {
expect(controller.selection.baseOffset, 72, reason: activator.toString()); expect(controller.selection.baseOffset, 72, reason: activator.toString());
expect(controller.selection.extentOffset, 72, reason: activator.toString()); expect(controller.selection.extentOffset, 72, reason: activator.toString());
} }
for (final SingleActivator activator in allModifierVariants(LogicalKeyboardKey.pageDown)) {
await sendKeyCombination(tester, activator);
await tester.pump();
expect(controller.text, testText);
expect(controller.selection.baseOffset, 72, reason: activator.toString());
expect(controller.selection.extentOffset, 72, reason: activator.toString());
}
}, variant: TargetPlatformVariant.all()); }, variant: TargetPlatformVariant.all());
testWidgets('run', (WidgetTester tester) async { testWidgets('run', (WidgetTester tester) async {
...@@ -1554,6 +1577,41 @@ void main() { ...@@ -1554,6 +1577,41 @@ void main() {
)); ));
}, variant: TargetPlatformVariant.all()); }, variant: TargetPlatformVariant.all());
testWidgets('run with page down/up', (WidgetTester tester) async {
controller.text =
'aa\n' // 3
'a\n' // 3 + 2 = 5
'aa\n' // 5 + 3 = 8
'aaa\n' // 8 + 4 = 12
'${"aaa\n" * 50}'
'aaaa';
controller.selection = const TextSelection.collapsed(offset: 2);
await tester.pumpWidget(buildEditableText());
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowDown));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 4,
affinity: TextAffinity.upstream,
));
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.pageDown));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(offset: 82));
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.arrowUp));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(offset: 78));
await sendKeyCombination(tester, const SingleActivator(LogicalKeyboardKey.pageUp));
await tester.pump();
expect(controller.selection, const TextSelection.collapsed(
offset: 2,
affinity: TextAffinity.upstream,
));
}, variant: TargetPlatformVariant.all(excluding: <TargetPlatform>{TargetPlatform.iOS, TargetPlatform.macOS})); // intended: on macOS Page Up/Down only scrolls
testWidgets('run can be interrupted by layout changes', (WidgetTester tester) async { testWidgets('run can be interrupted by layout changes', (WidgetTester tester) async {
controller.text = controller.text =
'aa\n' // 3 'aa\n' // 3
......
...@@ -6164,6 +6164,88 @@ void main() { ...@@ -6164,6 +6164,88 @@ void main() {
reason: 'on $platform', reason: 'on $platform',
); );
// Move down by page.
await sendKeys(
tester,
<LogicalKeyboardKey>[
LogicalKeyboardKey.pageDown,
],
targetPlatform: defaultTargetPlatform,
);
// On macOS, pageDown/Up don't change selection.
expect(
selection,
equals(
defaultTargetPlatform == TargetPlatform.macOS
|| defaultTargetPlatform == TargetPlatform.iOS
? const TextSelection.collapsed(offset: 0)
: const TextSelection.collapsed(offset: 55),
),
reason: 'on $platform',
);
// Move up by page (to start).
await sendKeys(
tester,
<LogicalKeyboardKey>[
LogicalKeyboardKey.pageUp,
],
targetPlatform: defaultTargetPlatform,
);
expect(
selection,
equals(
const TextSelection.collapsed(
offset: 0,
),
),
reason: 'on $platform',
);
// Select towards end by page.
await sendKeys(
tester,
<LogicalKeyboardKey>[
LogicalKeyboardKey.pageDown,
],
shift: true,
targetPlatform: defaultTargetPlatform,
);
expect(
selection,
equals(
const TextSelection(
baseOffset: 0,
extentOffset: 55,
affinity: TextAffinity.upstream,
),
),
reason: 'on $platform',
);
// Change selection extent towards start by page.
await sendKeys(
tester,
<LogicalKeyboardKey>[
LogicalKeyboardKey.pageUp,
],
shift: true,
targetPlatform: defaultTargetPlatform,
);
expect(
selection,
equals(
const TextSelection.collapsed(
offset: 0,
),
),
reason: 'on $platform',
);
// Jump forward three words. // Jump forward three words.
await sendKeys( await sendKeys(
tester, tester,
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment