Unverified Commit d04c906e authored by Michael Goderbauer's avatar Michael Goderbauer Committed by GitHub

Add arguments to SemanticsActions; implement extend selection for a11y (#13490)

**This PR contains a breaking API change:**
`SemanticsConfiguration.addAction()` has been removed and replaces by action-specific setters (`onTap`, `onLongPress`, etc.) to take care of the fact that some action handlers (those, who take arguments) have different signatures.
parent 1dd68d5d
2bdb21985c5eb9250a7b744b9b4d606c5ba30945 e07eafae1d14b454c33b1ae68d14b7a5694c22b6
...@@ -383,8 +383,8 @@ class _RenderCupertinoSlider extends RenderConstrainedBox { ...@@ -383,8 +383,8 @@ class _RenderCupertinoSlider extends RenderConstrainedBox {
config.isSemanticBoundary = isInteractive; config.isSemanticBoundary = isInteractive;
if (isInteractive) { if (isInteractive) {
config.addAction(SemanticsAction.increase, _increaseAction); config.onIncrease = _increaseAction;
config.addAction(SemanticsAction.decrease, _decreaseAction); config.onDecrease = _decreaseAction;
} }
} }
......
...@@ -380,7 +380,7 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox { ...@@ -380,7 +380,7 @@ class _RenderCupertinoSwitch extends RenderConstrainedBox {
config.isSemanticBoundary = isInteractive; config.isSemanticBoundary = isInteractive;
if (isInteractive) if (isInteractive)
config.addAction(SemanticsAction.tap, _handleTap); config.onTap = _handleTap;
config.isChecked = _value; config.isChecked = _value;
} }
......
...@@ -713,8 +713,8 @@ class _RenderSlider extends RenderBox { ...@@ -713,8 +713,8 @@ class _RenderSlider extends RenderBox {
config.isSemanticBoundary = isInteractive; config.isSemanticBoundary = isInteractive;
if (isInteractive) { if (isInteractive) {
config.addAction(SemanticsAction.increase, _increaseAction); config.onIncrease = _increaseAction;
config.addAction(SemanticsAction.decrease, _decreaseAction); config.onDecrease = _decreaseAction;
} }
} }
......
...@@ -288,7 +288,7 @@ abstract class RenderToggleable extends RenderConstrainedBox { ...@@ -288,7 +288,7 @@ abstract class RenderToggleable extends RenderConstrainedBox {
config.isSemanticBoundary = isInteractive; config.isSemanticBoundary = isInteractive;
if (isInteractive) if (isInteractive)
config.addAction(SemanticsAction.tap, _handleTap); config.onTap = _handleTap;
config.isChecked = _value; config.isChecked = _value;
} }
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'dart:async'; import 'dart:async';
import 'dart:developer'; import 'dart:developer';
import 'dart:typed_data';
import 'dart:ui' as ui show window; import 'dart:ui' as ui show window;
import 'package:flutter/foundation.dart'; import 'package:flutter/foundation.dart';
...@@ -187,8 +188,12 @@ abstract class RendererBinding extends BindingBase with ServicesBinding, Schedul ...@@ -187,8 +188,12 @@ abstract class RendererBinding extends BindingBase with ServicesBinding, Schedul
} }
} }
void _handleSemanticsAction(int id, SemanticsAction action) { void _handleSemanticsAction(int id, SemanticsAction action, ByteData args) {
_pipelineOwner.semanticsOwner?.performAction(id, action); _pipelineOwner.semanticsOwner?.performAction(
id,
action,
args != null ? const StandardMessageCodec().decodeMessage(args) : null,
);
} }
void _handleSemanticsOwnerCreated() { void _handleSemanticsOwnerCreated() {
......
...@@ -823,34 +823,34 @@ class RenderCustomPaint extends RenderProxyBox { ...@@ -823,34 +823,34 @@ class RenderCustomPaint extends RenderProxyBox {
config.textDirection = properties.textDirection; config.textDirection = properties.textDirection;
} }
if (properties.onTap != null) { if (properties.onTap != null) {
config.addAction(SemanticsAction.tap, properties.onTap); config.onTap = properties.onTap;
} }
if (properties.onLongPress != null) { if (properties.onLongPress != null) {
config.addAction(SemanticsAction.longPress, properties.onLongPress); config.onLongPress = properties.onLongPress;
} }
if (properties.onScrollLeft != null) { if (properties.onScrollLeft != null) {
config.addAction(SemanticsAction.scrollLeft, properties.onScrollLeft); config.onScrollLeft = properties.onScrollLeft;
} }
if (properties.onScrollRight != null) { if (properties.onScrollRight != null) {
config.addAction(SemanticsAction.scrollRight, properties.onScrollRight); config.onScrollRight = properties.onScrollRight;
} }
if (properties.onScrollUp != null) { if (properties.onScrollUp != null) {
config.addAction(SemanticsAction.scrollUp, properties.onScrollUp); config.onScrollUp = properties.onScrollUp;
} }
if (properties.onScrollDown != null) { if (properties.onScrollDown != null) {
config.addAction(SemanticsAction.scrollDown, properties.onScrollDown); config.onScrollDown = properties.onScrollDown;
} }
if (properties.onIncrease != null) { if (properties.onIncrease != null) {
config.addAction(SemanticsAction.increase, properties.onIncrease); config.onIncrease = properties.onIncrease;
} }
if (properties.onDecrease != null) { if (properties.onDecrease != null) {
config.addAction(SemanticsAction.decrease, properties.onDecrease); config.onDecrease = properties.onDecrease;
} }
if (properties.onMoveCursorForwardByCharacter != null) { if (properties.onMoveCursorForwardByCharacter != null) {
config.addAction(SemanticsAction.moveCursorForwardByCharacter, properties.onMoveCursorForwardByCharacter); config.onMoveCursorForwardByCharacter = properties.onMoveCursorForwardByCharacter;
} }
if (properties.onMoveCursorBackwardByCharacter != null) { if (properties.onMoveCursorBackwardByCharacter != null) {
config.addAction(SemanticsAction.moveCursorBackwardByCharacter, properties.onMoveCursorBackwardByCharacter); config.onMoveCursorBackwardByCharacter = properties.onMoveCursorBackwardByCharacter;
} }
newChild.updateWith( newChild.updateWith(
......
...@@ -349,30 +349,33 @@ class RenderEditable extends RenderBox { ...@@ -349,30 +349,33 @@ class RenderEditable extends RenderBox {
..isTextField = true; ..isTextField = true;
if (_selection?.isValid == true) { if (_selection?.isValid == true) {
if (_textPainter.getOffsetBefore(_selection.extentOffset) != null) { if (_textPainter.getOffsetBefore(_selection.extentOffset) != null)
config.addAction(SemanticsAction.moveCursorBackwardByCharacter, () { config.onMoveCursorBackwardByCharacter = _handleMoveCursorBackwardByCharacter;
final int offset = _textPainter.getOffsetBefore(_selection.extentOffset); if (_textPainter.getOffsetAfter(_selection.extentOffset) != null)
if (offset == null) config.onMoveCursorForwardByCharacter = _handleMoveCursorForwardByCharacter;
return;
onSelectionChanged(
new TextSelection.collapsed(offset: offset), this, SelectionChangedCause.keyboard,
);
});
}
if (_textPainter.getOffsetAfter(_selection.extentOffset) != null) {
config.addAction(SemanticsAction.moveCursorForwardByCharacter, () {
final int offset = _textPainter.getOffsetAfter(_selection.extentOffset);
if (offset == null)
return;
onSelectionChanged(
new TextSelection.collapsed(offset: offset), this, SelectionChangedCause.keyboard,
);
});
}
} }
} }
void _handleMoveCursorForwardByCharacter(bool extentSelection) {
final int extentOffset = _textPainter.getOffsetAfter(_selection.extentOffset);
if (extentOffset == null)
return;
final int baseOffset = !extentSelection ? extentOffset : _selection.baseOffset;
onSelectionChanged(
new TextSelection(baseOffset: baseOffset, extentOffset: extentOffset), this, SelectionChangedCause.keyboard,
);
}
void _handleMoveCursorBackwardByCharacter(bool extentSelection) {
final int extentOffset = _textPainter.getOffsetBefore(_selection.extentOffset);
if (extentOffset == null)
return;
final int baseOffset = !extentSelection ? extentOffset : _selection.baseOffset;
onSelectionChanged(
new TextSelection(baseOffset: baseOffset, extentOffset: extentOffset), this, SelectionChangedCause.keyboard,
);
}
@override @override
void attach(PipelineOwner owner) { void attach(PipelineOwner owner) {
super.attach(owner); super.attach(owner);
......
...@@ -2140,7 +2140,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im ...@@ -2140,7 +2140,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
/// void describeSemanticsConfiguration(SemanticsConfiguration config) { /// void describeSemanticsConfiguration(SemanticsConfiguration config) {
/// super.describeSemanticsConfiguration(config); /// super.describeSemanticsConfiguration(config);
/// config /// config
/// ..addAction(SemanticsAction.tap, _handleTap) /// ..onTap = _handleTap
/// ..label = 'I am a button' /// ..label = 'I am a button'
/// ..isButton = true; /// ..isButton = true;
/// } /// }
......
...@@ -2685,27 +2685,26 @@ class RenderSemanticsGestureHandler extends RenderProxyBox { ...@@ -2685,27 +2685,26 @@ class RenderSemanticsGestureHandler extends RenderProxyBox {
config.explicitChildNodes = onHorizontalDragUpdate != null config.explicitChildNodes = onHorizontalDragUpdate != null
|| onVerticalDragUpdate != null; || onVerticalDragUpdate != null;
final Map<SemanticsAction, VoidCallback> actions = <SemanticsAction, VoidCallback>{}; if (onTap != null && _isValidAction(SemanticsAction.tap))
if (onTap != null) config.onTap = onTap;
actions[SemanticsAction.tap] = onTap; if (onLongPress != null && _isValidAction(SemanticsAction.longPress))
if (onLongPress != null) config.onLongPress = onLongPress;
actions[SemanticsAction.longPress] = onLongPress;
if (onHorizontalDragUpdate != null) { if (onHorizontalDragUpdate != null) {
actions[SemanticsAction.scrollRight] = _performSemanticScrollRight; if (_isValidAction(SemanticsAction.scrollRight))
actions[SemanticsAction.scrollLeft] = _performSemanticScrollLeft; config.onScrollRight = _performSemanticScrollRight;
if (_isValidAction(SemanticsAction.scrollLeft))
config.onScrollLeft = _performSemanticScrollLeft;
} }
if (onVerticalDragUpdate != null) { if (onVerticalDragUpdate != null) {
actions[SemanticsAction.scrollUp] = _performSemanticScrollUp; if (_isValidAction(SemanticsAction.scrollUp))
actions[SemanticsAction.scrollDown] = _performSemanticScrollDown; config.onScrollUp = _performSemanticScrollUp;
if (_isValidAction(SemanticsAction.scrollDown))
config.onScrollDown = _performSemanticScrollDown;
} }
}
final Iterable<SemanticsAction> actionsToAdd = validActions ?? actions.keys; bool _isValidAction(SemanticsAction action) {
return validActions == null || validActions.contains(action);
for (SemanticsAction action in actionsToAdd) {
final VoidCallback handler = actions[action];
if (handler != null)
config.addAction(action, handler);
}
} }
SemanticsNode _innerNode; SemanticsNode _innerNode;
...@@ -2830,8 +2829,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox { ...@@ -2830,8 +2829,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
VoidCallback onScrollDown, VoidCallback onScrollDown,
VoidCallback onIncrease, VoidCallback onIncrease,
VoidCallback onDecrease, VoidCallback onDecrease,
VoidCallback onMoveCursorForwardByCharacter, MoveCursorHandler onMoveCursorForwardByCharacter,
VoidCallback onMoveCursorBackwardByCharacter, MoveCursorHandler onMoveCursorBackwardByCharacter,
}) : assert(container != null), }) : assert(container != null),
_container = container, _container = container,
_explicitChildNodes = explicitChildNodes, _explicitChildNodes = explicitChildNodes,
...@@ -3173,9 +3172,9 @@ class RenderSemanticsAnnotations extends RenderProxyBox { ...@@ -3173,9 +3172,9 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
/// ///
/// TalkBack users can trigger this by pressing the volume up key while the /// TalkBack users can trigger this by pressing the volume up key while the
/// input focus is in a text field. /// input focus is in a text field.
VoidCallback get onMoveCursorForwardByCharacter => _onMoveCursorForwardByCharacter; MoveCursorHandler get onMoveCursorForwardByCharacter => _onMoveCursorForwardByCharacter;
VoidCallback _onMoveCursorForwardByCharacter; MoveCursorHandler _onMoveCursorForwardByCharacter;
set onMoveCursorForwardByCharacter(VoidCallback handler) { set onMoveCursorForwardByCharacter(MoveCursorHandler handler) {
if (_onMoveCursorForwardByCharacter == handler) if (_onMoveCursorForwardByCharacter == handler)
return; return;
final bool hadValue = _onMoveCursorForwardByCharacter != null; final bool hadValue = _onMoveCursorForwardByCharacter != null;
...@@ -3191,9 +3190,9 @@ class RenderSemanticsAnnotations extends RenderProxyBox { ...@@ -3191,9 +3190,9 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
/// ///
/// TalkBack users can trigger this by pressing the volume down key while the /// TalkBack users can trigger this by pressing the volume down key while the
/// input focus is in a text field. /// input focus is in a text field.
VoidCallback get onMoveCursorBackwardByCharacter => _onMoveCursorBackwardByCharacter; MoveCursorHandler get onMoveCursorBackwardByCharacter => _onMoveCursorBackwardByCharacter;
VoidCallback _onMoveCursorBackwardByCharacter; MoveCursorHandler _onMoveCursorBackwardByCharacter;
set onMoveCursorBackwardByCharacter(VoidCallback handler) { set onMoveCursorBackwardByCharacter(MoveCursorHandler handler) {
if (_onMoveCursorBackwardByCharacter == handler) if (_onMoveCursorBackwardByCharacter == handler)
return; return;
final bool hadValue = _onMoveCursorBackwardByCharacter != null; final bool hadValue = _onMoveCursorBackwardByCharacter != null;
...@@ -3230,25 +3229,25 @@ class RenderSemanticsAnnotations extends RenderProxyBox { ...@@ -3230,25 +3229,25 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
// ones to ensure that changing a user provided handler from a non-null to // ones to ensure that changing a user provided handler from a non-null to
// another non-null value doesn't require a semantics update. // another non-null value doesn't require a semantics update.
if (onTap != null) if (onTap != null)
config.addAction(SemanticsAction.tap, _performTap); config.onTap = _performTap;
if (onLongPress != null) if (onLongPress != null)
config.addAction(SemanticsAction.longPress, _performLongPress); config.onLongPress = _performLongPress;
if (onScrollLeft != null) if (onScrollLeft != null)
config.addAction(SemanticsAction.scrollLeft, _performScrollLeft); config.onScrollLeft = _performScrollLeft;
if (onScrollRight != null) if (onScrollRight != null)
config.addAction(SemanticsAction.scrollRight, _performScrollRight); config.onScrollRight = _performScrollRight;
if (onScrollUp != null) if (onScrollUp != null)
config.addAction(SemanticsAction.scrollUp, _performScrollUp); config.onScrollUp = _performScrollUp;
if (onScrollDown != null) if (onScrollDown != null)
config.addAction(SemanticsAction.scrollDown, _performScrollDown); config.onScrollDown = _performScrollDown;
if (onIncrease != null) if (onIncrease != null)
config.addAction(SemanticsAction.increase, _performIncrease); config.onIncrease = _performIncrease;
if (onDecrease != null) if (onDecrease != null)
config.addAction(SemanticsAction.decrease, _performDecrease); config.onDecrease = _performDecrease;
if (onMoveCursorForwardByCharacter != null) if (onMoveCursorForwardByCharacter != null)
config.addAction(SemanticsAction.moveCursorForwardByCharacter, _performMoveCursorForwardByCharacter); config.onMoveCursorForwardByCharacter = _performMoveCursorForwardByCharacter;
if (onMoveCursorBackwardByCharacter != null) if (onMoveCursorBackwardByCharacter != null)
config.addAction(SemanticsAction.moveCursorBackwardByCharacter, _performMoveCursorBackwardByCharacter); config.onMoveCursorBackwardByCharacter = _performMoveCursorBackwardByCharacter;
} }
void _performTap() { void _performTap() {
...@@ -3291,14 +3290,14 @@ class RenderSemanticsAnnotations extends RenderProxyBox { ...@@ -3291,14 +3290,14 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
onDecrease(); onDecrease();
} }
void _performMoveCursorForwardByCharacter() { void _performMoveCursorForwardByCharacter(bool extendSelection) {
if (onMoveCursorForwardByCharacter != null) if (onMoveCursorForwardByCharacter != null)
onMoveCursorForwardByCharacter(); onMoveCursorForwardByCharacter(extendSelection);
} }
void _performMoveCursorBackwardByCharacter() { void _performMoveCursorBackwardByCharacter(bool extendSelection) {
if (onMoveCursorBackwardByCharacter != null) if (onMoveCursorBackwardByCharacter != null)
onMoveCursorBackwardByCharacter(); onMoveCursorBackwardByCharacter(extendSelection);
} }
} }
......
...@@ -265,10 +265,17 @@ TextEditingValue _selectionAwareTextManipulation( ...@@ -265,10 +265,17 @@ TextEditingValue _selectionAwareTextManipulation(
value.text.substring(selectionEndIndex) value.text.substring(selectionEndIndex)
); );
manipulatedText = beforeSelection + inSelection + afterSelection; manipulatedText = beforeSelection + inSelection + afterSelection;
manipulatedSelection = value.selection.copyWith( if (value.selection.baseOffset > value.selection.extentOffset) {
baseOffset: beforeSelection.length, manipulatedSelection = value.selection.copyWith(
extentOffset: beforeSelection.length + inSelection.length, baseOffset: beforeSelection.length + inSelection.length,
); extentOffset: beforeSelection.length,
);
} else {
manipulatedSelection = value.selection.copyWith(
baseOffset: beforeSelection.length,
extentOffset: beforeSelection.length + inSelection.length,
);
}
} }
return new TextEditingValue( return new TextEditingValue(
text: manipulatedText, text: manipulatedText,
......
...@@ -4726,8 +4726,8 @@ class Semantics extends SingleChildRenderObjectWidget { ...@@ -4726,8 +4726,8 @@ class Semantics extends SingleChildRenderObjectWidget {
VoidCallback onScrollDown, VoidCallback onScrollDown,
VoidCallback onIncrease, VoidCallback onIncrease,
VoidCallback onDecrease, VoidCallback onDecrease,
VoidCallback onMoveCursorForwardByCharacter, MoveCursorHandler onMoveCursorForwardByCharacter,
VoidCallback onMoveCursorBackwardByCharacter, MoveCursorHandler onMoveCursorBackwardByCharacter,
}) : this.fromProperties( }) : this.fromProperties(
key: key, key: key,
child: child, child: child,
......
...@@ -65,19 +65,19 @@ void main() { ...@@ -65,19 +65,19 @@ void main() {
TestRender middle; TestRender middle;
final TestRender root = new TestRender( final TestRender root = new TestRender(
action: SemanticsAction.tap, hasTapAction: true,
isSemanticBoundary: true, isSemanticBoundary: true,
child: new TestRender( child: new TestRender(
action: SemanticsAction.longPress, hasLongPressAction: true,
isSemanticBoundary: false, isSemanticBoundary: false,
child: middle = new TestRender( child: middle = new TestRender(
action: SemanticsAction.scrollLeft, hasScrollLeftAction: true,
isSemanticBoundary: false, isSemanticBoundary: false,
child: new TestRender( child: new TestRender(
action: SemanticsAction.scrollRight, hasScrollRightAction: true,
isSemanticBoundary: false, isSemanticBoundary: false,
child: new TestRender( child: new TestRender(
action: SemanticsAction.scrollUp, hasScrollUpAction: true,
isSemanticBoundary: true, isSemanticBoundary: true,
) )
) )
...@@ -91,7 +91,9 @@ void main() { ...@@ -91,7 +91,9 @@ void main() {
int expectedActions = SemanticsAction.tap.index | SemanticsAction.longPress.index | SemanticsAction.scrollLeft.index | SemanticsAction.scrollRight.index; int expectedActions = SemanticsAction.tap.index | SemanticsAction.longPress.index | SemanticsAction.scrollLeft.index | SemanticsAction.scrollRight.index;
expect(root.debugSemantics.getSemanticsData().actions, expectedActions); expect(root.debugSemantics.getSemanticsData().actions, expectedActions);
middle.action = SemanticsAction.scrollDown; middle
..hasScrollLeftAction = false
..hasScrollDownAction = true;
middle.markNeedsSemanticsUpdate(); middle.markNeedsSemanticsUpdate();
pumpFrame(phase: EnginePhase.flushSemantics); pumpFrame(phase: EnginePhase.flushSemantics);
...@@ -204,9 +206,9 @@ void main() { ...@@ -204,9 +206,9 @@ void main() {
final SemanticsConfiguration config = new SemanticsConfiguration() final SemanticsConfiguration config = new SemanticsConfiguration()
..isMergingSemanticsOfDescendants = true ..isMergingSemanticsOfDescendants = true
..addAction(SemanticsAction.scrollUp, () { }) ..onScrollUp = () { }
..addAction(SemanticsAction.longPress, () { }) ..onLongPress = () { }
..addAction(SemanticsAction.showOnScreen, () { }) ..onShowOnScreen = () { }
..isChecked = false ..isChecked = false
..isSelected = true ..isSelected = true
..isButton = true ..isButton = true
...@@ -237,22 +239,128 @@ void main() { ...@@ -237,22 +239,128 @@ void main() {
'SemanticsData(Rect.fromLTRB(50.0, 10.0, 70.0, 40.0), [10.0,0.0,0.0,0.0; 0.0,10.0,0.0,0.0; 0.0,0.0,1.0,0.0; 0.0,0.0,0.0,1.0])', 'SemanticsData(Rect.fromLTRB(50.0, 10.0, 70.0, 40.0), [10.0,0.0,0.0,0.0; 0.0,10.0,0.0,0.0; 0.0,0.0,1.0,0.0; 0.0,0.0,0.0,1.0])',
); );
}); });
test('SemanticsConfiguration getter/setter', () {
final SemanticsConfiguration config = new SemanticsConfiguration();
expect(config.isSemanticBoundary, isFalse);
expect(config.isButton, isFalse);
expect(config.isMergingSemanticsOfDescendants, isFalse);
expect(config.isChecked, isFalse);
expect(config.isSelected, isFalse);
expect(config.isBlockingSemanticsOfPreviouslyPaintedNodes, isFalse);
expect(config.isFocused, isFalse);
expect(config.isMergingDescendantsIntoOneNode, isFalse);
expect(config.isTextField, isFalse);
expect(config.onShowOnScreen, isNull);
expect(config.onScrollDown, isNull);
expect(config.onScrollUp, isNull);
expect(config.onScrollLeft, isNull);
expect(config.onScrollRight, isNull);
expect(config.onLongPress, isNull);
expect(config.onDecrease, isNull);
expect(config.onIncrease, isNull);
expect(config.onMoveCursorForwardByCharacter, isNull);
expect(config.onMoveCursorBackwardByCharacter, isNull);
expect(config.onTap, isNull);
config.isSemanticBoundary = true;
config.isButton = true;
config.isMergingSemanticsOfDescendants = true;
config.isChecked = true;
config.isSelected = true;
config.isBlockingSemanticsOfPreviouslyPaintedNodes = true;
config.isFocused = true;
config.isMergingDescendantsIntoOneNode = true;
config.isTextField = true;
final VoidCallback onShowOnScreen = () { };
final VoidCallback onScrollDown = () { };
final VoidCallback onScrollUp = () { };
final VoidCallback onScrollLeft = () { };
final VoidCallback onScrollRight = () { };
final VoidCallback onLongPress = () { };
final VoidCallback onDecrease = () { };
final VoidCallback onIncrease = () { };
final MoveCursorHandler onMoveCursorForwardByCharacter = (bool _) { };
final MoveCursorHandler onMoveCursorBackwardByCharacter = (bool _) { };
final VoidCallback onTap = () { };
config.onShowOnScreen = onShowOnScreen;
config.onScrollDown = onScrollDown;
config.onScrollUp = onScrollUp;
config.onScrollLeft = onScrollLeft;
config.onScrollRight = onScrollRight;
config.onLongPress = onLongPress;
config.onDecrease = onDecrease;
config.onIncrease = onIncrease;
config.onMoveCursorForwardByCharacter = onMoveCursorForwardByCharacter;
config.onMoveCursorBackwardByCharacter = onMoveCursorBackwardByCharacter;
config.onTap = onTap;
expect(config.isSemanticBoundary, isTrue);
expect(config.isButton, isTrue);
expect(config.isMergingSemanticsOfDescendants, isTrue);
expect(config.isChecked, isTrue);
expect(config.isSelected, isTrue);
expect(config.isBlockingSemanticsOfPreviouslyPaintedNodes, isTrue);
expect(config.isFocused, isTrue);
expect(config.isMergingDescendantsIntoOneNode, isTrue);
expect(config.isTextField, isTrue);
expect(config.onShowOnScreen, same(onShowOnScreen));
expect(config.onScrollDown, same(onScrollDown));
expect(config.onScrollUp, same(onScrollUp));
expect(config.onScrollLeft, same(onScrollLeft));
expect(config.onScrollRight, same(onScrollRight));
expect(config.onLongPress, same(onLongPress));
expect(config.onDecrease, same(onDecrease));
expect(config.onIncrease, same(onIncrease));
expect(config.onMoveCursorForwardByCharacter, same(onMoveCursorForwardByCharacter));
expect(config.onMoveCursorBackwardByCharacter, same(onMoveCursorBackwardByCharacter));
expect(config.onTap, same(onTap));
});
} }
class TestRender extends RenderProxyBox { class TestRender extends RenderProxyBox {
TestRender({ this.action, this.isSemanticBoundary, RenderObject child }) : super(child); TestRender({
this.hasTapAction: false,
final bool isSemanticBoundary; this.hasLongPressAction: false,
this.hasScrollLeftAction: false,
this.hasScrollRightAction: false,
this.hasScrollUpAction: false,
this.hasScrollDownAction: false,
this.isSemanticBoundary,
RenderObject child
}) : super(child);
bool hasTapAction;
bool hasLongPressAction;
bool hasScrollLeftAction;
bool hasScrollRightAction;
bool hasScrollUpAction;
bool hasScrollDownAction;
bool isSemanticBoundary;
SemanticsAction action;
@override @override
void describeSemanticsConfiguration(SemanticsConfiguration config) { void describeSemanticsConfiguration(SemanticsConfiguration config) {
super.describeSemanticsConfiguration(config); super.describeSemanticsConfiguration(config);
config config.isSemanticBoundary = isSemanticBoundary;
..isSemanticBoundary = isSemanticBoundary if (hasTapAction)
..addAction(action, () { }); config.onTap = () { };
if (hasLongPressAction)
config.onLongPress = () { };
if (hasScrollLeftAction)
config.onScrollLeft = () { };
if (hasScrollRightAction)
config.onScrollRight = () { };
if (hasScrollUpAction)
config.onScrollUp = () { };
if (hasScrollDownAction)
config.onScrollDown = () { };
} }
} }
...@@ -414,6 +414,7 @@ void main() { ...@@ -414,6 +414,7 @@ void main() {
testWidgets('can move cursor with a11y means', (WidgetTester tester) async { testWidgets('can move cursor with a11y means', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester); final SemanticsTester semantics = new SemanticsTester(tester);
const bool doNotExtendSelection = false;
controller.text = 'test'; controller.text = 'test';
controller.selection = new TextSelection.collapsed(offset: controller.text.length); controller.selection = new TextSelection.collapsed(offset: controller.text.length);
...@@ -440,7 +441,7 @@ void main() { ...@@ -440,7 +441,7 @@ void main() {
expect(controller.selection.baseOffset, 4); expect(controller.selection.baseOffset, 4);
expect(controller.selection.extentOffset, 4); expect(controller.selection.extentOffset, 4);
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId, SemanticsAction.moveCursorBackwardByCharacter); tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId, SemanticsAction.moveCursorBackwardByCharacter, doNotExtendSelection);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(controller.selection.baseOffset, 3); expect(controller.selection.baseOffset, 3);
...@@ -454,11 +455,11 @@ void main() { ...@@ -454,11 +455,11 @@ void main() {
], ],
)); ));
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId, SemanticsAction.moveCursorBackwardByCharacter); tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId, SemanticsAction.moveCursorBackwardByCharacter, doNotExtendSelection);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId, SemanticsAction.moveCursorBackwardByCharacter); tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId, SemanticsAction.moveCursorBackwardByCharacter, doNotExtendSelection);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId, SemanticsAction.moveCursorBackwardByCharacter); tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId, SemanticsAction.moveCursorBackwardByCharacter, doNotExtendSelection);
await tester.pumpAndSettle(); await tester.pumpAndSettle();
expect(controller.selection.baseOffset, 0); expect(controller.selection.baseOffset, 0);
...@@ -472,6 +473,89 @@ void main() { ...@@ -472,6 +473,89 @@ void main() {
], ],
)); ));
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId, SemanticsAction.moveCursorForwardByCharacter, doNotExtendSelection);
await tester.pumpAndSettle();
expect(controller.selection.baseOffset, 1);
expect(controller.selection.extentOffset, 1);
semantics.dispose();
});
testWidgets('can extend selection with a11y means', (WidgetTester tester) async {
final SemanticsTester semantics = new SemanticsTester(tester);
const bool extendSelection = true;
const bool doNotExtendSelection = false;
controller.text = 'test';
controller.selection = new TextSelection.collapsed(offset: controller.text.length);
await tester.pumpWidget(new MaterialApp(
home: new EditableText(
controller: controller,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
),
));
expect(semantics, includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorBackwardByCharacter,
],
));
final RenderEditable render = tester.allRenderObjects.firstWhere((RenderObject o) => o.runtimeType == RenderEditable);
final int semanticsId = render.debugSemantics.id;
expect(controller.selection.baseOffset, 4);
expect(controller.selection.extentOffset, 4);
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId, SemanticsAction.moveCursorBackwardByCharacter, extendSelection);
await tester.pumpAndSettle();
expect(controller.selection.baseOffset, 4);
expect(controller.selection.extentOffset, 3);
expect(semantics, includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorBackwardByCharacter,
SemanticsAction.moveCursorForwardByCharacter,
],
));
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId, SemanticsAction.moveCursorBackwardByCharacter, extendSelection);
await tester.pumpAndSettle();
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId, SemanticsAction.moveCursorBackwardByCharacter, extendSelection);
await tester.pumpAndSettle();
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId, SemanticsAction.moveCursorBackwardByCharacter, extendSelection);
await tester.pumpAndSettle();
expect(controller.selection.baseOffset, 4);
expect(controller.selection.extentOffset, 0);
await tester.pumpAndSettle();
expect(semantics, includesNodeWith(
value: 'test',
actions: <SemanticsAction>[
SemanticsAction.moveCursorForwardByCharacter,
],
));
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId, SemanticsAction.moveCursorForwardByCharacter, doNotExtendSelection);
await tester.pumpAndSettle();
expect(controller.selection.baseOffset, 1);
expect(controller.selection.extentOffset, 1);
tester.binding.pipelineOwner.semanticsOwner.performAction(semanticsId, SemanticsAction.moveCursorForwardByCharacter, extendSelection);
await tester.pumpAndSettle();
expect(controller.selection.baseOffset, 1);
expect(controller.selection.extentOffset, 2);
semantics.dispose(); semantics.dispose();
}); });
} }
...@@ -388,8 +388,8 @@ void main() { ...@@ -388,8 +388,8 @@ void main() {
onScrollDown: () => performedActions.add(SemanticsAction.scrollDown), onScrollDown: () => performedActions.add(SemanticsAction.scrollDown),
onIncrease: () => performedActions.add(SemanticsAction.increase), onIncrease: () => performedActions.add(SemanticsAction.increase),
onDecrease: () => performedActions.add(SemanticsAction.decrease), onDecrease: () => performedActions.add(SemanticsAction.decrease),
onMoveCursorForwardByCharacter: () => performedActions.add(SemanticsAction.moveCursorForwardByCharacter), onMoveCursorForwardByCharacter: (bool _) => performedActions.add(SemanticsAction.moveCursorForwardByCharacter),
onMoveCursorBackwardByCharacter: () => performedActions.add(SemanticsAction.moveCursorBackwardByCharacter), onMoveCursorBackwardByCharacter: (bool _) => performedActions.add(SemanticsAction.moveCursorBackwardByCharacter),
) )
); );
...@@ -412,7 +412,14 @@ void main() { ...@@ -412,7 +412,14 @@ void main() {
final SemanticsOwner semanticsOwner = tester.binding.pipelineOwner.semanticsOwner; final SemanticsOwner semanticsOwner = tester.binding.pipelineOwner.semanticsOwner;
int expectedLength = 1; int expectedLength = 1;
for (SemanticsAction action in allActions) { for (SemanticsAction action in allActions) {
semanticsOwner.performAction(expectedId, action); switch (action) {
case SemanticsAction.moveCursorBackwardByCharacter:
case SemanticsAction.moveCursorForwardByCharacter:
semanticsOwner.performAction(expectedId, action, true);
break;
default:
semanticsOwner.performAction(expectedId, action);
}
expect(performedActions.length, expectedLength); expect(performedActions.length, expectedLength);
expect(performedActions.last, action); expect(performedActions.last, action);
expectedLength += 1; expectedLength += 1;
......
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