mirror of
https://github.com/flutter/flutter.git
synced 2026-02-20 02:29:02 +08:00
Switch to the new semantics backend (#6259)
This match switches the framework to use the semantics backend in `dart:ui` rather than the Mojo backend.
This commit is contained in:
parent
0975d04972
commit
f5b9d388cd
@ -365,7 +365,7 @@ class ThemeData {
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator==(Object other) {
|
||||
bool operator ==(Object other) {
|
||||
if (other.runtimeType != runtimeType)
|
||||
return false;
|
||||
ThemeData otherData = other;
|
||||
|
||||
@ -9,8 +9,6 @@ import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:mojo/core.dart' as core;
|
||||
import 'package:flutter_services/semantics.dart' as mojom;
|
||||
|
||||
import 'box.dart';
|
||||
import 'debug.dart';
|
||||
@ -28,12 +26,15 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding,
|
||||
_instance = this;
|
||||
_pipelineOwner = new PipelineOwner(
|
||||
onNeedVisualUpdate: ensureVisualUpdate,
|
||||
onScheduleInitialSemantics: _scheduleInitialSemantics,
|
||||
onClearSemantics: _clearSemantics,
|
||||
onSemanticsOwnerCreated: _handleSemanticsOwnerCreated,
|
||||
onSemanticsOwnerDisposed: _handleSemanticsOwnerDisposed,
|
||||
);
|
||||
ui.window.onMetricsChanged = handleMetricsChanged;
|
||||
ui.window
|
||||
..onMetricsChanged = handleMetricsChanged
|
||||
..onSemanticsEnabledChanged = _handleSemanticsEnabledChanged
|
||||
..onSemanticsAction = _handleSemanticsAction;
|
||||
initRenderView();
|
||||
initSemantics();
|
||||
_handleSemanticsEnabledChanged();
|
||||
assert(renderView != null);
|
||||
addPersistentFrameCallback(_handlePersistentFrameCallback);
|
||||
}
|
||||
@ -134,18 +135,27 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding,
|
||||
);
|
||||
}
|
||||
|
||||
/// Prepares the rendering library to handle semantics requests from the engine.
|
||||
///
|
||||
/// Called automatically when the binding is created.
|
||||
void initSemantics() {
|
||||
shell.provideService(mojom.SemanticsServer.serviceName, (core.MojoMessagePipeEndpoint endpoint) {
|
||||
mojom.SemanticsServerStub stub = new mojom.SemanticsServerStub.fromEndpoint(endpoint);
|
||||
SemanticsServer server = new SemanticsServer(pipelineOwner);
|
||||
stub.impl = server;
|
||||
stub.ctrl.onError = (_) {
|
||||
server.dispose();
|
||||
};
|
||||
});
|
||||
SemanticsHandle _semanticsHandle;
|
||||
|
||||
void _handleSemanticsEnabledChanged() {
|
||||
if (ui.window.semanticsEnabled) {
|
||||
_semanticsHandle ??= _pipelineOwner.ensureSemantics();
|
||||
} else {
|
||||
_semanticsHandle?.dispose();
|
||||
_semanticsHandle = null;
|
||||
}
|
||||
}
|
||||
|
||||
void _handleSemanticsAction(int id, SemanticsAction action) {
|
||||
_pipelineOwner.semanticsOwner?.performAction(id, action);
|
||||
}
|
||||
|
||||
void _handleSemanticsOwnerCreated() {
|
||||
renderView.scheduleInitialSemantics();
|
||||
}
|
||||
|
||||
void _handleSemanticsOwnerDisposed() {
|
||||
renderView.clearSemantics();
|
||||
}
|
||||
|
||||
void _handlePersistentFrameCallback(Duration timeStamp) {
|
||||
@ -208,7 +218,7 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding,
|
||||
pipelineOwner.flushCompositingBits();
|
||||
pipelineOwner.flushPaint();
|
||||
renderView.compositeFrame(); // this sends the bits to the GPU
|
||||
pipelineOwner.flushSemantics();
|
||||
pipelineOwner.flushSemantics(); // this also sends the semantics to the OS.
|
||||
}
|
||||
|
||||
@override
|
||||
@ -234,14 +244,6 @@ abstract class RendererBinding extends BindingBase implements SchedulerBinding,
|
||||
};
|
||||
instance?.renderView?.visitChildren(visitor);
|
||||
}
|
||||
|
||||
void _scheduleInitialSemantics() {
|
||||
renderView.scheduleInitialSemantics();
|
||||
}
|
||||
|
||||
void _clearSemantics() {
|
||||
renderView.clearSemantics();
|
||||
}
|
||||
}
|
||||
|
||||
/// Prints a textual representation of the entire render tree.
|
||||
@ -261,44 +263,6 @@ void debugDumpSemanticsTree() {
|
||||
debugPrint(RendererBinding.instance?.renderView?.debugSemantics?.toStringDeep() ?? 'Semantics not collected.');
|
||||
}
|
||||
|
||||
/// Exposes the [SemanticsNode] tree to the underlying platform.
|
||||
class SemanticsServer extends mojom.SemanticsServer {
|
||||
/// Creates a semantics server that listens to semantic informationa about the
|
||||
/// given [PipelineOwner].
|
||||
///
|
||||
/// Call [dispose] to stop listening for semantic updates.
|
||||
SemanticsServer(PipelineOwner pipelineOwner) {
|
||||
_semanticsOwner = pipelineOwner.addSemanticsListener(_updateSemanticsTree);
|
||||
}
|
||||
|
||||
SemanticsOwner _semanticsOwner;
|
||||
final List<mojom.SemanticsListenerProxy> _listeners = <mojom.SemanticsListenerProxy>[];
|
||||
|
||||
/// Stops listening for semantic updates and closes all outstanding listeners.
|
||||
void dispose() {
|
||||
for (mojom.SemanticsListenerProxy listener in _listeners)
|
||||
listener.close();
|
||||
_listeners.clear();
|
||||
_semanticsOwner.removeListener(_updateSemanticsTree);
|
||||
_semanticsOwner = null;
|
||||
}
|
||||
|
||||
void _updateSemanticsTree(List<mojom.SemanticsNode> nodes) {
|
||||
for (mojom.SemanticsListenerProxy listener in _listeners)
|
||||
listener.updateSemanticsTree(nodes);
|
||||
}
|
||||
|
||||
@override
|
||||
void addSemanticsListener(@checked mojom.SemanticsListenerProxy listener) {
|
||||
_listeners.add(listener);
|
||||
}
|
||||
|
||||
@override
|
||||
void performAction(int id, mojom.SemanticAction encodedAction) {
|
||||
_semanticsOwner.performAction(id, SemanticsAction.values[encodedAction.mojoEnumValue]);
|
||||
}
|
||||
}
|
||||
|
||||
/// A concrete binding for applications that use the Rendering framework
|
||||
/// directly. This is the glue that binds the framework to the Flutter engine.
|
||||
///
|
||||
|
||||
@ -732,6 +732,36 @@ class _ForkingSemanticsFragment extends _SemanticsFragment {
|
||||
}
|
||||
}
|
||||
|
||||
class SemanticsHandle {
|
||||
SemanticsHandle._(this._owner, this.listener) {
|
||||
assert(_owner != null);
|
||||
if (listener != null)
|
||||
_owner.semanticsOwner.addListener(listener);
|
||||
}
|
||||
|
||||
PipelineOwner _owner;
|
||||
final VoidCallback listener;
|
||||
|
||||
@mustCallSuper
|
||||
void dispose() {
|
||||
assert(() {
|
||||
if (_owner == null) {
|
||||
throw new FlutterError(
|
||||
'SemanticsHandle has already been disposed.\n'
|
||||
'Each SemanticsHandle should be disposed exactly once.'
|
||||
);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
if (_owner != null) {
|
||||
if (listener != null)
|
||||
_owner.semanticsOwner.removeListener(listener);
|
||||
_owner._didDisposeSemanticsHandle();
|
||||
_owner = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// The pipeline owner manages the rendering pipeline.
|
||||
///
|
||||
/// The pipeline owner provides an interface for driving the rendering pipeline
|
||||
@ -769,8 +799,8 @@ class PipelineOwner {
|
||||
/// through the rendering pipeline.
|
||||
PipelineOwner({
|
||||
this.onNeedVisualUpdate,
|
||||
this.onScheduleInitialSemantics,
|
||||
this.onClearSemantics,
|
||||
this.onSemanticsOwnerCreated,
|
||||
this.onSemanticsOwnerDisposed,
|
||||
});
|
||||
|
||||
/// Called when a render object associated with this pipeline owner wishes to
|
||||
@ -782,20 +812,16 @@ class PipelineOwner {
|
||||
/// duplicate calls quickly.
|
||||
final VoidCallback onNeedVisualUpdate;
|
||||
|
||||
/// Called when [addSemanticsListener] is called when there was no
|
||||
/// [SemanticsOwner] present, to request that the
|
||||
/// [RenderObject.scheduleInitialSemantics] method be called on the
|
||||
/// appropriate object(s).
|
||||
/// Called whenever this pipeline owner creates as semantics object.
|
||||
///
|
||||
/// For example, the [RendererBinding] calls it on the [RenderView] object.
|
||||
final VoidCallback onScheduleInitialSemantics;
|
||||
/// Typical implementations will schedule the creation of the initial
|
||||
/// semantics tree.
|
||||
final VoidCallback onSemanticsOwnerCreated;
|
||||
|
||||
/// Called when the last [SemanticsListener] is removed from the
|
||||
/// [SemanticsOwner], to request that the [RenderObject.clearSemantics] method
|
||||
/// be called on the appropriate object(s).
|
||||
/// Called whenever this pipeline owner disposes its semantics owner.
|
||||
///
|
||||
/// For example, the [RendererBinding] calls it on the [RenderView] object.
|
||||
final VoidCallback onClearSemantics;
|
||||
/// Typical implementations will tear down the semantics tree.
|
||||
final VoidCallback onSemanticsOwnerDisposed;
|
||||
|
||||
/// Calls [onNeedVisualUpdate] if [onNeedVisualUpdate] is not null.
|
||||
///
|
||||
@ -819,32 +845,6 @@ class PipelineOwner {
|
||||
_rootNode?.attach(this);
|
||||
}
|
||||
|
||||
/// Calls the given listener whenever the semantics of the render tree change.
|
||||
///
|
||||
/// Creates [semanticsOwner] if necessary.
|
||||
SemanticsOwner addSemanticsListener(SemanticsListener listener) {
|
||||
if (_semanticsOwner == null) {
|
||||
_semanticsOwner = new SemanticsOwner(
|
||||
initialListener: listener,
|
||||
onLastListenerRemoved: _handleLastSemanticsListenerRemoved
|
||||
);
|
||||
if (onScheduleInitialSemantics != null)
|
||||
onScheduleInitialSemantics();
|
||||
} else {
|
||||
_semanticsOwner.addListener(listener);
|
||||
}
|
||||
assert(_semanticsOwner != null);
|
||||
return _semanticsOwner;
|
||||
}
|
||||
|
||||
void _handleLastSemanticsListenerRemoved() {
|
||||
assert(!_debugDoingSemantics);
|
||||
if (onClearSemantics != null)
|
||||
onClearSemantics();
|
||||
_semanticsOwner.dispose();
|
||||
_semanticsOwner = null;
|
||||
}
|
||||
|
||||
List<RenderObject> _nodesNeedingLayout = <RenderObject>[];
|
||||
|
||||
/// Whether this pipeline is currently in the layout phase.
|
||||
@ -953,18 +953,39 @@ class PipelineOwner {
|
||||
|
||||
/// The object that is managing semantics for this pipeline owner, if any.
|
||||
///
|
||||
/// An owner is created by [addSemanticsListener] the first time a listener is
|
||||
/// added.
|
||||
///
|
||||
/// The owner is valid for as long as there are listeners. Once the last
|
||||
/// listener is removed (by calling [SemanticsOwner.removeListener] on the
|
||||
/// [semanticsOwner]), the [semanticsOwner] field will revert to null, and the
|
||||
/// previous owner will be disposed.
|
||||
/// An owner is created by [ensureSemantics]. The owner is valid for as long
|
||||
/// there are [SemanticsHandle] returned by [ensureSemantics] that have not
|
||||
/// yet be disposed. Once the last handle has been disposed, the
|
||||
/// [semanticsOwner] field will revert to null, and the previous owner will be
|
||||
/// disposed.
|
||||
///
|
||||
/// When [semanticsOwner] is null, the [PipelineOwner] skips all steps
|
||||
/// relating to semantics.
|
||||
SemanticsOwner get semanticsOwner => _semanticsOwner;
|
||||
SemanticsOwner _semanticsOwner;
|
||||
|
||||
int _outstandingSemanticsHandle = 0;
|
||||
|
||||
SemanticsHandle ensureSemantics({ VoidCallback listener }) {
|
||||
if (_outstandingSemanticsHandle++ == 0) {
|
||||
assert(_semanticsOwner == null);
|
||||
_semanticsOwner = new SemanticsOwner();
|
||||
if (onSemanticsOwnerCreated != null)
|
||||
onSemanticsOwnerCreated();
|
||||
}
|
||||
return new SemanticsHandle._(this, listener);
|
||||
}
|
||||
|
||||
void _didDisposeSemanticsHandle() {
|
||||
assert(_semanticsOwner != null);
|
||||
if (--_outstandingSemanticsHandle == 0) {
|
||||
_semanticsOwner.dispose();
|
||||
_semanticsOwner = null;
|
||||
if (onSemanticsOwnerDisposed != null)
|
||||
onSemanticsOwnerDisposed();
|
||||
}
|
||||
}
|
||||
|
||||
bool _debugDoingSemantics = false;
|
||||
final List<RenderObject> _nodesNeedingSemantics = <RenderObject>[];
|
||||
|
||||
@ -987,12 +1008,12 @@ class PipelineOwner {
|
||||
if (node._needsSemanticsUpdate && node.owner == this)
|
||||
node._updateSemantics();
|
||||
}
|
||||
_semanticsOwner.sendSemanticsUpdate();
|
||||
} finally {
|
||||
_nodesNeedingSemantics.clear();
|
||||
assert(() { _debugDoingSemantics = false; return true; });
|
||||
Timeline.finishSync();
|
||||
}
|
||||
_semanticsOwner.sendSemanticsTree();
|
||||
}
|
||||
}
|
||||
|
||||
@ -1996,9 +2017,6 @@ abstract class RenderObject extends AbstractNode implements HitTestTarget {
|
||||
|
||||
/// Removes all semantics from this render object and its descendants.
|
||||
///
|
||||
/// Should only be called in response to the [PipelineOwner] calling its
|
||||
/// [PipelineOwner.onClearSemantics] callback.
|
||||
///
|
||||
/// Should only be called on objects whose [parent] is not a [RenderObject].
|
||||
void clearSemantics() {
|
||||
_needsSemanticsUpdate = true;
|
||||
|
||||
@ -2,13 +2,13 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui' as ui;
|
||||
import 'dart:ui' show Rect, SemanticsAction, SemanticsFlags;
|
||||
import 'dart:typed_data';
|
||||
|
||||
import 'package:flutter_services/semantics.dart' as mojom;
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/painting.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
|
||||
import 'node.dart';
|
||||
@ -57,14 +57,19 @@ typedef bool SemanticsNodeVisitor(SemanticsNode node);
|
||||
class SemanticsData {
|
||||
/// Creates a semantics data object.
|
||||
///
|
||||
/// The [flags], [actions], [label], and [rect] arguments must not be null.
|
||||
const SemanticsData({
|
||||
/// The [flags], [actions], [label], and [Rect] arguments must not be null.
|
||||
SemanticsData({
|
||||
@required this.flags,
|
||||
@required this.actions,
|
||||
@required this.label,
|
||||
@required this.rect,
|
||||
this.transform
|
||||
});
|
||||
}) {
|
||||
assert(flags != null);
|
||||
assert(actions != null);
|
||||
assert(label != null);
|
||||
assert(rect != null);
|
||||
}
|
||||
|
||||
/// A bit field of [SemanticsFlags] that apply to this node.
|
||||
final int flags;
|
||||
@ -90,6 +95,41 @@ class SemanticsData {
|
||||
|
||||
/// Whether [actions] contains the given action.
|
||||
bool hasAction(SemanticsAction action) => (actions & action.index) != 0;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
StringBuffer buffer = new StringBuffer();
|
||||
buffer.write('$runtimeType($rect');
|
||||
if (transform != null)
|
||||
buffer.write('; $transform');
|
||||
for (SemanticsAction action in SemanticsAction.values.values) {
|
||||
if ((actions & action.index) != 0)
|
||||
buffer.write('; $action');
|
||||
}
|
||||
for (SemanticsFlags flag in SemanticsFlags.values.values) {
|
||||
if ((flags & flag.index) != 0)
|
||||
buffer.write('; $flag');
|
||||
}
|
||||
if (label.isNotEmpty)
|
||||
buffer.write('; "$label"');
|
||||
buffer.write(')');
|
||||
return buffer.toString();
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(dynamic other) {
|
||||
if (other is! SemanticsData)
|
||||
return false;
|
||||
final SemanticsData typedOther = other;
|
||||
return typedOther.flags == flags
|
||||
&& typedOther.actions == actions
|
||||
&& typedOther.label == label
|
||||
&& typedOther.rect == rect
|
||||
&& typedOther.transform == transform;
|
||||
}
|
||||
|
||||
@override
|
||||
int get hashCode => hashValues(flags, actions, label, rect, transform);
|
||||
}
|
||||
|
||||
/// A node that represents some semantic data.
|
||||
@ -307,6 +347,9 @@ class SemanticsNode extends AbstractNode {
|
||||
bool get hasChildren => _children?.isNotEmpty ?? false;
|
||||
bool _dead = false;
|
||||
|
||||
/// The number of children this node has.
|
||||
int get childrenCount => hasChildren ? _children.length : 0;
|
||||
|
||||
/// Visits the immediate children of this node.
|
||||
///
|
||||
/// This function calls visitor for each child in a pre-order travseral
|
||||
@ -477,53 +520,33 @@ class SemanticsNode extends AbstractNode {
|
||||
);
|
||||
}
|
||||
|
||||
mojom.SemanticsNode _serialize() {
|
||||
mojom.SemanticsNode result = new mojom.SemanticsNode();
|
||||
result.id = id;
|
||||
if (_dirty) {
|
||||
// We could be even more efficient about not sending data here, by only
|
||||
// sending the bits that are dirty (tracking the geometry, flags, strings,
|
||||
// and children separately). For now, we send all or nothing.
|
||||
result.geometry = new mojom.SemanticGeometry();
|
||||
result.geometry.transform = transform?.storage;
|
||||
result.geometry.top = rect.top;
|
||||
result.geometry.left = rect.left;
|
||||
result.geometry.width = math.max(rect.width, 0.0);
|
||||
result.geometry.height = math.max(rect.height, 0.0);
|
||||
result.flags = new mojom.SemanticFlags();
|
||||
result.flags.hasCheckedState = hasCheckedState;
|
||||
result.flags.isChecked = isChecked;
|
||||
result.strings = new mojom.SemanticStrings();
|
||||
result.strings.label = label;
|
||||
List<mojom.SemanticsNode> children = <mojom.SemanticsNode>[];
|
||||
int mergedActions = _actions;
|
||||
if (_shouldMergeAllDescendantsIntoThisNode) {
|
||||
_visitDescendants((SemanticsNode node) {
|
||||
mergedActions |= node._actions;
|
||||
result.flags.hasCheckedState = result.flags.hasCheckedState || node.hasCheckedState;
|
||||
result.flags.isChecked = result.flags.isChecked || node.isChecked;
|
||||
if (node.label != '')
|
||||
result.strings.label = result.strings.label.isNotEmpty ? '${result.strings.label}\n${node.label}' : node.label;
|
||||
node._dirty = false;
|
||||
return true; // continue walk
|
||||
});
|
||||
// and we pretend to have no children
|
||||
} else {
|
||||
if (_children != null) {
|
||||
for (SemanticsNode child in _children)
|
||||
children.add(child._serialize());
|
||||
}
|
||||
}
|
||||
result.children = children;
|
||||
result.actions = <int>[];
|
||||
for (mojom.SemanticAction action in mojom.SemanticAction.values) {
|
||||
int bit = 1 << action.mojoEnumValue;
|
||||
if ((mergedActions & bit) != 0)
|
||||
result.actions.add(action.mojoEnumValue);
|
||||
}
|
||||
_dirty = false;
|
||||
static Float64List _initIdentityTransform() {
|
||||
return new Matrix4.identity().storage;
|
||||
}
|
||||
|
||||
static final Int32List _kEmptyChildList = new Int32List(0);
|
||||
static final Float64List _kIdentityTransform = _initIdentityTransform();
|
||||
|
||||
void _addToUpdate(ui.SemanticsUpdateBuilder builder) {
|
||||
assert(_dirty);
|
||||
final SemanticsData data = getSemanticsData();
|
||||
Int32List children;
|
||||
if (!hasChildren || mergeAllDescendantsIntoThisNode) {
|
||||
children = _kEmptyChildList;
|
||||
} else {
|
||||
final int childCount = _children.length;
|
||||
children = new Int32List(childCount);
|
||||
for (int i = 0; i < childCount; ++i)
|
||||
children[i] = _children[i].id;
|
||||
}
|
||||
return result;
|
||||
builder.updateNode(
|
||||
id: id,
|
||||
flags: data.flags,
|
||||
actions: data.actions,
|
||||
rect: data.rect,
|
||||
transform: data.transform?.storage ?? _kIdentityTransform,
|
||||
);
|
||||
_dirty = false;
|
||||
}
|
||||
|
||||
@override
|
||||
@ -567,73 +590,31 @@ class SemanticsNode extends AbstractNode {
|
||||
}
|
||||
}
|
||||
|
||||
/// Signature for functions that receive updates about render tree semantics.
|
||||
typedef void SemanticsListener(List<mojom.SemanticsNode> nodes);
|
||||
|
||||
/// Owns [SemanticsNode] objects and notifies listeners of changes to the
|
||||
/// render tree semantics.
|
||||
///
|
||||
/// To listen for semantic updates, call [PipelineOwner.addSemanticsListener],
|
||||
/// which will create a [SemanticsOwner] if necessary.
|
||||
class SemanticsOwner {
|
||||
/// Creates a [SemanticsOwner].
|
||||
///
|
||||
/// The `onLastListenerRemoved` argument must not be null and will be called
|
||||
/// when the last listener is removed from this object.
|
||||
SemanticsOwner({
|
||||
@required SemanticsListener initialListener,
|
||||
@required VoidCallback onLastListenerRemoved
|
||||
}) : _onLastListenerRemoved = onLastListenerRemoved {
|
||||
assert(_onLastListenerRemoved != null);
|
||||
addListener(initialListener);
|
||||
}
|
||||
|
||||
final VoidCallback _onLastListenerRemoved;
|
||||
|
||||
class SemanticsOwner extends ChangeNotifier {
|
||||
final Set<SemanticsNode> _dirtyNodes = new Set<SemanticsNode>();
|
||||
final Map<int, SemanticsNode> _nodes = <int, SemanticsNode>{};
|
||||
final Set<SemanticsNode> _detachedNodes = new Set<SemanticsNode>();
|
||||
|
||||
final List<SemanticsListener> _listeners = <SemanticsListener>[];
|
||||
|
||||
/// The root node of the semantics tree, if any.
|
||||
///
|
||||
/// If the semantics tree is empty, returns null.
|
||||
SemanticsNode get rootSemanticsNode => _nodes[0];
|
||||
|
||||
/// Releases any resources retained by this object.
|
||||
///
|
||||
/// Requires that there are no listeners registered with [addListener].
|
||||
@override
|
||||
void dispose() {
|
||||
assert(_listeners.isEmpty);
|
||||
_dirtyNodes.clear();
|
||||
_nodes.clear();
|
||||
_detachedNodes.clear();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
/// Add a consumer of semantic data.
|
||||
///
|
||||
/// After the [PipelineOwner] updates the semantic data for a given frame, it
|
||||
/// calls [sendSemanticsTree], which uploads the data to each listener
|
||||
/// registered with this function.
|
||||
///
|
||||
/// Listeners can be removed with [removeListener].
|
||||
void addListener(SemanticsListener listener) {
|
||||
_listeners.add(listener);
|
||||
}
|
||||
|
||||
/// Removes a consumer of semantic data.
|
||||
///
|
||||
/// Listeners can be added with [addListener].
|
||||
void removeListener(SemanticsListener listener) {
|
||||
_listeners.remove(listener);
|
||||
if (_listeners.isEmpty)
|
||||
_onLastListenerRemoved();
|
||||
}
|
||||
|
||||
/// Uploads the semantics tree to the listeners registered with [addListener].
|
||||
void sendSemanticsTree() {
|
||||
assert(_listeners.isNotEmpty);
|
||||
/// Update the semantics using [ui.window.updateSemantics].
|
||||
void sendSemanticsUpdate() {
|
||||
for (SemanticsNode oldNode in _detachedNodes) {
|
||||
// The other side will have forgotten this node if we even send
|
||||
// it again, so make sure to mark it dirty so that it'll get
|
||||
@ -680,7 +661,7 @@ class SemanticsOwner {
|
||||
}
|
||||
}
|
||||
visitedNodes.sort((SemanticsNode a, SemanticsNode b) => a.depth - b.depth);
|
||||
List<mojom.SemanticsNode> updatedNodes = <mojom.SemanticsNode>[];
|
||||
ui.SemanticsUpdateBuilder builder = new ui.SemanticsUpdateBuilder();
|
||||
for (SemanticsNode node in visitedNodes) {
|
||||
assert(node.parent?._dirty != true); // could be null (no parent) or false (not dirty)
|
||||
// The _serialize() method marks the node as not dirty, and
|
||||
@ -694,11 +675,11 @@ class SemanticsOwner {
|
||||
// which happens e.g. when the node is no longer contributing
|
||||
// semantics).
|
||||
if (node._dirty && node.attached)
|
||||
updatedNodes.add(node._serialize());
|
||||
node._addToUpdate(builder);
|
||||
}
|
||||
for (SemanticsListener listener in new List<SemanticsListener>.from(_listeners))
|
||||
listener(updatedNodes);
|
||||
_dirtyNodes.clear();
|
||||
ui.window.updateSemantics(builder.build());
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
SemanticsActionHandler _getSemanticsActionHandlerForId(int id, SemanticsAction action) {
|
||||
|
||||
@ -73,7 +73,7 @@ class MediaQueryData {
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator==(Object other) {
|
||||
bool operator ==(Object other) {
|
||||
if (other.runtimeType != runtimeType)
|
||||
return false;
|
||||
MediaQueryData typedOther = other;
|
||||
|
||||
@ -8,7 +8,6 @@ import 'dart:ui' show SemanticsFlags;
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/scheduler.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter_services/semantics.dart' as mojom;
|
||||
|
||||
import 'basic.dart';
|
||||
import 'binding.dart';
|
||||
@ -153,21 +152,23 @@ class _SemanticsDebuggerState extends State<SemanticsDebugger> {
|
||||
|
||||
class _SemanticsClient extends ChangeNotifier {
|
||||
_SemanticsClient(PipelineOwner pipelineOwner) {
|
||||
_semanticsOwner = pipelineOwner.addSemanticsListener(_updateSemanticsTree);
|
||||
_semanticsHandle = pipelineOwner.ensureSemantics(
|
||||
listener: _didUpdateSemantics
|
||||
);
|
||||
}
|
||||
|
||||
SemanticsOwner _semanticsOwner;
|
||||
SemanticsHandle _semanticsHandle;
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_semanticsOwner.removeListener(_updateSemanticsTree);
|
||||
_semanticsOwner = null;
|
||||
_semanticsHandle.dispose();
|
||||
_semanticsHandle = null;
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
int generation = 0;
|
||||
|
||||
void _updateSemanticsTree(List<mojom.SemanticsNode> nodes) {
|
||||
void _didUpdateSemantics() {
|
||||
generation += 1;
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
@ -8,7 +8,7 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
|
||||
import '../rendering/test_semantics_client.dart';
|
||||
import '../widget/semantics_tester.dart';
|
||||
|
||||
// This file uses "as dynamic" in a few places to defeat the static
|
||||
// analysis. In general you want to avoid using this style in your
|
||||
@ -431,7 +431,8 @@ void main() {
|
||||
});
|
||||
|
||||
testWidgets('Does tooltip contribute semantics', (WidgetTester tester) async {
|
||||
TestSemanticsClient client = new TestSemanticsClient(tester.binding.pipelineOwner);
|
||||
SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
GlobalKey key = new GlobalKey();
|
||||
await tester.pumpWidget(
|
||||
new Overlay(
|
||||
@ -456,37 +457,16 @@ void main() {
|
||||
]
|
||||
)
|
||||
);
|
||||
expect(client.updates.length, equals(1));
|
||||
expect(client.updates[0].id, equals(0));
|
||||
expect(client.updates[0].actions, isEmpty);
|
||||
expect(client.updates[0].flags.hasCheckedState, isFalse);
|
||||
expect(client.updates[0].flags.isChecked, isFalse);
|
||||
expect(client.updates[0].strings.label, equals('TIP'));
|
||||
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));
|
||||
client.updates.clear();
|
||||
|
||||
expect(semantics, hasSemantics(new TestSemantics(id: 0, label: 'TIP')));
|
||||
|
||||
// before using "as dynamic" in your code, see note top of file
|
||||
(key.currentState as dynamic).ensureTooltipVisible(); // this triggers a rebuild of the semantics because the tree changes
|
||||
|
||||
await tester.pump(const Duration(seconds: 2)); // faded in, show timer started (and at 0.0)
|
||||
expect(client.updates.length, equals(1));
|
||||
expect(client.updates[0].id, equals(0));
|
||||
expect(client.updates[0].actions, isEmpty);
|
||||
expect(client.updates[0].flags.hasCheckedState, isFalse);
|
||||
expect(client.updates[0].flags.isChecked, isFalse);
|
||||
expect(client.updates[0].strings.label, equals('TIP'));
|
||||
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));
|
||||
client.updates.clear();
|
||||
client.dispose();
|
||||
|
||||
expect(semantics, hasSemantics(new TestSemantics(id: 0, label: 'TIP')));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
@ -7,7 +7,6 @@ import 'package:flutter/rendering.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'rendering_tester.dart';
|
||||
import 'test_semantics_client.dart';
|
||||
|
||||
class TestTree {
|
||||
TestTree() {
|
||||
@ -126,20 +125,25 @@ void main() {
|
||||
});
|
||||
test('objects can be detached and re-attached: semantics', () {
|
||||
TestTree testTree = new TestTree();
|
||||
TestSemanticsClient client = new TestSemanticsClient(renderer.pipelineOwner);
|
||||
int semanticsUpdateCount = 0;
|
||||
SemanticsHandle semanticsHandle = renderer.pipelineOwner.ensureSemantics(
|
||||
listener: () {
|
||||
++semanticsUpdateCount;
|
||||
}
|
||||
);
|
||||
// Lay out, composite, paint, and update semantics
|
||||
layout(testTree.root, phase: EnginePhase.flushSemantics);
|
||||
expect(client.updates.length, equals(1));
|
||||
expect(semanticsUpdateCount, 1);
|
||||
// Remove testTree from the custom render view
|
||||
renderer.renderView.child = null;
|
||||
expect(testTree.child.owner, isNull);
|
||||
// Dirty one of the elements
|
||||
client.updates.clear();
|
||||
semanticsUpdateCount = 0;
|
||||
testTree.child.markNeedsSemanticsUpdate();
|
||||
expect(client.updates.length, equals(0));
|
||||
expect(semanticsUpdateCount, 0);
|
||||
// Lay out, composite, paint, and update semantics again
|
||||
layout(testTree.root, phase: EnginePhase.flushSemantics);
|
||||
expect(client.updates.length, equals(1));
|
||||
client.dispose();
|
||||
expect(semanticsUpdateCount, 1);
|
||||
semanticsHandle.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
@ -1,25 +0,0 @@
|
||||
// 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/rendering.dart';
|
||||
import 'package:flutter_services/semantics.dart' as mojom;
|
||||
|
||||
class TestSemanticsClient {
|
||||
TestSemanticsClient(PipelineOwner pipelineOwner) {
|
||||
_semanticsOwner = pipelineOwner.addSemanticsListener(_updateSemanticsTree);
|
||||
}
|
||||
|
||||
SemanticsOwner _semanticsOwner;
|
||||
|
||||
void dispose() {
|
||||
_semanticsOwner.removeListener(_updateSemanticsTree);
|
||||
_semanticsOwner = null;
|
||||
}
|
||||
|
||||
final List<mojom.SemanticsNode> updates = <mojom.SemanticsNode>[];
|
||||
|
||||
void _updateSemanticsTree(List<mojom.SemanticsNode> nodes) {
|
||||
updates.addAll(nodes);
|
||||
}
|
||||
}
|
||||
@ -2,16 +2,16 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_services/semantics.dart' as mojom;
|
||||
|
||||
import '../rendering/test_semantics_client.dart';
|
||||
import 'semantics_tester.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Does FlatButton contribute semantics', (WidgetTester tester) async {
|
||||
TestSemanticsClient client = new TestSemanticsClient(tester.binding.pipelineOwner);
|
||||
SemanticsTester semantics = new SemanticsTester(tester);
|
||||
await tester.pumpWidget(
|
||||
new Material(
|
||||
child: new Center(
|
||||
@ -22,25 +22,22 @@ void main() {
|
||||
)
|
||||
)
|
||||
);
|
||||
expect(client.updates.length, equals(1));
|
||||
expect(client.updates[0].id, equals(0));
|
||||
expect(client.updates[0].actions, isEmpty);
|
||||
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(1));
|
||||
expect(client.updates[0].children[0].id, equals(1));
|
||||
expect(client.updates[0].children[0].actions, equals(<int>[mojom.SemanticAction.tap.mojoEnumValue]));
|
||||
expect(client.updates[0].children[0].flags.hasCheckedState, isFalse);
|
||||
expect(client.updates[0].children[0].flags.isChecked, isFalse);
|
||||
expect(client.updates[0].children[0].strings.label, equals('Hello'));
|
||||
expect(client.updates[0].children[0].children.length, equals(0));
|
||||
client.updates.clear();
|
||||
client.dispose();
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics(
|
||||
id: 0,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 1,
|
||||
actions: SemanticsAction.tap.index,
|
||||
label: 'Hello',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 88.0, 36.0),
|
||||
transform: new Matrix4.translationValues(356.0, 282.0, 0.0)
|
||||
)
|
||||
]
|
||||
)
|
||||
));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
@ -7,11 +7,11 @@ import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../rendering/test_semantics_client.dart';
|
||||
import 'semantics_tester.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Semantics 1', (WidgetTester tester) async {
|
||||
TestSemanticsClient client = new TestSemanticsClient(tester.binding.pipelineOwner);
|
||||
SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
// smoketest
|
||||
await tester.pumpWidget(
|
||||
@ -22,23 +22,13 @@ void main() {
|
||||
)
|
||||
)
|
||||
);
|
||||
expect(client.updates.length, equals(1));
|
||||
expect(client.updates[0].id, equals(0));
|
||||
expect(client.updates[0].actions, isEmpty);
|
||||
expect(client.updates[0].flags.hasCheckedState, isFalse);
|
||||
expect(client.updates[0].flags.isChecked, isFalse);
|
||||
expect(client.updates[0].strings.label, equals('test1'));
|
||||
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));
|
||||
client.updates.clear();
|
||||
|
||||
expect(semantics, hasSemantics(new TestSemantics(id: 0, label: 'test1')));
|
||||
|
||||
// control for forking
|
||||
await tester.pumpWidget(
|
||||
new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
new Container(
|
||||
height: 10.0,
|
||||
@ -52,26 +42,15 @@ void main() {
|
||||
)
|
||||
),
|
||||
],
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch
|
||||
)
|
||||
);
|
||||
expect(client.updates.length, equals(1));
|
||||
expect(client.updates[0].id, equals(0));
|
||||
expect(client.updates[0].actions, isEmpty);
|
||||
expect(client.updates[0].flags.hasCheckedState, isFalse);
|
||||
expect(client.updates[0].flags.isChecked, isFalse);
|
||||
expect(client.updates[0].strings.label, equals('child1'));
|
||||
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));
|
||||
client.updates.clear();
|
||||
|
||||
expect(semantics, hasSemantics(new TestSemantics(id: 0, label: 'child1')));
|
||||
|
||||
// forking semantics
|
||||
await tester.pumpWidget(
|
||||
new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
new Container(
|
||||
height: 10.0,
|
||||
@ -85,48 +64,32 @@ void main() {
|
||||
)
|
||||
),
|
||||
],
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch
|
||||
)
|
||||
);
|
||||
expect(client.updates.length, equals(1));
|
||||
expect(client.updates[0].id, equals(0));
|
||||
expect(client.updates[0].actions, isEmpty);
|
||||
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].actions, isEmpty);
|
||||
expect(client.updates[0].children[0].flags.hasCheckedState, isFalse);
|
||||
expect(client.updates[0].children[0].flags.isChecked, isFalse);
|
||||
expect(client.updates[0].children[0].strings.label, equals('child1'));
|
||||
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(10.0));
|
||||
expect(client.updates[0].children[0].children.length, equals(0));
|
||||
expect(client.updates[0].children[1].id, equals(2));
|
||||
expect(client.updates[0].children[1].actions, isEmpty);
|
||||
expect(client.updates[0].children[1].flags.hasCheckedState, isFalse);
|
||||
expect(client.updates[0].children[1].flags.isChecked, isFalse);
|
||||
expect(client.updates[0].children[1].strings.label, equals('child2'));
|
||||
expect(client.updates[0].children[1].geometry.transform, equals(<double>[1.0,0.0,0.0,0.0, 0.0,1.0,0.0,0.0, 0.0,0.0,1.0,0.0, 0.0,10.0,0.0,1.0]));
|
||||
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(10.0));
|
||||
expect(client.updates[0].children[1].children.length, equals(0));
|
||||
client.updates.clear();
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics(
|
||||
id: 0,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 1,
|
||||
label: 'child1',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 2,
|
||||
label: 'child2',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
|
||||
transform: new Matrix4.translationValues(0.0, 10.0, 0.0),
|
||||
),
|
||||
],
|
||||
)
|
||||
));
|
||||
|
||||
// toggle a branch off
|
||||
await tester.pumpWidget(
|
||||
new Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch,
|
||||
children: <Widget>[
|
||||
new Container(
|
||||
height: 10.0,
|
||||
@ -140,22 +103,10 @@ void main() {
|
||||
)
|
||||
),
|
||||
],
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch
|
||||
)
|
||||
);
|
||||
expect(client.updates.length, equals(1));
|
||||
expect(client.updates[0].id, equals(0));
|
||||
expect(client.updates[0].actions, isEmpty);
|
||||
expect(client.updates[0].flags.hasCheckedState, isFalse);
|
||||
expect(client.updates[0].flags.isChecked, isFalse);
|
||||
expect(client.updates[0].strings.label, equals('child1'));
|
||||
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));
|
||||
client.updates.clear();
|
||||
|
||||
expect(semantics, hasSemantics(new TestSemantics(id: 0, label: 'child1')));
|
||||
|
||||
// toggle a branch back on
|
||||
await tester.pumpWidget(
|
||||
@ -176,41 +127,26 @@ void main() {
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch
|
||||
)
|
||||
);
|
||||
expect(client.updates.length, equals(1));
|
||||
expect(client.updates[0].id, equals(0));
|
||||
expect(client.updates[0].actions, isEmpty);
|
||||
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(3));
|
||||
expect(client.updates[0].children[0].actions, isEmpty);
|
||||
expect(client.updates[0].children[0].flags.hasCheckedState, isFalse);
|
||||
expect(client.updates[0].children[0].flags.isChecked, isFalse);
|
||||
expect(client.updates[0].children[0].strings.label, equals('child1'));
|
||||
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(10.0));
|
||||
expect(client.updates[0].children[0].children.length, equals(0));
|
||||
expect(client.updates[0].children[1].id, equals(2));
|
||||
expect(client.updates[0].children[1].actions, isEmpty);
|
||||
expect(client.updates[0].children[1].flags.hasCheckedState, isFalse);
|
||||
expect(client.updates[0].children[1].flags.isChecked, isFalse);
|
||||
expect(client.updates[0].children[1].strings.label, equals('child2'));
|
||||
expect(client.updates[0].children[1].geometry.transform, equals(<double>[1.0,0.0,0.0,0.0, 0.0,1.0,0.0,0.0, 0.0,0.0,1.0,0.0, 0.0,10.0,0.0,1.0]));
|
||||
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(10.0));
|
||||
expect(client.updates[0].children[1].children.length, equals(0));
|
||||
client.updates.clear();
|
||||
client.dispose();
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics(
|
||||
id: 0,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 3,
|
||||
label: 'child1',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 2,
|
||||
label: 'child2',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
|
||||
transform: new Matrix4.translationValues(0.0, 10.0, 0.0),
|
||||
),
|
||||
],
|
||||
)
|
||||
));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
@ -7,11 +7,11 @@ import 'package:flutter/rendering.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../rendering/test_semantics_client.dart';
|
||||
import 'semantics_tester.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Semantics 2', (WidgetTester tester) async {
|
||||
TestSemanticsClient client = new TestSemanticsClient(tester.binding.pipelineOwner);
|
||||
SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
// this test is the same as the test in Semantics 1, but
|
||||
// starting with the second branch being ignored and then
|
||||
@ -36,41 +36,25 @@ void main() {
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch
|
||||
)
|
||||
);
|
||||
expect(client.updates.length, equals(1));
|
||||
expect(client.updates[0].id, equals(0));
|
||||
expect(client.updates[0].actions, isEmpty);
|
||||
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].actions, isEmpty);
|
||||
expect(client.updates[0].children[0].flags.hasCheckedState, isFalse);
|
||||
expect(client.updates[0].children[0].flags.isChecked, isFalse);
|
||||
expect(client.updates[0].children[0].strings.label, equals('child1'));
|
||||
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(10.0));
|
||||
expect(client.updates[0].children[0].children.length, equals(0));
|
||||
expect(client.updates[0].children[1].id, equals(2));
|
||||
expect(client.updates[0].children[1].actions, isEmpty);
|
||||
expect(client.updates[0].children[1].flags.hasCheckedState, isFalse);
|
||||
expect(client.updates[0].children[1].flags.isChecked, isFalse);
|
||||
expect(client.updates[0].children[1].strings.label, equals('child2'));
|
||||
expect(client.updates[0].children[1].geometry.transform, equals(<double>[1.0,0.0,0.0,0.0, 0.0,1.0,0.0,0.0, 0.0,0.0,1.0,0.0, 0.0,10.0,0.0,1.0]));
|
||||
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(10.0));
|
||||
expect(client.updates[0].children[1].children.length, equals(0));
|
||||
client.updates.clear();
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics(
|
||||
id: 0,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 1,
|
||||
label: 'child1',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 2,
|
||||
label: 'child2',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
|
||||
transform: new Matrix4.translationValues(0.0, 10.0, 0.0),
|
||||
),
|
||||
],
|
||||
)
|
||||
));
|
||||
|
||||
// toggle a branch off
|
||||
await tester.pumpWidget(
|
||||
@ -91,19 +75,8 @@ void main() {
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch
|
||||
)
|
||||
);
|
||||
expect(client.updates.length, equals(1));
|
||||
expect(client.updates[0].id, equals(0));
|
||||
expect(client.updates[0].actions, isEmpty);
|
||||
expect(client.updates[0].flags.hasCheckedState, isFalse);
|
||||
expect(client.updates[0].flags.isChecked, isFalse);
|
||||
expect(client.updates[0].strings.label, equals('child1'));
|
||||
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));
|
||||
client.updates.clear();
|
||||
|
||||
expect(semantics, hasSemantics(new TestSemantics(id: 0, label: 'child1')));
|
||||
|
||||
// toggle a branch back on
|
||||
await tester.pumpWidget(
|
||||
@ -124,41 +97,26 @@ void main() {
|
||||
crossAxisAlignment: CrossAxisAlignment.stretch
|
||||
)
|
||||
);
|
||||
expect(client.updates.length, equals(1));
|
||||
expect(client.updates[0].id, equals(0));
|
||||
expect(client.updates[0].actions, isEmpty);
|
||||
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(3));
|
||||
expect(client.updates[0].children[0].actions, isEmpty);
|
||||
expect(client.updates[0].children[0].flags.hasCheckedState, isFalse);
|
||||
expect(client.updates[0].children[0].flags.isChecked, isFalse);
|
||||
expect(client.updates[0].children[0].strings.label, equals('child1'));
|
||||
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(10.0));
|
||||
expect(client.updates[0].children[0].children.length, equals(0));
|
||||
expect(client.updates[0].children[1].id, equals(2));
|
||||
expect(client.updates[0].children[1].actions, isEmpty);
|
||||
expect(client.updates[0].children[1].flags.hasCheckedState, isFalse);
|
||||
expect(client.updates[0].children[1].flags.isChecked, isFalse);
|
||||
expect(client.updates[0].children[1].strings.label, equals('child2'));
|
||||
expect(client.updates[0].children[1].geometry.transform, equals(<double>[1.0,0.0,0.0,0.0, 0.0,1.0,0.0,0.0, 0.0,0.0,1.0,0.0, 0.0,10.0,0.0,1.0]));
|
||||
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(10.0));
|
||||
expect(client.updates[0].children[1].children.length, equals(0));
|
||||
client.updates.clear();
|
||||
client.dispose();
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics(
|
||||
id: 0,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 3,
|
||||
label: 'child1',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 2,
|
||||
label: 'child2',
|
||||
rect: new Rect.fromLTRB(0.0, 0.0, 800.0, 10.0),
|
||||
transform: new Matrix4.translationValues(0.0, 10.0, 0.0),
|
||||
),
|
||||
],
|
||||
)
|
||||
));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
@ -2,15 +2,17 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ui' show SemanticsFlags;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../rendering/test_semantics_client.dart';
|
||||
import 'semantics_tester.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Semantics 3', (WidgetTester tester) async {
|
||||
TestSemanticsClient client = new TestSemanticsClient(tester.binding.pipelineOwner);
|
||||
SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
// implicit annotators
|
||||
await tester.pumpWidget(
|
||||
@ -25,19 +27,14 @@ void main() {
|
||||
)
|
||||
)
|
||||
);
|
||||
expect(client.updates.length, equals(1));
|
||||
expect(client.updates[0].id, equals(0));
|
||||
expect(client.updates[0].actions, isEmpty);
|
||||
expect(client.updates[0].flags.hasCheckedState, isTrue);
|
||||
expect(client.updates[0].flags.isChecked, isTrue);
|
||||
expect(client.updates[0].strings.label, equals('test'));
|
||||
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));
|
||||
client.updates.clear();
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics(
|
||||
id: 0,
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
label: 'test',
|
||||
)
|
||||
));
|
||||
|
||||
// remove one
|
||||
await tester.pumpWidget(
|
||||
@ -49,19 +46,13 @@ void main() {
|
||||
)
|
||||
)
|
||||
);
|
||||
expect(client.updates.length, equals(1));
|
||||
expect(client.updates[0].id, equals(0));
|
||||
expect(client.updates[0].actions, isEmpty);
|
||||
expect(client.updates[0].flags.hasCheckedState, isTrue);
|
||||
expect(client.updates[0].flags.isChecked, isTrue);
|
||||
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(0));
|
||||
client.updates.clear();
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics(
|
||||
id: 0,
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
)
|
||||
));
|
||||
|
||||
// change what it says
|
||||
await tester.pumpWidget(
|
||||
@ -73,19 +64,13 @@ void main() {
|
||||
)
|
||||
)
|
||||
);
|
||||
expect(client.updates.length, equals(1));
|
||||
expect(client.updates[0].id, equals(0));
|
||||
expect(client.updates[0].actions, isEmpty);
|
||||
expect(client.updates[0].flags.hasCheckedState, isFalse);
|
||||
expect(client.updates[0].flags.isChecked, isFalse);
|
||||
expect(client.updates[0].strings.label, equals('test'));
|
||||
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));
|
||||
client.updates.clear();
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics(
|
||||
id: 0,
|
||||
label: 'test',
|
||||
)
|
||||
));
|
||||
|
||||
// add a node
|
||||
await tester.pumpWidget(
|
||||
@ -100,19 +85,19 @@ void main() {
|
||||
)
|
||||
)
|
||||
);
|
||||
expect(client.updates.length, equals(1));
|
||||
expect(client.updates[0].id, equals(0));
|
||||
expect(client.updates[0].actions, isEmpty);
|
||||
expect(client.updates[0].flags.hasCheckedState, isTrue);
|
||||
expect(client.updates[0].flags.isChecked, isTrue);
|
||||
expect(client.updates[0].strings.label, equals('test'));
|
||||
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));
|
||||
client.updates.clear();
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics(
|
||||
id: 0,
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
label: 'test',
|
||||
)
|
||||
));
|
||||
|
||||
int changeCount = 0;
|
||||
tester.binding.pipelineOwner.semanticsOwner.addListener(() {
|
||||
changeCount += 1;
|
||||
});
|
||||
|
||||
// make no changes
|
||||
await tester.pumpWidget(
|
||||
@ -127,7 +112,9 @@ void main() {
|
||||
)
|
||||
)
|
||||
);
|
||||
expect(client.updates.length, equals(0));
|
||||
client.dispose();
|
||||
|
||||
expect(changeCount, 0);
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
@ -2,15 +2,17 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ui' show SemanticsFlags;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../rendering/test_semantics_client.dart';
|
||||
import 'semantics_tester.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Semantics 4', (WidgetTester tester) async {
|
||||
TestSemanticsClient client = new TestSemanticsClient(tester.binding.pipelineOwner);
|
||||
SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
// O
|
||||
// / \ O=root
|
||||
@ -40,18 +42,32 @@ void main() {
|
||||
]
|
||||
)
|
||||
);
|
||||
expect(client.updates.length, equals(1));
|
||||
expect(client.updates[0].id, equals(0));
|
||||
expect(client.updates[0].children.length, equals(2));
|
||||
expect(client.updates[0].children[0].id, equals(1));
|
||||
expect(client.updates[0].children[0].children.length, equals(0));
|
||||
expect(client.updates[0].children[1].id, equals(2));
|
||||
expect(client.updates[0].children[1].children.length, equals(2));
|
||||
expect(client.updates[0].children[1].children[0].id, equals(3));
|
||||
expect(client.updates[0].children[1].children[0].children.length, equals(0));
|
||||
expect(client.updates[0].children[1].children[1].id, equals(4));
|
||||
expect(client.updates[0].children[1].children[1].children.length, equals(0));
|
||||
client.updates.clear();
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics(
|
||||
id: 0,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 1,
|
||||
label: 'L1',
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 2,
|
||||
label: 'L2',
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 3,
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 4,
|
||||
flags: SemanticsFlags.hasCheckedState.index,
|
||||
),
|
||||
]
|
||||
),
|
||||
],
|
||||
)
|
||||
));
|
||||
|
||||
// O O=root
|
||||
// / \ L=node with label
|
||||
@ -78,10 +94,23 @@ void main() {
|
||||
]
|
||||
)
|
||||
);
|
||||
expect(client.updates.length, equals(1));
|
||||
expect(client.updates[0].id, equals(2));
|
||||
expect(client.updates[0].children.length, equals(0));
|
||||
client.updates.clear();
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics(
|
||||
id: 0,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 1,
|
||||
label: 'L1',
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 2,
|
||||
label: 'L2',
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
),
|
||||
],
|
||||
)
|
||||
));
|
||||
|
||||
// O=root
|
||||
// OLC L=node with label
|
||||
@ -105,10 +134,15 @@ void main() {
|
||||
]
|
||||
)
|
||||
);
|
||||
expect(client.updates.length, equals(1));
|
||||
expect(client.updates[0].id, equals(0));
|
||||
expect(client.updates[0].children.length, equals(0));
|
||||
client.updates.clear();
|
||||
client.dispose();
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics(
|
||||
id: 0,
|
||||
label: 'L2',
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
)
|
||||
));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
@ -6,11 +6,11 @@ import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../rendering/test_semantics_client.dart';
|
||||
import 'semantics_tester.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Semantics 5', (WidgetTester tester) async {
|
||||
TestSemanticsClient client = new TestSemanticsClient(tester.binding.pipelineOwner);
|
||||
SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Stack(
|
||||
@ -28,18 +28,22 @@ void main() {
|
||||
]
|
||||
)
|
||||
);
|
||||
expect(client.updates.length, equals(1));
|
||||
expect(client.updates[0].id, equals(0));
|
||||
expect(client.updates[0].flags.hasCheckedState, isFalse);
|
||||
expect(client.updates[0].strings.label, equals(''));
|
||||
expect(client.updates[0].children.length, equals(2));
|
||||
expect(client.updates[0].children[0].id, equals(1));
|
||||
expect(client.updates[0].children[0].flags.hasCheckedState, isFalse);
|
||||
expect(client.updates[0].children[0].strings.label, equals(''));
|
||||
expect(client.updates[0].children[1].id, equals(2));
|
||||
expect(client.updates[0].children[1].flags.hasCheckedState, isFalse);
|
||||
expect(client.updates[0].children[1].strings.label, equals('label'));
|
||||
client.updates.clear();
|
||||
client.dispose();
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics(
|
||||
id: 0,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 1,
|
||||
),
|
||||
new TestSemantics(
|
||||
id: 2,
|
||||
label: 'label',
|
||||
),
|
||||
]
|
||||
)
|
||||
));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
@ -2,16 +2,17 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ui' show SemanticsFlags;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_services/semantics.dart' as mojom;
|
||||
|
||||
import '../rendering/test_semantics_client.dart';
|
||||
import 'semantics_tester.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Semantics 7 - Merging', (WidgetTester tester) async {
|
||||
TestSemanticsClient client = new TestSemanticsClient(tester.binding.pipelineOwner);
|
||||
SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
String label;
|
||||
|
||||
@ -44,43 +45,26 @@ void main() {
|
||||
]
|
||||
)
|
||||
);
|
||||
expect(client.updates.length, equals(1));
|
||||
expect(client.updates[0].id, equals(0));
|
||||
expect(client.updates[0].actions, isEmpty);
|
||||
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].actions, isEmpty);
|
||||
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].actions, isEmpty);
|
||||
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
|
||||
client.updates.clear();
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics(
|
||||
id: 0,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 1,
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
label: label,
|
||||
),
|
||||
// IDs 2 and 3 are used up by the nodes that get merged in
|
||||
new TestSemantics(
|
||||
id: 4,
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
label: label,
|
||||
),
|
||||
// IDs 5 and 6 are used up by the nodes that get merged in
|
||||
],
|
||||
)
|
||||
));
|
||||
|
||||
label = '2';
|
||||
await tester.pumpWidget(
|
||||
@ -111,43 +95,27 @@ void main() {
|
||||
]
|
||||
)
|
||||
);
|
||||
expect(client.updates.length, equals(2));
|
||||
|
||||
// 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(semantics, hasSemantics(
|
||||
new TestSemantics(
|
||||
id: 0,
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 1,
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
label: label,
|
||||
),
|
||||
// IDs 2 and 3 are used up by the nodes that get merged in
|
||||
new TestSemantics(
|
||||
id: 4,
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
label: label,
|
||||
),
|
||||
// IDs 5 and 6 are used up by the nodes that get merged in
|
||||
],
|
||||
)
|
||||
));
|
||||
|
||||
expect(a.id, equals(1));
|
||||
expect(a.actions, isEmpty);
|
||||
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.actions, isEmpty);
|
||||
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();
|
||||
client.dispose();
|
||||
semantics.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
@ -2,15 +2,17 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:ui' show SemanticsFlags;
|
||||
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
|
||||
import '../rendering/test_semantics_client.dart';
|
||||
import 'semantics_tester.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Semantics 8 - Merging with reset', (WidgetTester tester) async {
|
||||
TestSemanticsClient client = new TestSemanticsClient(tester.binding.pipelineOwner);
|
||||
SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
await tester.pumpWidget(
|
||||
new MergeSemantics(
|
||||
@ -32,19 +34,14 @@ void main() {
|
||||
)
|
||||
)
|
||||
);
|
||||
expect(client.updates.length, equals(1));
|
||||
expect(client.updates[0].id, equals(0));
|
||||
expect(client.updates[0].actions, isEmpty);
|
||||
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));
|
||||
client.updates.clear();
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics(
|
||||
id: 0,
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
label: 'label',
|
||||
)
|
||||
));
|
||||
|
||||
// switch the order of the inner Semantics node to trigger a reset
|
||||
await tester.pumpWidget(
|
||||
@ -67,19 +64,15 @@ void main() {
|
||||
)
|
||||
)
|
||||
);
|
||||
expect(client.updates.length, equals(1));
|
||||
expect(client.updates[0].id, equals(0));
|
||||
expect(client.updates[0].actions, isEmpty);
|
||||
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));
|
||||
client.updates.clear();
|
||||
client.dispose();
|
||||
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics(
|
||||
id: 0,
|
||||
flags: SemanticsFlags.hasCheckedState.index | SemanticsFlags.isChecked.index,
|
||||
label: 'label',
|
||||
)
|
||||
));
|
||||
|
||||
semantics.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
@ -5,13 +5,17 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/widgets.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:flutter_services/semantics.dart' as mojom;
|
||||
|
||||
import '../rendering/test_semantics_client.dart';
|
||||
import 'semantics_tester.dart';
|
||||
|
||||
void main() {
|
||||
testWidgets('Semantics shutdown and restart', (WidgetTester tester) async {
|
||||
TestSemanticsClient client = new TestSemanticsClient(tester.binding.pipelineOwner);
|
||||
SemanticsTester semantics = new SemanticsTester(tester);
|
||||
|
||||
TestSemantics expectedSemantics = new TestSemantics(
|
||||
id: 0,
|
||||
label: 'test1',
|
||||
);
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Container(
|
||||
@ -22,37 +26,22 @@ void main() {
|
||||
)
|
||||
);
|
||||
|
||||
void checkUpdates(List<mojom.SemanticsNode> updates) {
|
||||
expect(updates.length, equals(1));
|
||||
expect(updates[0].id, equals(0));
|
||||
expect(updates[0].actions, isEmpty);
|
||||
expect(updates[0].flags.hasCheckedState, isFalse);
|
||||
expect(updates[0].flags.isChecked, isFalse);
|
||||
expect(updates[0].strings.label, equals('test1'));
|
||||
expect(updates[0].geometry.transform, isNull);
|
||||
expect(updates[0].geometry.left, equals(0.0));
|
||||
expect(updates[0].geometry.top, equals(0.0));
|
||||
expect(updates[0].geometry.width, equals(800.0));
|
||||
expect(updates[0].geometry.height, equals(600.0));
|
||||
expect(updates[0].children.length, equals(0));
|
||||
}
|
||||
expect(semantics, hasSemantics(expectedSemantics));
|
||||
|
||||
checkUpdates(client.updates);
|
||||
client.updates.clear();
|
||||
client.dispose();
|
||||
semantics.dispose();
|
||||
semantics = null;
|
||||
|
||||
expect(tester.binding.hasScheduledFrame, isFalse);
|
||||
client = new TestSemanticsClient(tester.binding.pipelineOwner);
|
||||
semantics = new SemanticsTester(tester);
|
||||
expect(tester.binding.hasScheduledFrame, isTrue);
|
||||
await tester.pump();
|
||||
|
||||
checkUpdates(client.updates);
|
||||
client.updates.clear();
|
||||
client.dispose();
|
||||
expect(semantics, hasSemantics(expectedSemantics));
|
||||
semantics.dispose();
|
||||
});
|
||||
|
||||
testWidgets('Detach and reattach assert', (WidgetTester tester) async {
|
||||
TestSemanticsClient client = new TestSemanticsClient(tester.binding.pipelineOwner);
|
||||
SemanticsTester semantics = new SemanticsTester(tester);
|
||||
GlobalKey key = new GlobalKey();
|
||||
|
||||
await tester.pumpWidget(
|
||||
@ -69,11 +58,18 @@ void main() {
|
||||
)
|
||||
);
|
||||
|
||||
expect(client.updates.length, equals(1));
|
||||
expect(client.updates[0].strings.label, equals('test1'));
|
||||
expect(client.updates[0].children.length, equals(1));
|
||||
expect(client.updates[0].children[0].strings.label, equals('test2a'));
|
||||
client.updates.clear();
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics(
|
||||
id: 0,
|
||||
label: 'test1',
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 1,
|
||||
label: 'test2a',
|
||||
)
|
||||
]
|
||||
)
|
||||
));
|
||||
|
||||
await tester.pumpWidget(
|
||||
new Container(
|
||||
@ -93,14 +89,25 @@ void main() {
|
||||
)
|
||||
);
|
||||
|
||||
expect(client.updates.length, equals(1));
|
||||
expect(client.updates[0].strings.label, equals('test1'));
|
||||
expect(client.updates[0].children.length, equals(1));
|
||||
expect(client.updates[0].children[0].strings.label, equals('middle'));
|
||||
expect(client.updates[0].children[0].children.length, equals(1));
|
||||
expect(client.updates[0].children[0].children[0].strings.label, equals('test2b'));
|
||||
expect(client.updates[0].children[0].children[0].children.length, equals(0));
|
||||
expect(semantics, hasSemantics(
|
||||
new TestSemantics(
|
||||
id: 0,
|
||||
label: 'test1',
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 2,
|
||||
label: 'middle',
|
||||
children: <TestSemantics>[
|
||||
new TestSemantics(
|
||||
id: 1,
|
||||
label: 'test2b',
|
||||
)
|
||||
]
|
||||
)
|
||||
]
|
||||
)
|
||||
));
|
||||
|
||||
client.dispose();
|
||||
semantics.dispose();
|
||||
});
|
||||
}
|
||||
|
||||
164
packages/flutter/test/widget/semantics_tester.dart
Normal file
164
packages/flutter/test/widget/semantics_tester.dart
Normal file
@ -0,0 +1,164 @@
|
||||
// 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/rendering.dart';
|
||||
import 'package:flutter_test/flutter_test.dart';
|
||||
import 'package:meta/meta.dart';
|
||||
|
||||
export 'package:flutter/rendering.dart' show SemanticsData;
|
||||
|
||||
/// Test semantics data that is compared against real semantics tree.
|
||||
///
|
||||
/// Useful with [hasSemantics] and [SemanticsTester] to test the contents of the
|
||||
/// semantics tree.
|
||||
class TestSemantics {
|
||||
/// Creates an object witht some test semantics data.
|
||||
///
|
||||
/// If [rect] argument is null, the [rect] field with ve initialized with
|
||||
/// `new Rect.fromLTRB(0.0, 0.0, 800.0, 600.0)`, which is the default size of
|
||||
/// the screen during unit testing.
|
||||
TestSemantics({
|
||||
this.id,
|
||||
this.flags: 0,
|
||||
this.actions: 0,
|
||||
this.label: '',
|
||||
Rect rect,
|
||||
this.transform,
|
||||
this.children: const <TestSemantics>[],
|
||||
}) : rect = rect ?? new Rect.fromLTRB(0.0, 0.0, 800.0, 600.0);
|
||||
|
||||
/// The unique identifier for this node.
|
||||
///
|
||||
/// The root node has an id of zero. Other nodes are given a unique id when
|
||||
/// they are created.
|
||||
final int id;
|
||||
|
||||
/// A bit field of [SemanticsFlags] that apply to this node.
|
||||
final int flags;
|
||||
|
||||
/// A bit field of [SemanticsActions] that apply to this node.
|
||||
final int actions;
|
||||
|
||||
/// A textual description of this node.
|
||||
final String label;
|
||||
|
||||
/// The bounding box for this node in its coordinate system.
|
||||
final Rect rect;
|
||||
|
||||
/// The transform from this node's coordinate system to its parent's coordinate system.
|
||||
///
|
||||
/// By default, the transform is null, which represents the identity
|
||||
/// transformation (i.e., that this node has the same coorinate system as its
|
||||
/// parent).
|
||||
final Matrix4 transform;
|
||||
|
||||
/// The children of this node.
|
||||
final List<TestSemantics> children;
|
||||
|
||||
SemanticsData _getSemanticsData() {
|
||||
return new SemanticsData(
|
||||
flags: flags,
|
||||
actions: actions,
|
||||
label: label,
|
||||
rect: rect,
|
||||
transform: transform
|
||||
);
|
||||
}
|
||||
|
||||
bool _matches(SemanticsNode node, Map<dynamic, dynamic> matchState) {
|
||||
if (node == null || id != node.id
|
||||
|| _getSemanticsData() != node.getSemanticsData()
|
||||
|| children.length != (node.mergeAllDescendantsIntoThisNode ? 0 : node.childrenCount)) {
|
||||
matchState[TestSemantics] = this;
|
||||
matchState[SemanticsNode] = node;
|
||||
return false;
|
||||
}
|
||||
if (children.isEmpty)
|
||||
return true;
|
||||
bool result = true;
|
||||
Iterator<TestSemantics> it = children.iterator;
|
||||
node.visitChildren((SemanticsNode node) {
|
||||
it.moveNext();
|
||||
if (!it.current._matches(node, matchState)) {
|
||||
result = false;
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return result;
|
||||
}
|
||||
}
|
||||
|
||||
/// Ensures that the given widget tester has a semantics tree to test.
|
||||
///
|
||||
/// Useful with [hasSemantics] to test the contents of the semantics tree.
|
||||
class SemanticsTester {
|
||||
/// Creates a semantics tester for the given widget tester.
|
||||
///
|
||||
/// You should call [dispose] at the end of a test that creates a semantics
|
||||
/// tester.
|
||||
SemanticsTester(this.tester) {
|
||||
_semanticsHandle = tester.binding.pipelineOwner.ensureSemantics();
|
||||
}
|
||||
|
||||
/// The widget tester that this object is testing the semantics of.
|
||||
final WidgetTester tester;
|
||||
SemanticsHandle _semanticsHandle;
|
||||
|
||||
/// Release resources held by this semantics tester.
|
||||
///
|
||||
/// Call this function at the end of any test that uses a semantics tester.
|
||||
@mustCallSuper
|
||||
void dispose() {
|
||||
_semanticsHandle.dispose();
|
||||
_semanticsHandle = null;
|
||||
}
|
||||
|
||||
@override
|
||||
String toString() => 'SemanticsTester';
|
||||
}
|
||||
|
||||
class _HasSemantics extends Matcher {
|
||||
const _HasSemantics(this._semantics);
|
||||
|
||||
final TestSemantics _semantics;
|
||||
|
||||
@override
|
||||
bool matches(@checked SemanticsTester item, Map<dynamic, dynamic> matchState) {
|
||||
return _semantics._matches(item.tester.binding.pipelineOwner.semanticsOwner.rootSemanticsNode, matchState);
|
||||
}
|
||||
|
||||
@override
|
||||
Description describe(Description description) {
|
||||
return description.add('semantics node id ${_semantics.id}');
|
||||
}
|
||||
|
||||
@override
|
||||
Description describeMismatch(dynamic item, Description mismatchDescription, Map<dynamic, dynamic> matchState, bool verbose) {
|
||||
TestSemantics testNode = matchState[TestSemantics];
|
||||
SemanticsNode node = matchState[SemanticsNode];
|
||||
if (node == null)
|
||||
return mismatchDescription.add('could not find node with id ${testNode.id}');
|
||||
if (testNode.id != node.id)
|
||||
return mismatchDescription.add('expected node id ${testNode.id} but found id ${node.id}');
|
||||
final SemanticsData data = node.getSemanticsData();
|
||||
if (testNode.flags != data.flags)
|
||||
return mismatchDescription.add('expected node id ${testNode.id} to have flags ${testNode.flags} but found flags ${data.flags}');
|
||||
if (testNode.actions != data.actions)
|
||||
return mismatchDescription.add('expected node id ${testNode.id} to have actions ${testNode.actions} but found actions ${data.actions}');
|
||||
if (testNode.label != data.label)
|
||||
return mismatchDescription.add('expected node id ${testNode.id} to have label "${testNode.label}" but found label "${data.label}"');
|
||||
if (testNode.rect != data.rect)
|
||||
return mismatchDescription.add('expected node id ${testNode.id} to have rect ${testNode.rect} but found rect ${data.rect}');
|
||||
if (testNode.transform != data.transform)
|
||||
return mismatchDescription.add('expected node id ${testNode.id} to have transform ${testNode.transform} but found transform ${data.transform}');
|
||||
final int childrenCount = node.mergeAllDescendantsIntoThisNode ? 0 : node.childrenCount;
|
||||
if (testNode.children.length != childrenCount)
|
||||
return mismatchDescription.add('expected node id ${testNode.id} to have ${testNode.children.length} but found $childrenCount children');
|
||||
return mismatchDescription;
|
||||
}
|
||||
}
|
||||
|
||||
/// Asserts that a [SemanticsTester] has a semantics tree that exactly matches the given semantics.
|
||||
Matcher hasSemantics(TestSemantics semantics) => new _HasSemantics(semantics);
|
||||
Loading…
x
Reference in New Issue
Block a user