Unverified Commit 9c9b71a0 authored by Mouad Debbar's avatar Mouad Debbar Committed by GitHub

Add multi-line flag to semantics (#36297)

parent 578204df
...@@ -846,6 +846,9 @@ class RenderCustomPaint extends RenderProxyBox { ...@@ -846,6 +846,9 @@ class RenderCustomPaint extends RenderProxyBox {
if (properties.obscured != null) { if (properties.obscured != null) {
config.isObscured = properties.obscured; config.isObscured = properties.obscured;
} }
if (properties.multiline != null) {
config.isMultiline = properties.multiline;
}
if (properties.hidden != null) { if (properties.hidden != null) {
config.isHidden = properties.hidden; config.isHidden = properties.hidden;
} }
...@@ -924,6 +927,12 @@ class RenderCustomPaint extends RenderProxyBox { ...@@ -924,6 +927,12 @@ class RenderCustomPaint extends RenderProxyBox {
if (properties.onMoveCursorBackwardByCharacter != null) { if (properties.onMoveCursorBackwardByCharacter != null) {
config.onMoveCursorBackwardByCharacter = properties.onMoveCursorBackwardByCharacter; config.onMoveCursorBackwardByCharacter = properties.onMoveCursorBackwardByCharacter;
} }
if (properties.onMoveCursorForwardByWord != null) {
config.onMoveCursorForwardByWord = properties.onMoveCursorForwardByWord;
}
if (properties.onMoveCursorBackwardByWord != null) {
config.onMoveCursorBackwardByWord = properties.onMoveCursorBackwardByWord;
}
if (properties.onSetSelection != null) { if (properties.onSetSelection != null) {
config.onSetSelection = properties.onSetSelection; config.onSetSelection = properties.onSetSelection;
} }
......
...@@ -992,6 +992,7 @@ class RenderEditable extends RenderBox { ...@@ -992,6 +992,7 @@ class RenderEditable extends RenderBox {
? obscuringCharacter * text.toPlainText().length ? obscuringCharacter * text.toPlainText().length
: text.toPlainText() : text.toPlainText()
..isObscured = obscureText ..isObscured = obscureText
..isMultiline = _isMultiline
..textDirection = textDirection ..textDirection = textDirection
..isFocused = hasFocus ..isFocused = hasFocus
..isTextField = true; ..isTextField = true;
......
...@@ -3430,6 +3430,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox { ...@@ -3430,6 +3430,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
bool focused, bool focused,
bool inMutuallyExclusiveGroup, bool inMutuallyExclusiveGroup,
bool obscured, bool obscured,
bool multiline,
bool scopesRoute, bool scopesRoute,
bool namesRoute, bool namesRoute,
bool hidden, bool hidden,
...@@ -3478,6 +3479,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox { ...@@ -3478,6 +3479,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
_focused = focused, _focused = focused,
_inMutuallyExclusiveGroup = inMutuallyExclusiveGroup, _inMutuallyExclusiveGroup = inMutuallyExclusiveGroup,
_obscured = obscured, _obscured = obscured,
_multiline = multiline,
_scopesRoute = scopesRoute, _scopesRoute = scopesRoute,
_namesRoute = namesRoute, _namesRoute = namesRoute,
_liveRegion = liveRegion, _liveRegion = liveRegion,
...@@ -3673,6 +3675,17 @@ class RenderSemanticsAnnotations extends RenderProxyBox { ...@@ -3673,6 +3675,17 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
markNeedsSemanticsUpdate(); markNeedsSemanticsUpdate();
} }
/// If non-null, sets the [SemanticsNode.isMultiline] semantic to the given
/// value.
bool get multiline => _multiline;
bool _multiline;
set multiline(bool value) {
if (multiline == value)
return;
_multiline = value;
markNeedsSemanticsUpdate();
}
/// If non-null, sets the [SemanticsNode.scopesRoute] semantic to the give value. /// If non-null, sets the [SemanticsNode.scopesRoute] semantic to the give value.
bool get scopesRoute => _scopesRoute; bool get scopesRoute => _scopesRoute;
bool _scopesRoute; bool _scopesRoute;
...@@ -4272,6 +4285,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox { ...@@ -4272,6 +4285,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
config.isInMutuallyExclusiveGroup = inMutuallyExclusiveGroup; config.isInMutuallyExclusiveGroup = inMutuallyExclusiveGroup;
if (obscured != null) if (obscured != null)
config.isObscured = obscured; config.isObscured = obscured;
if (multiline != null)
config.isMultiline = multiline;
if (hidden != null) if (hidden != null)
config.isHidden = hidden; config.isHidden = hidden;
if (image != null) if (image != null)
......
...@@ -570,6 +570,7 @@ class SemanticsProperties extends DiagnosticableTree { ...@@ -570,6 +570,7 @@ class SemanticsProperties extends DiagnosticableTree {
this.inMutuallyExclusiveGroup, this.inMutuallyExclusiveGroup,
this.hidden, this.hidden,
this.obscured, this.obscured,
this.multiline,
this.scopesRoute, this.scopesRoute,
this.namesRoute, this.namesRoute,
this.image, this.image,
...@@ -700,6 +701,15 @@ class SemanticsProperties extends DiagnosticableTree { ...@@ -700,6 +701,15 @@ class SemanticsProperties extends DiagnosticableTree {
/// Doing so instructs screen readers to not read out the [value]. /// Doing so instructs screen readers to not read out the [value].
final bool obscured; final bool obscured;
/// Whether the [value] is coming from a field that supports multi-line text
/// editing.
///
/// This option is only meaningful when [textField] is true to indicate
/// whether it's a single-line or multi-line text field.
///
/// This option is null when [textField] is false.
final bool multiline;
/// If non-null, whether the node corresponds to the root of a subtree for /// If non-null, whether the node corresponds to the root of a subtree for
/// which a route name should be announced. /// which a route name should be announced.
/// ///
...@@ -1654,6 +1664,11 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { ...@@ -1654,6 +1664,11 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
TextSelection get textSelection => _textSelection; TextSelection get textSelection => _textSelection;
TextSelection _textSelection; TextSelection _textSelection;
/// If this node represents a text field, this indicates whether or not it's
/// a multi-line text field.
bool get isMultiline => _isMultiline;
bool _isMultiline;
/// The total number of scrollable children that contribute to semantics. /// The total number of scrollable children that contribute to semantics.
/// ///
/// If the number of children are unknown or unbounded, this value will be /// If the number of children are unknown or unbounded, this value will be
...@@ -1678,7 +1693,6 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { ...@@ -1678,7 +1693,6 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
double get scrollPosition => _scrollPosition; double get scrollPosition => _scrollPosition;
double _scrollPosition; double _scrollPosition;
/// Indicates the maximum in-range value for [scrollPosition] if the node is /// Indicates the maximum in-range value for [scrollPosition] if the node is
/// scrollable. /// scrollable.
/// ///
...@@ -1756,6 +1770,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin { ...@@ -1756,6 +1770,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
_customSemanticsActions = Map<CustomSemanticsAction, VoidCallback>.from(config._customSemanticsActions); _customSemanticsActions = Map<CustomSemanticsAction, VoidCallback>.from(config._customSemanticsActions);
_actionsAsBits = config._actionsAsBits; _actionsAsBits = config._actionsAsBits;
_textSelection = config._textSelection; _textSelection = config._textSelection;
_isMultiline = config.isMultiline;
_scrollPosition = config._scrollPosition; _scrollPosition = config._scrollPosition;
_scrollExtentMax = config._scrollExtentMax; _scrollExtentMax = config._scrollExtentMax;
_scrollExtentMin = config._scrollExtentMin; _scrollExtentMin = config._scrollExtentMin;
...@@ -3531,6 +3546,15 @@ class SemanticsConfiguration { ...@@ -3531,6 +3546,15 @@ class SemanticsConfiguration {
_setFlag(SemanticsFlag.isObscured, value); _setFlag(SemanticsFlag.isObscured, value);
} }
/// Whether the text field is multi-line.
///
/// This option is usually set in combination with [textField] to indicate
/// that the text field is configured to be multi-line.
bool get isMultiline => _hasFlag(SemanticsFlag.isMultiline);
set isMultiline(bool value) {
_setFlag(SemanticsFlag.isMultiline, value);
}
/// Whether the platform can scroll the semantics node when the user attempts /// Whether the platform can scroll the semantics node when the user attempts
/// to move focus to an offscreen child. /// to move focus to an offscreen child.
/// ///
......
...@@ -5926,6 +5926,7 @@ class Semantics extends SingleChildRenderObjectWidget { ...@@ -5926,6 +5926,7 @@ class Semantics extends SingleChildRenderObjectWidget {
bool focused, bool focused,
bool inMutuallyExclusiveGroup, bool inMutuallyExclusiveGroup,
bool obscured, bool obscured,
bool multiline,
bool scopesRoute, bool scopesRoute,
bool namesRoute, bool namesRoute,
bool hidden, bool hidden,
...@@ -5976,6 +5977,7 @@ class Semantics extends SingleChildRenderObjectWidget { ...@@ -5976,6 +5977,7 @@ class Semantics extends SingleChildRenderObjectWidget {
focused: focused, focused: focused,
inMutuallyExclusiveGroup: inMutuallyExclusiveGroup, inMutuallyExclusiveGroup: inMutuallyExclusiveGroup,
obscured: obscured, obscured: obscured,
multiline: multiline,
scopesRoute: scopesRoute, scopesRoute: scopesRoute,
namesRoute: namesRoute, namesRoute: namesRoute,
hidden: hidden, hidden: hidden,
...@@ -6086,6 +6088,7 @@ class Semantics extends SingleChildRenderObjectWidget { ...@@ -6086,6 +6088,7 @@ class Semantics extends SingleChildRenderObjectWidget {
liveRegion: properties.liveRegion, liveRegion: properties.liveRegion,
inMutuallyExclusiveGroup: properties.inMutuallyExclusiveGroup, inMutuallyExclusiveGroup: properties.inMutuallyExclusiveGroup,
obscured: properties.obscured, obscured: properties.obscured,
multiline: properties.multiline,
scopesRoute: properties.scopesRoute, scopesRoute: properties.scopesRoute,
namesRoute: properties.namesRoute, namesRoute: properties.namesRoute,
hidden: properties.hidden, hidden: properties.hidden,
...@@ -6151,6 +6154,7 @@ class Semantics extends SingleChildRenderObjectWidget { ...@@ -6151,6 +6154,7 @@ class Semantics extends SingleChildRenderObjectWidget {
..focused = properties.focused ..focused = properties.focused
..inMutuallyExclusiveGroup = properties.inMutuallyExclusiveGroup ..inMutuallyExclusiveGroup = properties.inMutuallyExclusiveGroup
..obscured = properties.obscured ..obscured = properties.obscured
..multiline = properties.multiline
..hidden = properties.hidden ..hidden = properties.hidden
..image = properties.image ..image = properties.image
..liveRegion = properties.liveRegion ..liveRegion = properties.liveRegion
......
...@@ -341,6 +341,8 @@ void _defineTests() { ...@@ -341,6 +341,8 @@ void _defineTests() {
onPaste: () => performedActions.add(SemanticsAction.paste), onPaste: () => performedActions.add(SemanticsAction.paste),
onMoveCursorForwardByCharacter: (bool _) => performedActions.add(SemanticsAction.moveCursorForwardByCharacter), onMoveCursorForwardByCharacter: (bool _) => performedActions.add(SemanticsAction.moveCursorForwardByCharacter),
onMoveCursorBackwardByCharacter: (bool _) => performedActions.add(SemanticsAction.moveCursorBackwardByCharacter), onMoveCursorBackwardByCharacter: (bool _) => performedActions.add(SemanticsAction.moveCursorBackwardByCharacter),
onMoveCursorForwardByWord: (bool _) => performedActions.add(SemanticsAction.moveCursorForwardByWord),
onMoveCursorBackwardByWord: (bool _) => performedActions.add(SemanticsAction.moveCursorBackwardByWord),
onSetSelection: (TextSelection _) => performedActions.add(SemanticsAction.setSelection), onSetSelection: (TextSelection _) => performedActions.add(SemanticsAction.setSelection),
onDidGainAccessibilityFocus: () => performedActions.add(SemanticsAction.didGainAccessibilityFocus), onDidGainAccessibilityFocus: () => performedActions.add(SemanticsAction.didGainAccessibilityFocus),
onDidLoseAccessibilityFocus: () => performedActions.add(SemanticsAction.didLoseAccessibilityFocus), onDidLoseAccessibilityFocus: () => performedActions.add(SemanticsAction.didLoseAccessibilityFocus),
...@@ -349,8 +351,6 @@ void _defineTests() { ...@@ -349,8 +351,6 @@ void _defineTests() {
), ),
)); ));
final Set<SemanticsAction> allActions = SemanticsAction.values.values.toSet() final Set<SemanticsAction> allActions = SemanticsAction.values.values.toSet()
..remove(SemanticsAction.moveCursorForwardByWord)
..remove(SemanticsAction.moveCursorBackwardByWord)
..remove(SemanticsAction.customAction) // customAction is not user-exposed. ..remove(SemanticsAction.customAction) // customAction is not user-exposed.
..remove(SemanticsAction.showOnScreen); // showOnScreen is not user-exposed ..remove(SemanticsAction.showOnScreen); // showOnScreen is not user-exposed
...@@ -378,6 +378,8 @@ void _defineTests() { ...@@ -378,6 +378,8 @@ void _defineTests() {
switch (action) { switch (action) {
case SemanticsAction.moveCursorBackwardByCharacter: case SemanticsAction.moveCursorBackwardByCharacter:
case SemanticsAction.moveCursorForwardByCharacter: case SemanticsAction.moveCursorForwardByCharacter:
case SemanticsAction.moveCursorBackwardByWord:
case SemanticsAction.moveCursorForwardByWord:
semanticsOwner.performAction(expectedId, action, true); semanticsOwner.performAction(expectedId, action, true);
break; break;
case SemanticsAction.setSelection: case SemanticsAction.setSelection:
...@@ -417,20 +419,24 @@ void _defineTests() { ...@@ -417,20 +419,24 @@ void _defineTests() {
inMutuallyExclusiveGroup: true, inMutuallyExclusiveGroup: true,
header: true, header: true,
obscured: true, obscured: true,
// TODO(mdebbar): Uncomment after https://github.com/flutter/engine/pull/9894
//multiline: true,
scopesRoute: true, scopesRoute: true,
namesRoute: true, namesRoute: true,
image: true, image: true,
liveRegion: true, liveRegion: true,
toggled: true,
), ),
), ),
), ),
)); ));
List<SemanticsFlag> flags = SemanticsFlag.values.values.toList(); List<SemanticsFlag> flags = SemanticsFlag.values.values.toList();
// [SemanticsFlag.hasImplicitScrolling] isn't part of [SemanticsProperties]
// therefore it has to be removed.
flags flags
..remove(SemanticsFlag.hasImplicitScrolling) // TODO(mdebbar): Remove this line after https://github.com/flutter/engine/pull/9894
..remove(SemanticsFlag.hasToggledState) ..remove(SemanticsFlag.isMultiline)
..remove(SemanticsFlag.hasImplicitScrolling) ..remove(SemanticsFlag.hasImplicitScrolling);
..remove(SemanticsFlag.isToggled);
TestSemantics expectedSemantics = TestSemantics.root( TestSemantics expectedSemantics = TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
TestSemantics.rootChild( TestSemantics.rootChild(
...@@ -454,6 +460,7 @@ void _defineTests() { ...@@ -454,6 +460,7 @@ void _defineTests() {
rect: Rect.fromLTRB(1.0, 2.0, 3.0, 4.0), rect: Rect.fromLTRB(1.0, 2.0, 3.0, 4.0),
properties: SemanticsProperties( properties: SemanticsProperties(
enabled: true, enabled: true,
checked: true,
toggled: true, toggled: true,
selected: true, selected: true,
hidden: true, hidden: true,
...@@ -464,6 +471,8 @@ void _defineTests() { ...@@ -464,6 +471,8 @@ void _defineTests() {
inMutuallyExclusiveGroup: true, inMutuallyExclusiveGroup: true,
header: true, header: true,
obscured: true, obscured: true,
// TODO(mdebbar): Uncomment after https://github.com/flutter/engine/pull/9894
//multiline: true,
scopesRoute: true, scopesRoute: true,
namesRoute: true, namesRoute: true,
image: true, image: true,
...@@ -473,11 +482,12 @@ void _defineTests() { ...@@ -473,11 +482,12 @@ void _defineTests() {
), ),
)); ));
flags = SemanticsFlag.values.values.toList(); flags = SemanticsFlag.values.values.toList();
// [SemanticsFlag.hasImplicitScrolling] isn't part of [SemanticsProperties]
// therefore it has to be removed.
flags flags
..remove(SemanticsFlag.hasImplicitScrolling) // TODO(mdebbar): Remove this line after https://github.com/flutter/engine/pull/9894
..remove(SemanticsFlag.hasCheckedState) ..remove(SemanticsFlag.isMultiline)
..remove(SemanticsFlag.hasImplicitScrolling) ..remove(SemanticsFlag.hasImplicitScrolling);
..remove(SemanticsFlag.isChecked);
expectedSemantics = TestSemantics.root( expectedSemantics = TestSemantics.root(
children: <TestSemantics>[ children: <TestSemantics>[
......
...@@ -910,6 +910,67 @@ void main() { ...@@ -910,6 +910,67 @@ void main() {
semantics.dispose(); semantics.dispose();
}); });
testWidgets('EditableText sets multi-line flag in semantics', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
await tester.pumpWidget(
MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality(
textDirection: TextDirection.ltr,
child: FocusScope(
node: focusScopeNode,
autofocus: true,
child: EditableText(
backgroundCursorColor: Colors.grey,
controller: controller,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
maxLines: 1,
),
),
),
),
);
expect(
semantics,
includesNodeWith(flags: <SemanticsFlag>[SemanticsFlag.isTextField]),
);
await tester.pumpWidget(
MediaQuery(
data: const MediaQueryData(devicePixelRatio: 1.0),
child: Directionality(
textDirection: TextDirection.ltr,
child: FocusScope(
node: focusScopeNode,
autofocus: true,
child: EditableText(
backgroundCursorColor: Colors.grey,
controller: controller,
focusNode: focusNode,
style: textStyle,
cursorColor: cursorColor,
maxLines: 3,
),
),
),
),
);
expect(
semantics,
includesNodeWith(flags: <SemanticsFlag>[
SemanticsFlag.isTextField,
SemanticsFlag.isMultiline,
]),
);
semantics.dispose();
});
testWidgets('EditableText includes text as value in semantics', (WidgetTester tester) async { testWidgets('EditableText includes text as value in semantics', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester); final SemanticsTester semantics = SemanticsTester(tester);
......
...@@ -479,6 +479,8 @@ void main() { ...@@ -479,6 +479,8 @@ void main() {
inMutuallyExclusiveGroup: true, inMutuallyExclusiveGroup: true,
header: true, header: true,
obscured: true, obscured: true,
// TODO(mdebbar): Uncomment after https://github.com/flutter/engine/pull/9894
//multiline: true,
scopesRoute: true, scopesRoute: true,
namesRoute: true, namesRoute: true,
image: true, image: true,
...@@ -487,6 +489,8 @@ void main() { ...@@ -487,6 +489,8 @@ void main() {
); );
final List<SemanticsFlag> flags = SemanticsFlag.values.values.toList(); final List<SemanticsFlag> flags = SemanticsFlag.values.values.toList();
flags flags
// TODO(mdebbar): Remove this line after https://github.com/flutter/engine/pull/9894
..remove(SemanticsFlag.isMultiline)
..remove(SemanticsFlag.hasToggledState) ..remove(SemanticsFlag.hasToggledState)
..remove(SemanticsFlag.isToggled) ..remove(SemanticsFlag.isToggled)
..remove(SemanticsFlag.hasImplicitScrolling); ..remove(SemanticsFlag.hasImplicitScrolling);
......
...@@ -419,6 +419,7 @@ Matcher matchesSemantics({ ...@@ -419,6 +419,7 @@ Matcher matchesSemantics({
bool isInMutuallyExclusiveGroup = false, bool isInMutuallyExclusiveGroup = false,
bool isHeader = false, bool isHeader = false,
bool isObscured = false, bool isObscured = false,
bool isMultiline = false,
bool namesRoute = false, bool namesRoute = false,
bool scopesRoute = false, bool scopesRoute = false,
bool isHidden = false, bool isHidden = false,
...@@ -479,6 +480,8 @@ Matcher matchesSemantics({ ...@@ -479,6 +480,8 @@ Matcher matchesSemantics({
flags.add(SemanticsFlag.isHeader); flags.add(SemanticsFlag.isHeader);
if (isObscured) if (isObscured)
flags.add(SemanticsFlag.isObscured); flags.add(SemanticsFlag.isObscured);
if (isMultiline)
flags.add(SemanticsFlag.isMultiline);
if (namesRoute) if (namesRoute)
flags.add(SemanticsFlag.namesRoute); flags.add(SemanticsFlag.namesRoute);
if (scopesRoute) if (scopesRoute)
......
...@@ -560,7 +560,9 @@ void main() { ...@@ -560,7 +560,9 @@ void main() {
for (int index in SemanticsAction.values.keys) for (int index in SemanticsAction.values.keys)
actions |= index; actions |= index;
for (int index in SemanticsFlag.values.keys) for (int index in SemanticsFlag.values.keys)
flags |= index; // TODO(mdebbar): Remove this if after https://github.com/flutter/engine/pull/9894
if (SemanticsFlag.values[index] != SemanticsFlag.isMultiline)
flags |= index;
final SemanticsData data = SemanticsData( final SemanticsData data = SemanticsData(
flags: flags, flags: flags,
actions: actions, actions: actions,
...@@ -604,6 +606,8 @@ void main() { ...@@ -604,6 +606,8 @@ void main() {
isInMutuallyExclusiveGroup: true, isInMutuallyExclusiveGroup: true,
isHeader: true, isHeader: true,
isObscured: true, isObscured: true,
// TODO(mdebbar): Uncomment after https://github.com/flutter/engine/pull/9894
//isMultiline: true,
namesRoute: true, namesRoute: true,
scopesRoute: true, scopesRoute: true,
isHidden: true, isHidden: true,
......
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