Unverified Commit e56a73e4 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Add support for by word movement granularities to semantics (#19949)

parent 5cd97f00
......@@ -423,10 +423,16 @@ class RenderEditable extends RenderBox {
if (_selection?.isValid == true) {
config.textSelection = _selection;
if (_textPainter.getOffsetBefore(_selection.extentOffset) != null)
config.onMoveCursorBackwardByCharacter = _handleMoveCursorBackwardByCharacter;
if (_textPainter.getOffsetAfter(_selection.extentOffset) != null)
config.onMoveCursorForwardByCharacter = _handleMoveCursorForwardByCharacter;
if (_textPainter.getOffsetBefore(_selection.extentOffset) != null) {
config
..onMoveCursorBackwardByWord = _handleMoveCursorBackwardByWord
..onMoveCursorBackwardByCharacter = _handleMoveCursorBackwardByCharacter;
}
if (_textPainter.getOffsetAfter(_selection.extentOffset) != null) {
config
..onMoveCursorForwardByWord = _handleMoveCursorForwardByWord
..onMoveCursorForwardByCharacter = _handleMoveCursorForwardByCharacter;
}
}
}
......@@ -454,6 +460,109 @@ class RenderEditable extends RenderBox {
);
}
void _handleMoveCursorForwardByWord(bool extentSelection) {
final TextRange currentWord = _textPainter.getWordBoundary(_selection.extent);
if (currentWord == null)
return;
final TextRange nextWord = _getNextWord(currentWord.end);
if (nextWord == null)
return;
final int baseOffset = extentSelection ? _selection.baseOffset : nextWord.start;
onSelectionChanged(
new TextSelection(
baseOffset: baseOffset,
extentOffset: nextWord.start,
),
this,
SelectionChangedCause.keyboard,
);
}
void _handleMoveCursorBackwardByWord(bool extentSelection) {
final TextRange currentWord = _textPainter.getWordBoundary(_selection.extent);
if (currentWord == null)
return;
final TextRange previousWord = _getPreviousWord(currentWord.start - 1);
if (previousWord == null)
return;
final int baseOffset = extentSelection ? _selection.baseOffset : previousWord.start;
onSelectionChanged(
new TextSelection(
baseOffset: baseOffset,
extentOffset: previousWord.start,
),
this,
SelectionChangedCause.keyboard,
);
}
TextRange _getNextWord(int offset) {
while (true) {
final TextRange range = _textPainter.getWordBoundary(new TextPosition(offset: offset));
if (range == null || !range.isValid || range.isCollapsed)
return null;
if (!_onlyWhitespace(range))
return range;
offset = range.end;
}
}
TextRange _getPreviousWord(int offset) {
while (offset >= 0) {
final TextRange range = _textPainter.getWordBoundary(new TextPosition(offset: offset));
if (range == null || !range.isValid || range.isCollapsed)
return null;
if (!_onlyWhitespace(range))
return range;
offset = range.start - 1;
}
return null;
}
// Check if the given text range only contains white space or separator
// characters.
//
// newline characters from ascii and separators from the
// [unicode separator category](https://www.compart.com/en/unicode/category/Zs)
// TODO(jonahwilliams): replace when we expose this ICU information.
bool _onlyWhitespace(TextRange range) {
for (int i = range.start; i < range.end; i++) {
final int codeUnit = text.codeUnitAt(i);
switch (codeUnit) {
case 0x9: // horizontal tab
case 0xA: // line feed
case 0xB: // vertical tab
case 0xC: // form feed
case 0xD: // carriage return
case 0x1C: // file separator
case 0x1D: // group separator
case 0x1E: // record separator
case 0x1F: // unit separator
case 0x20: // space
case 0xA0: // no-break space
case 0x1680: // ogham space mark
case 0x2000: // en quad
case 0x2001: // em quad
case 0x2002: // en space
case 0x2003: // em space
case 0x2004: // three-per-em space
case 0x2005: // four-er-em space
case 0x2006: // six-per-em space
case 0x2007: // figure space
case 0x2008: // punctuation space
case 0x2009: // thin space
case 0x200A: // hair space
case 0x202F: // narrow no-break space
case 0x205F: // medium mathematical space
case 0x3000: // ideographic space
break;
default:
return false;
}
}
return true;
}
@override
void attach(PipelineOwner owner) {
super.attach(owner);
......
......@@ -3266,6 +3266,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
VoidCallback onPaste,
MoveCursorHandler onMoveCursorForwardByCharacter,
MoveCursorHandler onMoveCursorBackwardByCharacter,
MoveCursorHandler onMoveCursorForwardByWord,
MoveCursorHandler onMoveCursorBackwardByWord,
SetSelectionHandler onSetSelection,
VoidCallback onDidGainAccessibilityFocus,
VoidCallback onDidLoseAccessibilityFocus,
......@@ -3311,6 +3313,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
_onPaste = onPaste,
_onMoveCursorForwardByCharacter = onMoveCursorForwardByCharacter,
_onMoveCursorBackwardByCharacter = onMoveCursorBackwardByCharacter,
_onMoveCursorForwardByWord = onMoveCursorForwardByWord,
_onMoveCursorBackwardByWord = onMoveCursorBackwardByWord,
_onSetSelection = onSetSelection,
_onDidGainAccessibilityFocus = onDidGainAccessibilityFocus,
_onDidLoseAccessibilityFocus = onDidLoseAccessibilityFocus,
......@@ -3899,6 +3903,42 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
markNeedsSemanticsUpdate();
}
/// The handler for [SemanticsAction.onMoveCursorForwardByWord].
///
/// This handler is invoked when the user wants to move the cursor in a
/// text field backward by one character.
///
/// TalkBack users can trigger this by pressing the volume down key while the
/// input focus is in a text field.
MoveCursorHandler get onMoveCursorForwardByWord => _onMoveCursorForwardByWord;
MoveCursorHandler _onMoveCursorForwardByWord;
set onMoveCursorForwardByWord(MoveCursorHandler handler) {
if (_onMoveCursorForwardByWord == handler)
return;
final bool hadValue = _onMoveCursorForwardByWord != null;
_onMoveCursorForwardByWord = handler;
if ((handler != null) != hadValue)
markNeedsSemanticsUpdate();
}
/// The handler for [SemanticsAction.onMoveCursorBackwardByWord].
///
/// This handler is invoked when the user wants to move the cursor in a
/// text field backward by one character.
///
/// TalkBack users can trigger this by pressing the volume down key while the
/// input focus is in a text field.
MoveCursorHandler get onMoveCursorBackwardByWord => _onMoveCursorBackwardByWord;
MoveCursorHandler _onMoveCursorBackwardByWord;
set onMoveCursorBackwardByWord(MoveCursorHandler handler) {
if (_onMoveCursorBackwardByWord == handler)
return;
final bool hadValue = _onMoveCursorBackwardByWord != null;
_onMoveCursorBackwardByWord = handler;
if ((handler != null) != hadValue)
markNeedsSemanticsUpdate();
}
/// The handler for [SemanticsAction.setSelection].
///
/// This handler is invoked when the user either wants to change the currently
......@@ -4086,6 +4126,10 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
config.onMoveCursorForwardByCharacter = _performMoveCursorForwardByCharacter;
if (onMoveCursorBackwardByCharacter != null)
config.onMoveCursorBackwardByCharacter = _performMoveCursorBackwardByCharacter;
if (onMoveCursorForwardByWord != null)
config.onMoveCursorForwardByWord = _performMoveCursorForwardByWord;
if (onMoveCursorBackwardByWord != null)
config.onMoveCursorBackwardByWord = _performMoveCursorBackwardByWord;
if (onSetSelection != null)
config.onSetSelection = _performSetSelection;
if (onDidGainAccessibilityFocus != null)
......@@ -4166,6 +4210,16 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
onMoveCursorBackwardByCharacter(extendSelection);
}
void _performMoveCursorForwardByWord(bool extendSelection) {
if (onMoveCursorForwardByWord != null)
onMoveCursorForwardByWord(extendSelection);
}
void _performMoveCursorBackwardByWord(bool extendSelection) {
if (onMoveCursorBackwardByWord != null)
onMoveCursorBackwardByWord(extendSelection);
}
void _performSetSelection(TextSelection selection) {
if (onSetSelection != null)
onSetSelection(selection);
......
......@@ -534,6 +534,8 @@ class SemanticsProperties extends DiagnosticableTree {
this.onPaste,
this.onMoveCursorForwardByCharacter,
this.onMoveCursorBackwardByCharacter,
this.onMoveCursorForwardByWord,
this.onMoveCursorBackwardByWord,
this.onSetSelection,
this.onDidGainAccessibilityFocus,
this.onDidLoseAccessibilityFocus,
......@@ -904,6 +906,24 @@ class SemanticsProperties extends DiagnosticableTree {
/// input focus is in a text field.
final MoveCursorHandler onMoveCursorBackwardByCharacter;
/// The handler for [SemanticsAction.onMoveCursorForwardByWord].
///
/// This handler is invoked when the user wants to move the cursor in a
/// text field backward by one word.
///
/// TalkBack users can trigger this by pressing the volume down key while the
/// input focus is in a text field.
final MoveCursorHandler onMoveCursorForwardByWord;
/// The handler for [SemanticsAction.onMoveCursorBackwardByWord].
///
/// This handler is invoked when the user wants to move the cursor in a
/// text field backward by one word.
///
/// TalkBack users can trigger this by pressing the volume down key while the
/// input focus is in a text field.
final MoveCursorHandler onMoveCursorBackwardByWord;
/// The handler for [SemanticsAction.setSelection].
///
/// This handler is invoked when the user either wants to change the currently
......@@ -2730,6 +2750,44 @@ class SemanticsConfiguration {
_onMoveCursorBackwardByCharacter = value;
}
/// The handler for [SemanticsAction.onMoveCursorForwardByWord].
///
/// This handler is invoked when the user wants to move the cursor in a
/// text field backward by one word.
///
/// TalkBack users can trigger this by pressing the volume down key while the
/// input focus is in a text field.
MoveCursorHandler get onMoveCursorForwardByWord => _onMoveCursorForwardByWord;
MoveCursorHandler _onMoveCursorForwardByWord;
set onMoveCursorForwardByWord(MoveCursorHandler value) {
assert(value != null);
_addAction(SemanticsAction.moveCursorForwardByWord, (dynamic args) {
final bool extentSelection = args;
assert(extentSelection != null);
value(extentSelection);
});
_onMoveCursorForwardByCharacter = value;
}
/// The handler for [SemanticsAction.onMoveCursorBackwardByWord].
///
/// This handler is invoked when the user wants to move the cursor in a
/// text field backward by one word.
///
/// TalkBack users can trigger this by pressing the volume down key while the
/// input focus is in a text field.
MoveCursorHandler get onMoveCursorBackwardByWord => _onMoveCursorBackwardByWord;
MoveCursorHandler _onMoveCursorBackwardByWord;
set onMoveCursorBackwardByWord(MoveCursorHandler value) {
assert(value != null);
_addAction(SemanticsAction.moveCursorBackwardByWord, (dynamic args) {
final bool extentSelection = args;
assert(extentSelection != null);
value(extentSelection);
});
_onMoveCursorBackwardByCharacter = value;
}
/// The handler for [SemanticsAction.setSelection].
///
/// This handler is invoked when the user either wants to change the currently
......
......@@ -5335,6 +5335,8 @@ class Semantics extends SingleChildRenderObjectWidget {
onPaste: properties.onPaste,
onMoveCursorForwardByCharacter: properties.onMoveCursorForwardByCharacter,
onMoveCursorBackwardByCharacter: properties.onMoveCursorBackwardByCharacter,
onMoveCursorForwardByWord: properties.onMoveCursorForwardByWord,
onMoveCursorBackwardByWord: properties.onMoveCursorBackwardByWord,
onSetSelection: properties.onSetSelection,
onDidGainAccessibilityFocus: properties.onDidGainAccessibilityFocus,
onDidLoseAccessibilityFocus: properties.onDidLoseAccessibilityFocus,
......@@ -5397,6 +5399,8 @@ class Semantics extends SingleChildRenderObjectWidget {
..onPaste = properties.onPaste
..onMoveCursorForwardByCharacter = properties.onMoveCursorForwardByCharacter
..onMoveCursorBackwardByCharacter = properties.onMoveCursorForwardByCharacter
..onMoveCursorForwardByWord = properties.onMoveCursorForwardByWord
..onMoveCursorBackwardByWord = properties.onMoveCursorBackwardByWord
..onSetSelection = properties.onSetSelection
..onDidGainAccessibilityFocus = properties.onDidGainAccessibilityFocus
..onDidLoseAccessibilityFocus = properties.onDidLoseAccessibilityFocus
......
......@@ -1902,6 +1902,7 @@ void main() {
actions: <SemanticsAction>[
SemanticsAction.tap,
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.moveCursorBackwardByWord,
SemanticsAction.setSelection,
SemanticsAction.paste,
],
......@@ -1927,6 +1928,8 @@ void main() {
SemanticsAction.tap,
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.moveCursorForwardByCharacter,
SemanticsAction.moveCursorBackwardByWord,
SemanticsAction.moveCursorForwardByWord,
SemanticsAction.setSelection,
SemanticsAction.paste,
],
......@@ -1952,6 +1955,7 @@ void main() {
actions: <SemanticsAction>[
SemanticsAction.tap,
SemanticsAction.moveCursorForwardByCharacter,
SemanticsAction.moveCursorForwardByWord,
SemanticsAction.setSelection,
SemanticsAction.paste,
],
......@@ -2011,6 +2015,7 @@ void main() {
actions: <SemanticsAction>[
SemanticsAction.tap,
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.moveCursorBackwardByWord,
SemanticsAction.setSelection,
SemanticsAction.paste,
],
......@@ -2036,6 +2041,8 @@ void main() {
SemanticsAction.tap,
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.moveCursorForwardByCharacter,
SemanticsAction.moveCursorBackwardByWord,
SemanticsAction.moveCursorForwardByWord,
SemanticsAction.setSelection,
SemanticsAction.paste,
SemanticsAction.cut,
......@@ -2085,6 +2092,7 @@ void main() {
actions: <SemanticsAction>[
SemanticsAction.tap,
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.moveCursorBackwardByWord,
SemanticsAction.setSelection,
SemanticsAction.paste,
],
......@@ -2129,6 +2137,7 @@ void main() {
actions: <SemanticsAction>[
SemanticsAction.tap,
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.moveCursorBackwardByWord,
SemanticsAction.setSelection,
SemanticsAction.paste,
SemanticsAction.cut,
......@@ -2197,6 +2206,7 @@ void main() {
actions: <SemanticsAction>[
SemanticsAction.tap,
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.moveCursorBackwardByWord,
SemanticsAction.setSelection,
SemanticsAction.paste,
],
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment