Commit e0c24e26 authored by Ian Hickson's avatar Ian Hickson

Merge pull request #1706 from Hixie/accessibility

Fix drawer in accessibility mode
parents c91ace82 9cea6c50
...@@ -40,6 +40,7 @@ abstract class SemanticActionHandler { ...@@ -40,6 +40,7 @@ abstract class SemanticActionHandler {
enum _SemanticFlags { enum _SemanticFlags {
mergeAllDescendantsIntoThisNode, mergeAllDescendantsIntoThisNode,
inheritedMergeAllDescendantsIntoThisNode, // whether an ancestor had mergeAllDescendantsIntoThisNode set
canBeTapped, canBeTapped,
canBeLongPressed, canBeLongPressed,
canBeScrolledHorizontally, canBeScrolledHorizontally,
...@@ -119,6 +120,11 @@ class SemanticsNode extends AbstractNode { ...@@ -119,6 +120,11 @@ class SemanticsNode extends AbstractNode {
bool get mergeAllDescendantsIntoThisNode => _flags[_SemanticFlags.mergeAllDescendantsIntoThisNode]; bool get mergeAllDescendantsIntoThisNode => _flags[_SemanticFlags.mergeAllDescendantsIntoThisNode];
void set mergeAllDescendantsIntoThisNode(bool value) => _setFlag(_SemanticFlags.mergeAllDescendantsIntoThisNode, value); void set mergeAllDescendantsIntoThisNode(bool value) => _setFlag(_SemanticFlags.mergeAllDescendantsIntoThisNode, value);
bool get _inheritedMergeAllDescendantsIntoThisNode => _flags[_SemanticFlags.inheritedMergeAllDescendantsIntoThisNode];
void set _inheritedMergeAllDescendantsIntoThisNode(bool value) => _setFlag(_SemanticFlags.inheritedMergeAllDescendantsIntoThisNode, value);
bool get _shouldMergeAllDescendantsIntoThisNode => mergeAllDescendantsIntoThisNode || _inheritedMergeAllDescendantsIntoThisNode;
bool get canBeTapped => _flags[_SemanticFlags.canBeTapped]; bool get canBeTapped => _flags[_SemanticFlags.canBeTapped];
void set canBeTapped(bool value) => _setFlag(_SemanticFlags.canBeTapped, value, needsHandler: true); void set canBeTapped(bool value) => _setFlag(_SemanticFlags.canBeTapped, value, needsHandler: true);
...@@ -148,7 +154,10 @@ class SemanticsNode extends AbstractNode { ...@@ -148,7 +154,10 @@ class SemanticsNode extends AbstractNode {
} }
void reset() { void reset() {
bool hadInheritedMergeAllDescendantsIntoThisNode = _inheritedMergeAllDescendantsIntoThisNode;
_flags.reset(); _flags.reset();
if (hadInheritedMergeAllDescendantsIntoThisNode)
_inheritedMergeAllDescendantsIntoThisNode = true;
_label = ''; _label = '';
_markDirty(); _markDirty();
} }
...@@ -257,6 +266,8 @@ class SemanticsNode extends AbstractNode { ...@@ -257,6 +266,8 @@ class SemanticsNode extends AbstractNode {
assert(!_nodes.containsKey(_id)); assert(!_nodes.containsKey(_id));
_nodes[_id] = this; _nodes[_id] = this;
_detachedNodes.remove(this); _detachedNodes.remove(this);
if (parent != null)
_inheritedMergeAllDescendantsIntoThisNode = parent._shouldMergeAllDescendantsIntoThisNode;
if (_children != null) { if (_children != null) {
for (SemanticsNode child in _children) for (SemanticsNode child in _children)
child.attach(); child.attach();
...@@ -308,7 +319,7 @@ class SemanticsNode extends AbstractNode { ...@@ -308,7 +319,7 @@ class SemanticsNode extends AbstractNode {
result.strings = new mojom.SemanticStrings(); result.strings = new mojom.SemanticStrings();
result.strings.label = label; result.strings.label = label;
List<mojom.SemanticsNode> children = <mojom.SemanticsNode>[]; List<mojom.SemanticsNode> children = <mojom.SemanticsNode>[];
if (mergeAllDescendantsIntoThisNode) { if (_shouldMergeAllDescendantsIntoThisNode) {
_visitDescendants((SemanticsNode node) { _visitDescendants((SemanticsNode node) {
result.flags.canBeTapped = result.flags.canBeTapped || node.canBeTapped; result.flags.canBeTapped = result.flags.canBeTapped || node.canBeTapped;
result.flags.canBeLongPressed = result.flags.canBeLongPressed || node.canBeLongPressed; result.flags.canBeLongPressed = result.flags.canBeLongPressed || node.canBeLongPressed;
...@@ -318,6 +329,7 @@ class SemanticsNode extends AbstractNode { ...@@ -318,6 +329,7 @@ class SemanticsNode extends AbstractNode {
result.flags.isChecked = result.flags.isChecked || node.isChecked; result.flags.isChecked = result.flags.isChecked || node.isChecked;
if (node.label != '') if (node.label != '')
result.strings.label = result.strings.label.isNotEmpty ? '${result.strings.label}\n${node.label}' : node.label; result.strings.label = result.strings.label.isNotEmpty ? '${result.strings.label}\n${node.label}' : node.label;
node._dirty = false;
return true; // continue walk return true; // continue walk
}); });
// and we pretend to have no children // and we pretend to have no children
...@@ -359,16 +371,32 @@ class SemanticsNode extends AbstractNode { ...@@ -359,16 +371,32 @@ class SemanticsNode extends AbstractNode {
// we mutate the list as we walk it here, which is why we use an index instead of an iterator // we mutate the list as we walk it here, which is why we use an index instead of an iterator
SemanticsNode node = _dirtyNodes[index]; SemanticsNode node = _dirtyNodes[index];
assert(node._dirty); assert(node._dirty);
assert(node.parent == null || !node.parent.mergeAllDescendantsIntoThisNode || node.mergeAllDescendantsIntoThisNode); assert(node.parent == null || !node.parent._shouldMergeAllDescendantsIntoThisNode || node._inheritedMergeAllDescendantsIntoThisNode);
if (node.mergeAllDescendantsIntoThisNode) { if (node._shouldMergeAllDescendantsIntoThisNode) {
// if we're merged into our parent, make sure our parent is added to the list assert(node.mergeAllDescendantsIntoThisNode || node.parent != null);
if (node.parent != null && node.parent.mergeAllDescendantsIntoThisNode) if (node.mergeAllDescendantsIntoThisNode ||
node.parent._markDirty(); // this can add the node to the dirty list node.parent != null && node.parent._shouldMergeAllDescendantsIntoThisNode) {
// make sure all the descendants are also marked, so that if one gets marked dirty later we know to walk up then too // if we're merged into our parent, make sure our parent is added to the list
if (node._children != null) if (node.parent != null && node.parent._shouldMergeAllDescendantsIntoThisNode)
for (SemanticsNode child in node._children) node.parent._markDirty(); // this can add the node to the dirty list
child.mergeAllDescendantsIntoThisNode = true; // this can add the node to the dirty list // make sure all the descendants are also marked, so that if one gets marked dirty later we know to walk up then too
} if (node._children != null) {
for (SemanticsNode child in node._children)
child._inheritedMergeAllDescendantsIntoThisNode = true; // this can add the node to the dirty list
}
} else {
// we previously were being merged but aren't any more
// update our bits and all our descendants'
assert(node._inheritedMergeAllDescendantsIntoThisNode);
assert(!node.mergeAllDescendantsIntoThisNode);
assert(node.parent == null || !node.parent._shouldMergeAllDescendantsIntoThisNode);
node._inheritedMergeAllDescendantsIntoThisNode = false;
if (node._children != null) {
for (SemanticsNode child in node._children)
child._inheritedMergeAllDescendantsIntoThisNode = false; // this can add the node to the dirty list
}
}
}
assert(_dirtyNodes[index] == node); // make sure nothing went in front of us in the list assert(_dirtyNodes[index] == node); // make sure nothing went in front of us in the list
} }
_dirtyNodes.sort((SemanticsNode a, SemanticsNode b) => a.depth - b.depth); _dirtyNodes.sort((SemanticsNode a, SemanticsNode b) => a.depth - b.depth);
...@@ -396,7 +424,7 @@ class SemanticsNode extends AbstractNode { ...@@ -396,7 +424,7 @@ class SemanticsNode extends AbstractNode {
static SemanticActionHandler getSemanticActionHandlerForId(int id, { _SemanticFlags neededFlag }) { static SemanticActionHandler getSemanticActionHandlerForId(int id, { _SemanticFlags neededFlag }) {
assert(neededFlag != null); assert(neededFlag != null);
SemanticsNode result = _nodes[id]; SemanticsNode result = _nodes[id];
if (result != null && result.mergeAllDescendantsIntoThisNode && !result._canHandle(neededFlag)) { if (result != null && result._shouldMergeAllDescendantsIntoThisNode && !result._canHandle(neededFlag)) {
result._visitDescendants((SemanticsNode node) { result._visitDescendants((SemanticsNode node) {
if (node._actionHandler != null && node._flags[neededFlag]) { if (node._actionHandler != null && node._flags[neededFlag]) {
result = node; result = node;
...@@ -412,8 +440,8 @@ class SemanticsNode extends AbstractNode { ...@@ -412,8 +440,8 @@ class SemanticsNode extends AbstractNode {
String toString() { String toString() {
return '$runtimeType($_id' return '$runtimeType($_id'
'${_dirty ? " (dirty)" : ""}' '${_dirty ? " (${ _dirtyNodes.contains(this) ? 'dirty' : 'STALE' })" : ""}'
'${mergeAllDescendantsIntoThisNode ? " (leaf merge)" : ""}' '${_shouldMergeAllDescendantsIntoThisNode ? " (leaf merge)" : ""}'
'; $rect' '; $rect'
'${wasAffectedByClip ? " (clipped)" : ""}' '${wasAffectedByClip ? " (clipped)" : ""}'
'${canBeTapped ? "; canBeTapped" : ""}' '${canBeTapped ? "; canBeTapped" : ""}'
......
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:test/test.dart';
import 'test_semantics.dart';
import 'package:sky_services/semantics/semantics.mojom.dart' as mojom;
void main() {
test('Semantics 7 - Merging', () {
testWidgets((WidgetTester tester) {
TestSemanticsListener client = new TestSemanticsListener();
String label;
label = '1';
tester.pumpWidget(
new Stack(
children: <Widget>[
new MergeSemantics(
child: new Semantics(
checked: true,
container: true,
child: new Semantics(
container: true,
label: label
)
)
),
new MergeSemantics(
child: new Stack(
children: <Widget>[
new Semantics(
checked: true
),
new Semantics(
label: label
)
]
)
),
]
)
);
expect(client.updates.length, equals(2));
expect(client.updates[0].id, equals(0));
expect(client.updates[0].flags.canBeTapped, isFalse);
expect(client.updates[0].flags.canBeLongPressed, isFalse);
expect(client.updates[0].flags.canBeScrolledHorizontally, isFalse);
expect(client.updates[0].flags.canBeScrolledVertically, isFalse);
expect(client.updates[0].flags.hasCheckedState, isFalse);
expect(client.updates[0].flags.isChecked, isFalse);
expect(client.updates[0].strings.label, equals(''));
expect(client.updates[0].geometry.transform, isNull);
expect(client.updates[0].geometry.left, equals(0.0));
expect(client.updates[0].geometry.top, equals(0.0));
expect(client.updates[0].geometry.width, equals(800.0));
expect(client.updates[0].geometry.height, equals(600.0));
expect(client.updates[0].children.length, equals(2));
expect(client.updates[0].children[0].id, equals(1));
expect(client.updates[0].children[0].flags.canBeTapped, isFalse);
expect(client.updates[0].children[0].flags.canBeLongPressed, isFalse);
expect(client.updates[0].children[0].flags.canBeScrolledHorizontally, isFalse);
expect(client.updates[0].children[0].flags.canBeScrolledVertically, isFalse);
expect(client.updates[0].children[0].flags.hasCheckedState, isTrue);
expect(client.updates[0].children[0].flags.isChecked, isTrue);
expect(client.updates[0].children[0].strings.label, equals(label));
expect(client.updates[0].children[0].geometry.transform, isNull);
expect(client.updates[0].children[0].geometry.left, equals(0.0));
expect(client.updates[0].children[0].geometry.top, equals(0.0));
expect(client.updates[0].children[0].geometry.width, equals(800.0));
expect(client.updates[0].children[0].geometry.height, equals(600.0));
expect(client.updates[0].children[0].children.length, equals(0));
// IDs 2 and 3 are used up by the nodes that get merged in
expect(client.updates[0].children[1].id, equals(4));
expect(client.updates[0].children[1].flags.canBeTapped, isFalse);
expect(client.updates[0].children[1].flags.canBeLongPressed, isFalse);
expect(client.updates[0].children[1].flags.canBeScrolledHorizontally, isFalse);
expect(client.updates[0].children[1].flags.canBeScrolledVertically, isFalse);
expect(client.updates[0].children[1].flags.hasCheckedState, isTrue);
expect(client.updates[0].children[1].flags.isChecked, isTrue);
expect(client.updates[0].children[1].strings.label, equals(label));
expect(client.updates[0].children[1].geometry.transform, isNull);
expect(client.updates[0].children[1].geometry.left, equals(0.0));
expect(client.updates[0].children[1].geometry.top, equals(0.0));
expect(client.updates[0].children[1].geometry.width, equals(800.0));
expect(client.updates[0].children[1].geometry.height, equals(600.0));
expect(client.updates[0].children[1].children.length, equals(0));
// IDs 5 and 6 are used up by the nodes that get merged in
expect(client.updates[1], isNull);
client.updates.clear();
label = '2';
tester.pumpWidget(
new Stack(
children: <Widget>[
new MergeSemantics(
child: new Semantics(
checked: true,
container: true,
child: new Semantics(
container: true,
label: label
)
)
),
new MergeSemantics(
child: new Stack(
children: <Widget>[
new Semantics(
checked: true
),
new Semantics(
label: label
)
]
)
),
]
)
);
expect(client.updates.length, equals(3));
expect(client.updates[2], isNull);
// The order of the nodes is undefined, so allow both orders.
mojom.SemanticsNode a, b;
if (client.updates[0].id == 1) {
a = client.updates[0];
b = client.updates[1];
} else {
a = client.updates[1];
b = client.updates[0];
}
expect(a.id, equals(1));
expect(a.flags.canBeTapped, isFalse);
expect(a.flags.canBeLongPressed, isFalse);
expect(a.flags.canBeScrolledHorizontally, isFalse);
expect(a.flags.canBeScrolledVertically, isFalse);
expect(a.flags.hasCheckedState, isTrue);
expect(a.flags.isChecked, isTrue);
expect(a.strings.label, equals(label));
expect(a.geometry.transform, isNull);
expect(a.geometry.left, equals(0.0));
expect(a.geometry.top, equals(0.0));
expect(a.geometry.width, equals(800.0));
expect(a.geometry.height, equals(600.0));
expect(a.children.length, equals(0));
expect(b.id, equals(4));
expect(b.flags.canBeTapped, isFalse);
expect(b.flags.canBeLongPressed, isFalse);
expect(b.flags.canBeScrolledHorizontally, isFalse);
expect(b.flags.canBeScrolledVertically, isFalse);
expect(b.flags.hasCheckedState, isTrue);
expect(b.flags.isChecked, isTrue);
expect(b.strings.label, equals(label));
expect(b.geometry.transform, isNull);
expect(b.geometry.left, equals(0.0));
expect(b.geometry.top, equals(0.0));
expect(b.geometry.width, equals(800.0));
expect(b.geometry.height, equals(600.0));
expect(b.children.length, equals(0));
client.updates.clear();
});
});
}
// Copyright 2015 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:test/test.dart';
import 'test_semantics.dart';
void main() {
test('Semantics 8 - Merging with reset', () {
testWidgets((WidgetTester tester) {
TestSemanticsListener client = new TestSemanticsListener();
tester.pumpWidget(
new MergeSemantics(
child: new Semantics(
container: true,
child: new Semantics(
container: true,
child: new Stack(
children: <Widget>[
new Semantics(
checked: true
),
new Semantics(
label: 'label'
)
]
)
)
)
)
);
expect(client.updates.length, equals(2));
expect(client.updates[0].id, equals(0));
expect(client.updates[0].flags.canBeTapped, isFalse);
expect(client.updates[0].flags.canBeLongPressed, isFalse);
expect(client.updates[0].flags.canBeScrolledHorizontally, isFalse);
expect(client.updates[0].flags.canBeScrolledVertically, isFalse);
expect(client.updates[0].flags.hasCheckedState, isTrue);
expect(client.updates[0].flags.isChecked, isTrue);
expect(client.updates[0].strings.label, equals('label'));
expect(client.updates[0].geometry.transform, isNull);
expect(client.updates[0].geometry.left, equals(0.0));
expect(client.updates[0].geometry.top, equals(0.0));
expect(client.updates[0].geometry.width, equals(800.0));
expect(client.updates[0].geometry.height, equals(600.0));
expect(client.updates[0].children.length, equals(0));
expect(client.updates[1], isNull);
client.updates.clear();
// switch the order of the inner Semantics node to trigger a reset
tester.pumpWidget(
new MergeSemantics(
child: new Semantics(
container: true,
child: new Semantics(
container: true,
child: new Stack(
children: <Widget>[
new Semantics(
label: 'label'
),
new Semantics(
checked: true
)
]
)
)
)
)
);
expect(client.updates.length, equals(2));
expect(client.updates[0].id, equals(0));
expect(client.updates[0].flags.canBeTapped, isFalse);
expect(client.updates[0].flags.canBeLongPressed, isFalse);
expect(client.updates[0].flags.canBeScrolledHorizontally, isFalse);
expect(client.updates[0].flags.canBeScrolledVertically, isFalse);
expect(client.updates[0].flags.hasCheckedState, isTrue);
expect(client.updates[0].flags.isChecked, isTrue);
expect(client.updates[0].strings.label, equals('label'));
expect(client.updates[0].geometry.transform, isNull);
expect(client.updates[0].geometry.left, equals(0.0));
expect(client.updates[0].geometry.top, equals(0.0));
expect(client.updates[0].geometry.width, equals(800.0));
expect(client.updates[0].geometry.height, equals(600.0));
expect(client.updates[0].children.length, equals(0));
expect(client.updates[1], isNull);
client.updates.clear();
});
});
}
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