Unverified Commit 1ec44a0c authored by Kate Lovett's avatar Kate Lovett Committed by GitHub

Incorporating Link Semantics (#41327)

parent 14c1c211
c635d70c726699cc79c0f3d900e0d0a3ea65efe2
a29385d9fe81761f2cedba7d9d4aa4709db04d83
......@@ -831,6 +831,9 @@ class RenderCustomPaint extends RenderProxyBox {
if (properties.button != null) {
config.isButton = properties.button;
}
if (properties.link != null) {
config.isLink = properties.link;
}
if (properties.textField != null) {
config.isTextField = properties.textField;
}
......
......@@ -887,6 +887,7 @@ class RenderParagraph extends RenderBox
if (info.recognizer is TapGestureRecognizer) {
final TapGestureRecognizer recognizer = info.recognizer;
configuration.onTap = recognizer.onTap;
configuration.isLink = true;
} else if (info.recognizer is LongPressGestureRecognizer) {
final LongPressGestureRecognizer recognizer = info.recognizer;
configuration.onLongPress = recognizer.onLongPress;
......
......@@ -3485,6 +3485,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
bool toggled,
bool selected,
bool button,
bool link,
bool header,
bool textField,
bool readOnly,
......@@ -3536,6 +3537,7 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
_toggled = toggled,
_selected = selected,
_button = button,
_link = link,
_header = header,
_textField = textField,
_readOnly = readOnly,
......@@ -3678,6 +3680,16 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
markNeedsSemanticsUpdate();
}
/// If non-null, sets the [SemanticsNode.isLink] semantic to the given value.
bool get link => _link;
bool _link;
set link(bool value) {
if (link == value)
return;
_link = value;
markNeedsSemanticsUpdate();
}
/// If non-null, sets the [SemanticsNode.isHeader] semantic to the given value.
bool get header => _header;
bool _header;
......@@ -4360,6 +4372,8 @@ class RenderSemanticsAnnotations extends RenderProxyBox {
config.isSelected = selected;
if (button != null)
config.isButton = button;
if (link != null)
config.isLink = link;
if (header != null)
config.isHeader = header;
if (textField != null)
......
......@@ -591,6 +591,7 @@ class SemanticsProperties extends DiagnosticableTree {
this.selected,
this.toggled,
this.button,
this.link,
this.header,
this.textField,
this.readOnly,
......@@ -670,6 +671,13 @@ class SemanticsProperties extends DiagnosticableTree {
/// is focused.
final bool button;
/// If non-null, indicates that this subtree represents a link.
///
/// iOS's VoiceOver provides users with a unique hint when a link is focused.
/// Android's Talkback will announce a link hint the same way it does a
/// button.
final bool link;
/// If non-null, indicates that this subtree represents a header.
///
/// A header divides into sections. For example, an address book application
......@@ -3615,6 +3623,12 @@ class SemanticsConfiguration {
_setFlag(SemanticsFlag.isButton, value);
}
/// Whether the owning [RenderObject] is a link (true) or not (false).
bool get isLink => _hasFlag(SemanticsFlag.isLink);
set isLink(bool value) {
_setFlag(SemanticsFlag.isLink, value);
}
/// Whether the owning [RenderObject] is a header (true) or not (false).
bool get isHeader => _hasFlag(SemanticsFlag.isHeader);
set isHeader(bool value) {
......
......@@ -6184,6 +6184,7 @@ class Semantics extends SingleChildRenderObjectWidget {
bool selected,
bool toggled,
bool button,
bool link,
bool header,
bool textField,
bool readOnly,
......@@ -6237,6 +6238,7 @@ class Semantics extends SingleChildRenderObjectWidget {
toggled: toggled,
selected: selected,
button: button,
link: link,
header: header,
textField: textField,
readOnly: readOnly,
......@@ -6349,6 +6351,7 @@ class Semantics extends SingleChildRenderObjectWidget {
toggled: properties.toggled,
selected: properties.selected,
button: properties.button,
link: properties.link,
header: properties.header,
textField: properties.textField,
readOnly: properties.readOnly,
......@@ -6418,6 +6421,7 @@ class Semantics extends SingleChildRenderObjectWidget {
..toggled = properties.toggled
..selected = properties.selected
..button = properties.button
..link = properties.link
..header = properties.header
..textField = properties.textField
..readOnly = properties.readOnly
......
......@@ -4,6 +4,7 @@
import 'package:flutter/painting.dart';
import 'package:flutter/widgets.dart';
import '../flutter_test_alternative.dart';
void main() {
......
......@@ -4,6 +4,7 @@
import 'dart:ui' as ui show TextBox;
import 'package:flutter/gestures.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter/services.dart';
......
......@@ -573,6 +573,7 @@ void main() {
expect(config.isSemanticBoundary, isFalse);
expect(config.isButton, isFalse);
expect(config.isLink, isFalse);
expect(config.isMergingSemanticsOfDescendants, isFalse);
expect(config.isEnabled, null);
expect(config.isChecked, null);
......@@ -596,6 +597,7 @@ void main() {
config.isSemanticBoundary = true;
config.isButton = true;
config.isLink = true;
config.isMergingSemanticsOfDescendants = true;
config.isEnabled = true;
config.isChecked = true;
......@@ -632,6 +634,7 @@ void main() {
expect(config.isSemanticBoundary, isTrue);
expect(config.isButton, isTrue);
expect(config.isLink, isTrue);
expect(config.isMergingSemanticsOfDescendants, isTrue);
expect(config.isEnabled, isTrue);
expect(config.isChecked, isTrue);
......
......@@ -413,6 +413,7 @@ void _defineTests() {
selected: true,
hidden: true,
button: true,
link: true,
textField: true,
readOnly: true,
focused: true,
......@@ -461,6 +462,7 @@ void _defineTests() {
selected: true,
hidden: true,
button: true,
link: true,
textField: true,
readOnly: true,
focused: true,
......
......@@ -473,6 +473,7 @@ void main() {
checked: true,
selected: true,
button: true,
link: true,
textField: true,
readOnly: true,
focused: true,
......
......@@ -232,6 +232,7 @@ void main() {
TestSemantics(
label: 'Clickable',
actions: <SemanticsAction>[SemanticsAction.tap],
flags: <SemanticsFlag>[SemanticsFlag.isLink],
textDirection: TextDirection.ltr,
),
],
......@@ -281,9 +282,8 @@ void main() {
TestSemantics(
label: 'world',
textDirection: TextDirection.ltr,
actions: <SemanticsAction>[
SemanticsAction.tap,
],
actions: <SemanticsAction>[SemanticsAction.tap],
flags: <SemanticsFlag>[SemanticsFlag.isLink],
),
TestSemantics(
label: ' this is a cat-astrophe',
......@@ -338,6 +338,7 @@ void main() {
label: 'world',
textDirection: TextDirection.ltr,
actions: <SemanticsAction>[SemanticsAction.tap],
flags: <SemanticsFlag>[SemanticsFlag.isLink],
),
],
),
......@@ -389,9 +390,8 @@ void main() {
TestSemantics(
label: 'world',
textDirection: TextDirection.ltr,
actions: <SemanticsAction>[
SemanticsAction.tap,
],
actions: <SemanticsAction>[SemanticsAction.tap],
flags: <SemanticsFlag>[SemanticsFlag.isLink],
),
TestSemantics(
label: ' this is a regrettable event',
......@@ -454,9 +454,8 @@ void main() {
rect: const Rect.fromLTRB(150.0, -4.0, 200.0, 18.0),
label: 'RIS',
textDirection: TextDirection.rtl, // in the last string we switched to RTL using RLE.
actions: <SemanticsAction>[
SemanticsAction.tap,
],
actions: <SemanticsAction>[SemanticsAction.tap],
flags: <SemanticsFlag>[SemanticsFlag.isLink],
),
TestSemantics(
rect: const Rect.fromLTRB(192.0, -4.0, 424.0, 18.0),
......@@ -491,6 +490,46 @@ void main() {
semantics.dispose();
}, skip: true); // TODO(jonahwilliams): correct once https://github.com/flutter/flutter/issues/20891 is resolved.
testWidgets('TapGesture recognizers contribute link semantics', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
const TextStyle textStyle = TextStyle(fontFamily: 'Ahem');
await tester.pumpWidget(
Text.rich(
TextSpan(
children: <TextSpan>[
TextSpan(
text: 'click me',
recognizer: TapGestureRecognizer()..onTap = () { },
),
],
style: textStyle,
),
textDirection: TextDirection.ltr,
),
);
final TestSemantics expectedSemantics = TestSemantics.root(
children: <TestSemantics>[
TestSemantics.rootChild(
children: <TestSemantics>[
TestSemantics(
label: 'click me',
textDirection: TextDirection.ltr,
actions: <SemanticsAction>[SemanticsAction.tap],
flags: <SemanticsFlag>[SemanticsFlag.isLink]
),
],
),
],
);
expect(semantics, hasSemantics(
expectedSemantics,
ignoreTransform: true,
ignoreId: true,
ignoreRect: true,
));
semantics.dispose();
});
testWidgets('inline widgets generate semantic nodes', (WidgetTester tester) async {
final SemanticsTester semantics = SemanticsTester(tester);
const TextStyle textStyle = TextStyle(fontFamily: 'Ahem');
......@@ -534,9 +573,8 @@ void main() {
TestSemantics(
label: 'pebble',
textDirection: TextDirection.ltr,
actions: <SemanticsAction>[
SemanticsAction.tap,
],
actions: <SemanticsAction>[SemanticsAction.tap],
flags: <SemanticsFlag>[SemanticsFlag.isLink],
),
TestSemantics(
label: ' in the ',
......@@ -612,9 +650,8 @@ void main() {
TestSemantics(
label: 'pebble',
textDirection: TextDirection.ltr,
actions: <SemanticsAction>[
SemanticsAction.tap,
],
actions: <SemanticsAction>[SemanticsAction.tap],
flags: <SemanticsFlag>[SemanticsFlag.isLink],
rect: const Rect.fromLTRB(52.0, 48.0, 228.0, 84.0),
),
TestSemantics(
......
......@@ -440,6 +440,7 @@ Matcher matchesSemantics({
bool isChecked = false,
bool isSelected = false,
bool isButton = false,
bool isLink = false,
bool isFocused = false,
bool isTextField = false,
bool isReadOnly = false,
......@@ -489,6 +490,7 @@ Matcher matchesSemantics({
if (isChecked) SemanticsFlag.isChecked,
if (isSelected) SemanticsFlag.isSelected,
if (isButton) SemanticsFlag.isButton,
if (isLink) SemanticsFlag.isLink,
if (isTextField) SemanticsFlag.isTextField,
if (isReadOnly) SemanticsFlag.isReadOnly,
if (isFocused) SemanticsFlag.isFocused,
......
......@@ -412,6 +412,7 @@ void main() {
namesRoute: true,
header: true,
button: true,
link: true,
onTap: () { },
onLongPress: () { },
label: 'foo',
......@@ -439,6 +440,7 @@ void main() {
hasTapAction: true,
hasLongPressAction: true,
isButton: true,
isLink: true,
isHeader: true,
namesRoute: true,
onTapHint: 'scan',
......@@ -460,6 +462,7 @@ void main() {
hasTapAction: true,
hasLongPressAction: true,
isButton: true,
isLink: true,
isHeader: true,
namesRoute: true,
onTapHint: 'scan',
......@@ -481,6 +484,7 @@ void main() {
hasTapAction: true,
hasLongPressAction: true,
isButton: true,
isLink: true,
isHeader: true,
namesRoute: true,
onTapHint: 'scans',
......@@ -544,6 +548,7 @@ void main() {
isChecked: true,
isSelected: true,
isButton: true,
isLink: true,
isTextField: true,
isReadOnly: true,
hasEnabledState: 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