Unverified Commit ffb24eda authored by Yegor's avatar Yegor Committed by GitHub

Accessibility API for CustomPainter (#13313)

Summary:

- Add `key` field to `SemanticsNode`, while moving key into `foundation` library so it can be used by the render layer.
- Introduce `SemanticsProperties` and move many of the `Semantics` fields into it.
- Introduce `CustomPaintSemantics` - a `SemanticsNode` prototype created by `CustomPainter`.
- Introduce `semanticsBuilder` and `shouldRebuildSemantics` in `CustomerPainter`

**Breaking change**

The default `Semantics` constructor becomes non-const (due to https://github.com/dart-lang/sdk/issues/20962). However, a new `const Semantics.fromProperties` is added that still allowed creating constant `Semantics` widgets ([mailing list announcement](https://groups.google.com/forum/#!topic/flutter-dev/KQXBl2_1sws)).

Fixes https://github.com/flutter/flutter/issues/11791
Fixes https://github.com/flutter/flutter/issues/1666
parent a69af990
......@@ -39,6 +39,7 @@ export 'src/foundation/change_notifier.dart';
export 'src/foundation/collections.dart';
export 'src/foundation/debug.dart';
export 'src/foundation/diagnostics.dart';
export 'src/foundation/key.dart';
export 'src/foundation/licenses.dart';
export 'src/foundation/node.dart';
export 'src/foundation/observer_list.dart';
......
......@@ -35,6 +35,7 @@ export 'src/rendering/animated_size.dart';
export 'src/rendering/binding.dart';
export 'src/rendering/box.dart';
export 'src/rendering/custom_layout.dart';
export 'src/rendering/custom_paint.dart';
export 'src/rendering/debug.dart';
export 'src/rendering/debug_overflow_indicator.dart';
export 'src/rendering/editable.dart';
......
// Copyright 2017 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 'dart:ui' show hashValues;
import 'package:meta/meta.dart';
/// A [Key] is an identifier for [Widget]s, [Element]s and [SemanticsNode]s.
///
/// A new widget will only be used to update an existing element if its key is
/// the same as the key of the current widget associated with the element.
///
/// Keys must be unique amongst the [Element]s with the same parent.
///
/// Subclasses of [Key] should either subclass [LocalKey] or [GlobalKey].
///
/// See also the discussion at [Widget.key].
@immutable
abstract class Key {
/// Construct a [ValueKey<String>] with the given [String].
///
/// This is the simplest way to create keys.
const factory Key(String value) = ValueKey<String>;
/// Default constructor, used by subclasses.
///
/// Useful so that subclasses can call us, because the [new Key] factory
/// constructor shadows the implicit constructor.
@protected
const Key.empty();
}
/// A key that is not a [GlobalKey].
///
/// Keys must be unique amongst the [Element]s with the same parent. By
/// contrast, [GlobalKey]s must be unique across the entire app.
///
/// See also the discussion at [Widget.key].
abstract class LocalKey extends Key {
/// Default constructor, used by subclasses.
const LocalKey() : super.empty();
}
/// A key that uses a value of a particular type to identify itself.
///
/// A [ValueKey<T>] is equal to another [ValueKey<T>] if, and only if, their
/// values are [operator==].
///
/// This class can be subclassed to create value keys that will not be equal to
/// other value keys that happen to use the same value. If the subclass is
/// private, this results in a value key type that cannot collide with keys from
/// other sources, which could be useful, for example, if the keys are being
/// used as fallbacks in the same scope as keys supplied from another widget.
///
/// See also the discussion at [Widget.key].
class ValueKey<T> extends LocalKey {
/// Creates a key that delegates its [operator==] to the given value.
const ValueKey(this.value);
/// The value to which this key delegates its [operator==]
final T value;
@override
bool operator ==(dynamic other) {
if (other.runtimeType != runtimeType)
return false;
final ValueKey<T> typedOther = other;
return value == typedOther.value;
}
@override
int get hashCode => hashValues(runtimeType, value);
@override
String toString() {
final String valueString = T == String ? '<\'$value\'>' : '<$value>';
// The crazy on the next line is a workaround for
// https://github.com/dart-lang/sdk/issues/28548
if (runtimeType == new _TypeLiteral<ValueKey<T>>().type)
return '[$valueString]';
return '[$T $valueString]';
}
}
class _TypeLiteral<T> { Type get type => T; }
......@@ -6,6 +6,7 @@ import 'dart:async';
import 'dart:math' as math;
import 'package:flutter/foundation.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/widgets.dart';
import 'theme.dart';
......@@ -214,4 +215,10 @@ class _ScrollbarPainter extends ChangeNotifier implements CustomPainter {
@override
bool shouldRepaint(_ScrollbarPainter oldDelegate) => false;
@override
bool shouldRebuildSemantics(CustomPainter oldDelegate) => false;
@override
SemanticsBuilderCallback get semanticsBuilder => null;
}
This diff is collapsed.
......@@ -2362,7 +2362,7 @@ abstract class RenderObject extends AbstractNode with DiagnosticableTreeMixin im
///
/// If [isSemanticBoundary] is true, this method is called with the `node`
/// created for this [RenderObject], the `config` to be applied to that node
/// and the `children` [SemanticNode]s that decedents of this RenderObject
/// and the `children` [SemanticNode]s that descendants of this RenderObject
/// have generated.
///
/// By default, the method will annotate `node` with `config` and add the
......
This diff is collapsed.
......@@ -17,6 +17,7 @@ export 'dart:ui' show hashValues, hashList;
export 'package:flutter/foundation.dart' show FlutterError, debugPrint, debugPrintStack;
export 'package:flutter/foundation.dart' show VoidCallback, ValueChanged, ValueGetter, ValueSetter;
export 'package:flutter/foundation.dart' show DiagnosticLevel;
export 'package:flutter/foundation.dart' show Key, LocalKey, ValueKey;
export 'package:flutter/rendering.dart' show RenderObject, RenderBox, debugDumpRenderTree, debugDumpLayerTree;
// Examples can assume:
......@@ -30,84 +31,6 @@ export 'package:flutter/rendering.dart' show RenderObject, RenderBox, debugDumpR
// KEYS
/// A [Key] is an identifier for [Widget]s and [Element]s.
///
/// A new widget will only be used to update an existing element if its key is
/// the same as the key of the current widget associated with the element.
///
/// Keys must be unique amongst the [Element]s with the same parent.
///
/// Subclasses of [Key] should either subclass [LocalKey] or [GlobalKey].
///
/// See also the discussion at [Widget.key].
@immutable
abstract class Key {
/// Construct a [ValueKey<String>] with the given [String].
///
/// This is the simplest way to create keys.
const factory Key(String value) = ValueKey<String>;
/// Default constructor, used by subclasses.
///
/// Useful so that subclasses can call us, because the Key() factory
/// constructor shadows the implicit constructor.
const Key._();
}
/// A key that is not a [GlobalKey].
///
/// Keys must be unique amongst the [Element]s with the same parent. By
/// contrast, [GlobalKey]s must be unique across the entire app.
///
/// See also the discussion at [Widget.key].
abstract class LocalKey extends Key {
/// Default constructor, used by subclasses.
const LocalKey() : super._();
}
/// A key that uses a value of a particular type to identify itself.
///
/// A [ValueKey<T>] is equal to another [ValueKey<T>] if, and only if, their
/// values are [operator==].
///
/// This class can be subclassed to create value keys that will not be equal to
/// other value keys that happen to use the same value. If the subclass is
/// private, this results in a value key type that cannot collide with keys from
/// other sources, which could be useful, for example, if the keys are being
/// used as fallbacks in the same scope as keys supplied from another widget.
///
/// See also the discussion at [Widget.key].
class ValueKey<T> extends LocalKey {
/// Creates a key that delegates its [operator==] to the given value.
const ValueKey(this.value);
/// The value to which this key delegates its [operator==]
final T value;
@override
bool operator ==(dynamic other) {
if (other.runtimeType != runtimeType)
return false;
final ValueKey<T> typedOther = other;
return value == typedOther.value;
}
@override
int get hashCode => hashValues(runtimeType, value);
@override
String toString() {
final String valueString = T == String ? '<\'$value\'>' : '<$value>';
// The crazy on the next line is a workaround for
// https://github.com/dart-lang/sdk/issues/28548
if (runtimeType == new _TypeLiteral<ValueKey<T>>().type)
return '[$valueString]';
return '[$T $valueString]';
}
}
class _TypeLiteral<T> { Type get type => T; }
/// A key that is only equal to itself.
class UniqueKey extends LocalKey {
/// Creates a key that is equal only to itself.
......@@ -183,7 +106,7 @@ abstract class GlobalKey<T extends State<StatefulWidget>> extends Key {
///
/// Used by subclasses because the factory constructor shadows the implicit
/// constructor.
const GlobalKey.constructor() : super._();
const GlobalKey.constructor() : super.empty();
static final Map<GlobalKey, Element> _registry = <GlobalKey, Element>{};
static final Set<GlobalKey> _removedKeys = new HashSet<GlobalKey>();
......@@ -4287,9 +4210,10 @@ abstract class RenderObjectElement extends Element {
return forgottenChildren != null && forgottenChildren.contains(child) ? null : child;
}
// This attempts to diff the new child list (this.children) with
// the old child list (old.children), and update our renderObject
// accordingly.
// This attempts to diff the new child list (newWidgets) with
// the old child list (oldChildren), and produce a new list of elements to
// be the new list of child elements of this element. The called of this
// method is expected to update this render object accordingly.
// The cases it tries to optimize for are:
// - the old list is empty
......@@ -4305,13 +4229,13 @@ abstract class RenderObjectElement extends Element {
// 2. Walk the lists from the bottom, without syncing nodes, until you no
// longer have matching nodes. We'll sync these nodes at the end. We
// don't sync them now because we want to sync all the nodes in order
// from beginning ot end.
// from beginning to end.
// At this point we narrowed the old and new lists to the point
// where the nodes no longer match.
// 3. Walk the narrowed part of the old list to get the list of
// keys and sync null with non-keyed items.
// 4. Walk the narrowed part of the new list forwards:
// * Sync unkeyed items with null
// * Sync non-keyed items with null
// * Sync keyed items with the source if it exists, else with null.
// 5. Walk the bottom of the list again, syncing the nodes.
// 6. Sync null with any items in the list of keys that are still
......@@ -4378,7 +4302,7 @@ abstract class RenderObjectElement extends Element {
if (haveOldChildren) {
final Key key = newWidget.key;
if (key != null) {
oldChild = oldKeyedChildren[newWidget.key];
oldChild = oldKeyedChildren[key];
if (oldChild != null) {
if (Widget.canUpdate(oldChild.widget, newWidget)) {
// we found a match!
......@@ -4400,7 +4324,7 @@ abstract class RenderObjectElement extends Element {
newChildrenTop += 1;
}
// We've scaned the whole list.
// We've scanned the whole list.
assert(oldChildrenTop == oldChildrenBottom + 1);
assert(newChildrenTop == newChildrenBottom + 1);
assert(newWidgets.length - newChildrenTop == oldChildren.length - oldChildrenTop);
......@@ -4423,7 +4347,7 @@ abstract class RenderObjectElement extends Element {
oldChildrenTop += 1;
}
// clean up any of the remaining middle nodes from the old list
// Clean up any of the remaining middle nodes from the old list.
if (haveOldChildren && oldKeyedChildren.isNotEmpty) {
for (Element oldChild in oldKeyedChildren.values) {
if (forgottenChildren == null || !forgottenChildren.contains(oldChild))
......
This diff is collapsed.
......@@ -47,10 +47,10 @@ Widget buildTestWidgets({bool excludeSemantics, String label, bool isSemanticsBo
isSemanticBoundary: isSemanticsBoundary,
child: new Column(
children: <Widget>[
const Semantics(
new Semantics(
label: 'child1',
),
const Semantics(
new Semantics(
label: 'child2',
),
],
......
......@@ -14,12 +14,12 @@ void main() {
final SemanticsTester semantics = new SemanticsTester(tester);
await tester.pumpWidget(
const Semantics(
new Semantics(
container: true,
onTap: dummyTapHandler,
child: const Semantics(
child: new Semantics(
onTap: dummyTapHandler,
child: const Semantics(
child: new Semantics(
onTap: dummyTapHandler,
textDirection: TextDirection.ltr,
label: 'foo',
......@@ -54,12 +54,12 @@ void main() {
// This should not throw an assert.
await tester.pumpWidget(
const Semantics(
new Semantics(
container: true,
onTap: dummyTapHandler,
child: const Semantics(
child: new Semantics(
onTap: dummyTapHandler,
child: const Semantics(
child: new Semantics(
onTap: dummyTapHandler,
textDirection: TextDirection.ltr,
label: 'bar', // <-- only change
......
......@@ -50,7 +50,7 @@ void main() {
children: <Widget>[
new Container(
height: 10.0,
child: const Semantics(
child: new Semantics(
label: 'child1',
textDirection: TextDirection.ltr,
selected: true,
......@@ -58,9 +58,9 @@ void main() {
),
new Container(
height: 10.0,
child: const IgnorePointer(
child: new IgnorePointer(
ignoring: true,
child: const Semantics(
child: new Semantics(
label: 'child1',
textDirection: TextDirection.ltr,
selected: true,
......@@ -92,7 +92,7 @@ void main() {
children: <Widget>[
new Container(
height: 10.0,
child: const Semantics(
child: new Semantics(
label: 'child1',
textDirection: TextDirection.ltr,
selected: true,
......@@ -100,9 +100,9 @@ void main() {
),
new Container(
height: 10.0,
child: const IgnorePointer(
child: new IgnorePointer(
ignoring: false,
child: const Semantics(
child: new Semantics(
label: 'child2',
textDirection: TextDirection.ltr,
selected: true,
......@@ -146,7 +146,7 @@ void main() {
children: <Widget>[
new Container(
height: 10.0,
child: const Semantics(
child: new Semantics(
label: 'child1',
textDirection: TextDirection.ltr,
selected: true,
......@@ -154,9 +154,9 @@ void main() {
),
new Container(
height: 10.0,
child: const IgnorePointer(
child: new IgnorePointer(
ignoring: true,
child: const Semantics(
child: new Semantics(
label: 'child2',
textDirection: TextDirection.ltr,
selected: true,
......@@ -188,7 +188,7 @@ void main() {
children: <Widget>[
new Container(
height: 10.0,
child: const Semantics(
child: new Semantics(
label: 'child1',
textDirection: TextDirection.ltr,
selected: true,
......@@ -196,9 +196,9 @@ void main() {
),
new Container(
height: 10.0,
child: const IgnorePointer(
child: new IgnorePointer(
ignoring: false,
child: const Semantics(
child: new Semantics(
label: 'child2',
textDirection: TextDirection.ltr,
selected: true,
......
......@@ -28,7 +28,7 @@ void main() {
children: <Widget>[
new Container(
height: 10.0,
child: const Semantics(
child: new Semantics(
label: 'child1',
textDirection: TextDirection.ltr,
selected: true,
......@@ -36,9 +36,9 @@ void main() {
),
new Container(
height: 10.0,
child: const IgnorePointer(
child: new IgnorePointer(
ignoring: false,
child: const Semantics(
child: new Semantics(
label: 'child2',
textDirection: TextDirection.ltr,
selected: true,
......@@ -82,7 +82,7 @@ void main() {
children: <Widget>[
new Container(
height: 10.0,
child: const Semantics(
child: new Semantics(
label: 'child1',
textDirection: TextDirection.ltr,
selected: true,
......@@ -90,9 +90,9 @@ void main() {
),
new Container(
height: 10.0,
child: const IgnorePointer(
child: new IgnorePointer(
ignoring: true,
child: const Semantics(
child: new Semantics(
label: 'child2',
textDirection: TextDirection.ltr,
selected: true,
......@@ -124,7 +124,7 @@ void main() {
children: <Widget>[
new Container(
height: 10.0,
child: const Semantics(
child: new Semantics(
label: 'child1',
textDirection: TextDirection.ltr,
selected: true,
......@@ -132,9 +132,9 @@ void main() {
),
new Container(
height: 10.0,
child: const IgnorePointer(
child: new IgnorePointer(
ignoring: false,
child: const Semantics(
child: new Semantics(
label: 'child2',
textDirection: TextDirection.ltr,
selected: true,
......
......@@ -23,7 +23,7 @@ void main() {
label: 'test',
textDirection: TextDirection.ltr,
child: new Container(
child: const Semantics(
child: new Semantics(
checked: true
),
),
......@@ -50,7 +50,7 @@ void main() {
new Semantics(
container: true,
child: new Container(
child: const Semantics(
child: new Semantics(
checked: true,
),
),
......@@ -74,7 +74,7 @@ void main() {
new Semantics(
container: true,
child: new Container(
child: const Semantics(
child: new Semantics(
label: 'test',
textDirection: TextDirection.ltr,
),
......@@ -100,9 +100,9 @@ void main() {
new Semantics(
container: true,
child: new Container(
child: const Semantics(
child: new Semantics(
checked: true,
child: const Semantics(
child: new Semantics(
label: 'test',
textDirection: TextDirection.ltr,
),
......@@ -134,9 +134,9 @@ void main() {
new Semantics(
container: true,
child: new Container(
child: const Semantics(
child: new Semantics(
checked: true,
child: const Semantics(
child: new Semantics(
label: 'test',
textDirection: TextDirection.ltr,
),
......
......@@ -26,7 +26,7 @@ void main() {
child: new Stack(
fit: StackFit.expand,
children: <Widget>[
const Semantics(
new Semantics(
container: true,
label: 'L1',
),
......@@ -36,10 +36,10 @@ void main() {
child: new Stack(
fit: StackFit.expand,
children: <Widget>[
const Semantics(
new Semantics(
checked: true,
),
const Semantics(
new Semantics(
checked: false,
),
],
......@@ -88,7 +88,7 @@ void main() {
child: new Stack(
fit: StackFit.expand,
children: <Widget>[
const Semantics(
new Semantics(
label: 'L1',
container: true,
),
......@@ -98,10 +98,10 @@ void main() {
child: new Stack(
fit: StackFit.expand,
children: <Widget>[
const Semantics(
new Semantics(
checked: true,
),
const Semantics(),
new Semantics(),
],
),
),
......@@ -136,17 +136,17 @@ void main() {
child: new Stack(
fit: StackFit.expand,
children: <Widget>[
const Semantics(),
new Semantics(),
new Semantics(
label: 'L2',
container: true,
child: new Stack(
fit: StackFit.expand,
children: <Widget>[
const Semantics(
new Semantics(
checked: true,
),
const Semantics(),
new Semantics(),
],
),
),
......
......@@ -17,14 +17,14 @@ void main() {
textDirection: TextDirection.ltr,
fit: StackFit.expand,
children: <Widget>[
const Semantics(
new Semantics(
// this tests that empty nodes disappear
),
const Semantics(
new Semantics(
// this tests whether you can have a container with no other semantics
container: true,
),
const Semantics(
new Semantics(
label: 'label', // (force a fork)
textDirection: TextDirection.ltr,
),
......
......@@ -37,7 +37,7 @@ void main() {
child: new Stack(
fit: StackFit.expand,
children: <Widget>[
const Semantics(
new Semantics(
checked: true,
),
new Semantics(
......@@ -93,7 +93,7 @@ void main() {
child: new Stack(
fit: StackFit.expand,
children: <Widget>[
const Semantics(
new Semantics(
checked: true,
),
new Semantics(
......
......@@ -23,10 +23,10 @@ void main() {
child: new Stack(
textDirection: TextDirection.ltr,
children: <Widget>[
const Semantics(
new Semantics(
checked: true,
),
const Semantics(
new Semantics(
label: 'label',
textDirection: TextDirection.ltr,
)
......@@ -61,11 +61,11 @@ void main() {
child: new Stack(
textDirection: TextDirection.ltr,
children: <Widget>[
const Semantics(
new Semantics(
label: 'label',
textDirection: TextDirection.ltr,
),
const Semantics(
new Semantics(
checked: true
)
]
......
......@@ -16,11 +16,11 @@ void main() {
textDirection: TextDirection.ltr,
child: new Stack(
children: <Widget>[
const Semantics(),
const Semantics(
new Semantics(),
new Semantics(
container: true,
),
const Semantics(
new Semantics(
label: 'label',
textDirection: TextDirection.ltr,
),
......@@ -35,11 +35,11 @@ void main() {
child: new SemanticsDebugger(
child: new Stack(
children: <Widget>[
const Semantics(),
const Semantics(
new Semantics(),
new Semantics(
container: true,
),
const Semantics(
new Semantics(
label: 'label',
textDirection: TextDirection.ltr,
),
......@@ -62,14 +62,14 @@ void main() {
child: new SemanticsDebugger(
child: new Stack(
children: <Widget>[
const Semantics(label: 'label1', textDirection: TextDirection.ltr),
new Semantics(label: 'label1', textDirection: TextDirection.ltr),
new Positioned(
key: key,
left: 0.0,
top: 0.0,
width: 100.0,
height: 100.0,
child: const Semantics(label: 'label2', textDirection: TextDirection.ltr),
child: new Semantics(label: 'label2', textDirection: TextDirection.ltr),
),
],
),
......@@ -83,7 +83,7 @@ void main() {
child: new SemanticsDebugger(
child: new Stack(
children: <Widget>[
const Semantics(label: 'label1', textDirection: TextDirection.ltr),
new Semantics(label: 'label1', textDirection: TextDirection.ltr),
new Semantics(
container: true,
child: new Stack(
......@@ -94,9 +94,9 @@ void main() {
top: 0.0,
width: 100.0,
height: 100.0,
child: const Semantics(label: 'label2', textDirection: TextDirection.ltr),
child: new Semantics(label: 'label2', textDirection: TextDirection.ltr),
),
const Semantics(label: 'label3', textDirection: TextDirection.ltr),
new Semantics(label: 'label3', textDirection: TextDirection.ltr),
],
),
),
......@@ -112,7 +112,7 @@ void main() {
child: new SemanticsDebugger(
child: new Stack(
children: <Widget>[
const Semantics(label: 'label1', textDirection: TextDirection.ltr),
new Semantics(label: 'label1', textDirection: TextDirection.ltr),
new Semantics(
container: true,
child: new Stack(
......@@ -123,9 +123,9 @@ void main() {
top: 0.0,
width: 100.0,
height: 100.0,
child: const Semantics(label: 'label2', textDirection: TextDirection.ltr)),
const Semantics(label: 'label3', textDirection: TextDirection.ltr),
const Semantics(label: 'label4', textDirection: TextDirection.ltr),
child: new Semantics(label: 'label2', textDirection: TextDirection.ltr)),
new Semantics(label: 'label3', textDirection: TextDirection.ltr),
new Semantics(label: 'label4', textDirection: TextDirection.ltr),
],
),
),
......
......@@ -262,10 +262,10 @@ void main() {
container: true,
child: new Column(
children: <Widget>[
const Semantics(
new Semantics(
hint: 'hint one',
),
const Semantics(
new Semantics(
hint: 'hint two',
)
......@@ -347,10 +347,10 @@ void main() {
container: true,
child: new Column(
children: <Widget>[
const Semantics(
new Semantics(
hint: 'hint',
),
const Semantics(
new Semantics(
value: 'value',
),
],
......
......@@ -196,7 +196,7 @@ class TestSemantics {
final SemanticsData nodeData = node.getSemanticsData();
bool fail(String message) {
matchState[TestSemantics] = '$message\n$_matcherHelp';
matchState[TestSemantics] = '$message';
return false;
}
......@@ -246,8 +246,29 @@ class TestSemantics {
}
@override
String toString() {
return 'node $id, flags=$flags, actions=$actions, label="$label", textDirection=$textDirection, rect=$rect, transform=$transform, ${children.length} child${ children.length == 1 ? "" : "ren" }';
String toString([int indentAmount = 0]) {
final String indent = ' ' * indentAmount;
final StringBuffer buf = new StringBuffer();
buf.writeln('$indent$runtimeType {');
if (id != null)
buf.writeln('$indent id: $id');
buf.writeln('$indent flags: $flags');
buf.writeln('$indent actions: $actions');
if (label != null)
buf.writeln('$indent label: "$label"');
if (textDirection != null)
buf.writeln('$indent textDirection: $textDirection');
if (rect != null)
buf.writeln('$indent rect: $rect');
if (transform != null)
buf.writeln('$indent transform:\n${transform.toString().trim().split('\n').map((String line) => '$indent $line').join('\n')}');
buf.writeln('$indent children: [');
for (TestSemantics child in children) {
buf.writeln(child.toString(indentAmount + 2));
}
buf.writeln('$indent ]');
buf.write('$indent}');
return buf.toString();
}
}
......@@ -295,12 +316,17 @@ class _HasSemantics extends Matcher {
@override
Description describe(Description description) {
return description.add('semantics node matching: $_semantics');
return description.add('semantics node matching:\n$_semantics');
}
@override
Description describeMismatch(dynamic item, Description mismatchDescription, Map<dynamic, dynamic> matchState, bool verbose) {
return mismatchDescription.add(matchState[TestSemantics]);
return mismatchDescription
.add('${matchState[TestSemantics]}\n')
.add(
'Current SemanticsNode tree:\n'
)
.add(RendererBinding.instance?.renderView?.debugSemantics?.toStringDeep(childOrder: DebugSemanticsDumpOrder.inverseHitTest));
}
}
......
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