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 {
if (properties.obscured != null) {
config.isObscured = properties.obscured;
}
if (properties.multiline != null) {
config.isMultiline = properties.multiline;
}
if (properties.hidden != null) {
config.isHidden = properties.hidden;
}
......@@ -924,6 +927,12 @@ class RenderCustomPaint extends RenderProxyBox {
if (properties.onMoveCursorBackwardByCharacter != null) {
config.onMoveCursorBackwardByCharacter = properties.onMoveCursorBackwardByCharacter;
}
if (properties.onMoveCursorForwardByWord != null) {
config.onMoveCursorForwardByWord = properties.onMoveCursorForwardByWord;
}
if (properties.onMoveCursorBackwardByWord != null) {
config.onMoveCursorBackwardByWord = properties.onMoveCursorBackwardByWord;
}
if (properties.onSetSelection != null) {
config.onSetSelection = properties.onSetSelection;
}
......
......@@ -992,6 +992,7 @@ class RenderEditable extends RenderBox {
? obscuringCharacter * text.toPlainText().length
: text.toPlainText()
..isObscured = obscureText
..isMultiline = _isMultiline
..textDirection = textDirection
..isFocused = hasFocus
..isTextField = true;
......
......@@ -3430,6 +3430,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
bool focused,
bool inMutuallyExclusiveGroup,
bool obscured,
bool multiline,
bool scopesRoute,
bool namesRoute,
bool hidden,
......@@ -3478,6 +3479,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
_focused = focused,
_inMutuallyExclusiveGroup = inMutuallyExclusiveGroup,
_obscured = obscured,
_multiline = multiline,
_scopesRoute = scopesRoute,
_namesRoute = namesRoute,
_liveRegion = liveRegion,
......@@ -3673,6 +3675,17 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
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.
bool get scopesRoute => _scopesRoute;
bool _scopesRoute;
......@@ -4272,6 +4285,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
config.isInMutuallyExclusiveGroup = inMutuallyExclusiveGroup;
if (obscured != null)
config.isObscured = obscured;
if (multiline != null)
config.isMultiline = multiline;
if (hidden != null)
config.isHidden = hidden;
if (image != null)
......
......@@ -570,6 +570,7 @@ class SemanticsProperties extends DiagnosticableTree {
this.inMutuallyExclusiveGroup,
this.hidden,
this.obscured,
this.multiline,
this.scopesRoute,
this.namesRoute,
this.image,
......@@ -700,6 +701,15 @@ class SemanticsProperties extends DiagnosticableTree {
/// Doing so instructs screen readers to not read out the [value].
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
/// which a route name should be announced.
///
......@@ -1654,6 +1664,11 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
TextSelection get 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.
///
/// If the number of children are unknown or unbounded, this value will be
......@@ -1678,7 +1693,6 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
double get scrollPosition => _scrollPosition;
double _scrollPosition;
/// Indicates the maximum in-range value for [scrollPosition] if the node is
/// scrollable.
///
......@@ -1756,6 +1770,7 @@ class SemanticsNode extends AbstractNode with DiagnosticableTreeMixin {
_customSemanticsActions = Map<CustomSemanticsAction, VoidCallback>.from(config._customSemanticsActions);
_actionsAsBits = config._actionsAsBits;
_textSelection = config._textSelection;
_isMultiline = config.isMultiline;
_scrollPosition = config._scrollPosition;
_scrollExtentMax = config._scrollExtentMax;
_scrollExtentMin = config._scrollExtentMin;
......@@ -3531,6 +3546,15 @@ class SemanticsConfiguration {
_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
/// to move focus to an offscreen child.
///
......
......@@ -5926,6 +5926,7 @@ class Semantics extends SingleChildRenderObjectWidget {
bool focused,
bool inMutuallyExclusiveGroup,
bool obscured,
bool multiline,
bool scopesRoute,
bool namesRoute,
bool hidden,
......@@ -5976,6 +5977,7 @@ class Semantics extends SingleChildRenderObjectWidget {
focused: focused,
inMutuallyExclusiveGroup: inMutuallyExclusiveGroup,
obscured: obscured,
multiline: multiline,
scopesRoute: scopesRoute,
namesRoute: namesRoute,
hidden: hidden,
......@@ -6086,6 +6088,7 @@ class Semantics extends SingleChildRenderObjectWidget {
liveRegion: properties.liveRegion,
inMutuallyExclusiveGroup: properties.inMutuallyExclusiveGroup,
obscured: properties.obscured,
multiline: properties.multiline,
scopesRoute: properties.scopesRoute,
namesRoute: properties.namesRoute,
hidden: properties.hidden,
......@@ -6151,6 +6154,7 @@ class Semantics extends SingleChildRenderObjectWidget {
..focused = properties.focused
..inMutuallyExclusiveGroup = properties.inMutuallyExclusiveGroup
..obscured = properties.obscured
..multiline = properties.multiline
..hidden = properties.hidden
..image = properties.image
..liveRegion = properties.liveRegion
......
......@@ -341,6 +341,8 @@ void _defineTests() {
onPaste: () => performedActions.add(SemanticsAction.paste),
onMoveCursorForwardByCharacter: (bool _) => performedActions.add(SemanticsAction.moveCursorForwardByCharacter),
onMoveCursorBackwardByCharacter: (bool _) => performedActions.add(SemanticsAction.moveCursorBackwardByCharacter),
onMoveCursorForwardByWord: (bool _) => performedActions.add(SemanticsAction.moveCursorForwardByWord),
onMoveCursorBackwardByWord: (bool _) => performedActions.add(SemanticsAction.moveCursorBackwardByWord),
onSetSelection: (TextSelection _) => performedActions.add(SemanticsAction.setSelection),
onDidGainAccessibilityFocus: () => performedActions.add(SemanticsAction.didGainAccessibilityFocus),
onDidLoseAccessibilityFocus: () => performedActions.add(SemanticsAction.didLoseAccessibilityFocus),
......@@ -349,8 +351,6 @@ void _defineTests() {
),
));
final Set<SemanticsAction> allActions = SemanticsAction.values.values.toSet()
..remove(SemanticsAction.moveCursorForwardByWord)
..remove(SemanticsAction.moveCursorBackwardByWord)
..remove(SemanticsAction.customAction) // customAction is not user-exposed.
..remove(SemanticsAction.showOnScreen); // showOnScreen is not user-exposed
......@@ -378,6 +378,8 @@ void _defineTests() {
switch (action) {
case SemanticsAction.moveCursorBackwardByCharacter:
case SemanticsAction.moveCursorForwardByCharacter:
case SemanticsAction.moveCursorBackwardByWord:
case SemanticsAction.moveCursorForwardByWord:
semanticsOwner.performAction(expectedId, action, true);
break;
case SemanticsAction.setSelection:
......@@ -417,20 +419,24 @@ void _defineTests() {
inMutuallyExclusiveGroup: true,
header: true,
obscured: true,
// TODO(mdebbar): Uncomment after https://github.com/flutter/engine/pull/9894
//multiline: true,
scopesRoute: true,
namesRoute: true,
image: true,
liveRegion: true,
toggled: true,
),
),
),
));
List<SemanticsFlag> flags = SemanticsFlag.values.values.toList();
// [SemanticsFlag.hasImplicitScrolling] isn't part of [SemanticsProperties]
// therefore it has to be removed.
flags
..remove(SemanticsFlag.hasImplicitScrolling)
..remove(SemanticsFlag.hasToggledState)
..remove(SemanticsFlag.hasImplicitScrolling)
..remove(SemanticsFlag.isToggled);
// TODO(mdebbar): Remove this line after https://github.com/flutter/engine/pull/9894
..remove(SemanticsFlag.isMultiline)
..remove(SemanticsFlag.hasImplicitScrolling);
TestSemantics expectedSemantics = TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
......@@ -454,6 +460,7 @@ void _defineTests() {
rect: Rect.fromLTRB(1.0, 2.0, 3.0, 4.0),
properties: SemanticsProperties(
enabled: true,
checked: true,
toggled: true,
selected: true,
hidden: true,
......@@ -464,6 +471,8 @@ void _defineTests() {
inMutuallyExclusiveGroup: true,
header: true,
obscured: true,
// TODO(mdebbar): Uncomment after https://github.com/flutter/engine/pull/9894
//multiline: true,
scopesRoute: true,
namesRoute: true,
image: true,
......@@ -473,11 +482,12 @@ void _defineTests() {
),
));
flags = SemanticsFlag.values.values.toList();
// [SemanticsFlag.hasImplicitScrolling] isn't part of [SemanticsProperties]
// therefore it has to be removed.
flags
..remove(SemanticsFlag.hasImplicitScrolling)
..remove(SemanticsFlag.hasCheckedState)
..remove(SemanticsFlag.hasImplicitScrolling)
..remove(SemanticsFlag.isChecked);
// TODO(mdebbar): Remove this line after https://github.com/flutter/engine/pull/9894
..remove(SemanticsFlag.isMultiline)
..remove(SemanticsFlag.hasImplicitScrolling);
expectedSemantics = TestSemantics.root(
children: <TestSemantics>[
......
......@@ -910,6 +910,67 @@ void main() {
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 {
final SemanticsTester semantics = SemanticsTester(tester);
......
......@@ -479,6 +479,8 @@ void main() {
inMutuallyExclusiveGroup: true,
header: true,
obscured: true,
// TODO(mdebbar): Uncomment after https://github.com/flutter/engine/pull/9894
//multiline: true,
scopesRoute: true,
namesRoute: true,
image: true,
......@@ -487,6 +489,8 @@ void main() {
);
final List<SemanticsFlag> flags = SemanticsFlag.values.values.toList();
flags
// TODO(mdebbar): Remove this line after https://github.com/flutter/engine/pull/9894
..remove(SemanticsFlag.isMultiline)
..remove(SemanticsFlag.hasToggledState)
..remove(SemanticsFlag.isToggled)
..remove(SemanticsFlag.hasImplicitScrolling);
......
......@@ -419,6 +419,7 @@ Matcher matchesSemantics({
bool isInMutuallyExclusiveGroup = false,
bool isHeader = false,
bool isObscured = false,
bool isMultiline = false,
bool namesRoute = false,
bool scopesRoute = false,
bool isHidden = false,
......@@ -479,6 +480,8 @@ Matcher matchesSemantics({
flags.add(SemanticsFlag.isHeader);
if (isObscured)
flags.add(SemanticsFlag.isObscured);
if (isMultiline)
flags.add(SemanticsFlag.isMultiline);
if (namesRoute)
flags.add(SemanticsFlag.namesRoute);
if (scopesRoute)
......
......@@ -560,7 +560,9 @@ void main() {
for (int index in SemanticsAction.values.keys)
actions |= index;
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(
flags: flags,
actions: actions,
......@@ -604,6 +606,8 @@ void main() {
isInMutuallyExclusiveGroup: true,
isHeader: true,
isObscured: true,
// TODO(mdebbar): Uncomment after https://github.com/flutter/engine/pull/9894
//isMultiline: true,
namesRoute: true,
scopesRoute: 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