Unverified Commit f5f05511 authored by Jonah Williams's avatar Jonah Williams Committed by GitHub

Initial implementation of AnnotatedRegion for system chrome (#17672)

parent 9a51588d
......@@ -292,28 +292,23 @@ Widget _wrapWithBackground({
Color backgroundColor,
Widget child,
}) {
final bool darkBackground = backgroundColor.computeLuminance() < 0.179;
final SystemUiOverlayStyle overlayStyle = darkBackground
? SystemUiOverlayStyle.light
: SystemUiOverlayStyle.dark;
final DecoratedBox childWithBackground = new DecoratedBox(
decoration: new BoxDecoration(
border: border,
color: backgroundColor,
),
child: child,
child: new AnnotatedRegion<SystemUiOverlayStyle>(
value: overlayStyle,
sized: true,
child: child,
),
);
final bool darkBackground = backgroundColor.computeLuminance() < 0.179;
// TODO(jonahwilliams): remove once we have platform themes.
switch (defaultTargetPlatform) {
case TargetPlatform.iOS:
SystemChrome.setSystemUIOverlayStyle(
darkBackground ? SystemUiOverlayStyle.light : SystemUiOverlayStyle.dark
);
break;
case TargetPlatform.android:
case TargetPlatform.fuchsia:
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark);
}
if (backgroundColor.alpha == 0xFF)
return childWithBackground;
......
......@@ -702,7 +702,7 @@ class _MaterialAppState extends State<MaterialApp> {
},
)
);
assert(() {
if (widget.debugShowMaterialGrid) {
result = new GridPaper(
......
......@@ -345,21 +345,6 @@ class _AppBarState extends State<AppBar> {
TextStyle centerStyle = widget.textTheme?.title ?? themeData.primaryTextTheme.title;
TextStyle sideStyle = widget.textTheme?.body1 ?? themeData.primaryTextTheme.body1;
if (parentRoute?.isCurrent ?? true) {
final Brightness brightness = widget.brightness ?? themeData.primaryColorBrightness;
// TODO(jonahwilliams): remove once we have platform themes.
switch (defaultTargetPlatform) {
case TargetPlatform.iOS:
SystemChrome.setSystemUIOverlayStyle(brightness == Brightness.dark
? SystemUiOverlayStyle.light
: SystemUiOverlayStyle.dark);
break;
case TargetPlatform.android:
case TargetPlatform.fuchsia:
SystemChrome.setSystemUIOverlayStyle(SystemUiOverlayStyle.dark);
}
}
if (widget.toolbarOpacity != 1.0) {
final double opacity = const Interval(0.25, 1.0, curve: Curves.fastOutSlowIn).transform(widget.toolbarOpacity);
if (centerStyle?.color != null)
......@@ -451,7 +436,6 @@ class _AppBarState extends State<AppBar> {
),
),
);
if (widget.bottom != null) {
appBar = new Column(
mainAxisAlignment: MainAxisAlignment.spaceBetween,
......@@ -492,14 +476,21 @@ class _AppBarState extends State<AppBar> {
],
);
}
final Brightness brightness = widget.brightness ?? themeData.primaryColorBrightness;
final SystemUiOverlayStyle overlayStyle = brightness == Brightness.dark
? SystemUiOverlayStyle.dark
: SystemUiOverlayStyle.light;
return new Semantics(
container: true,
explicitChildNodes: true,
child: new Material(
color: widget.backgroundColor ?? themeData.primaryColor,
elevation: widget.elevation,
child: appBar,
child: new AnnotatedRegion<SystemUiOverlayStyle>(
value: overlayStyle,
child: new Material(
color: widget.backgroundColor ?? themeData.primaryColor,
elevation: widget.elevation,
child: appBar,
),
),
);
}
......
......@@ -89,6 +89,19 @@ abstract class Layer extends AbstractNode with DiagnosticableTreeMixin {
assert(!attached);
}
/// Returns the value of [S] that corresponds to the point described by
/// [regionOffset].
///
/// Returns null if no matching region is found.
///
/// The main way for a value to be assigned here is by pushing an
/// [AnnotatedRegionLayer] into the layer tree.
///
/// See also:
///
/// * [AnnotatedRegionLayer], for placing values in the layer tree.
S find<S>(Offset regionOffset);
/// Override this method to upload this layer to the engine.
///
/// The `layerOffset` is the accumulated offset of this layer's parent from the
......@@ -166,6 +179,9 @@ class PictureLayer extends Layer {
super.debugFillProperties(properties);
properties.add(new DiagnosticsProperty<Rect>('paint bounds', canvasBounds));
}
@override
S find<S>(Offset regionOffset) => null;
}
/// A composited layer that maps a backend texture to a rectangle.
......@@ -218,6 +234,9 @@ class TextureLayer extends Layer {
height: shiftedRect.height,
);
}
@override
S find<S>(Offset regionOffset) => null;
}
/// A layer that indicates to the compositor that it should display
......@@ -280,6 +299,9 @@ class PerformanceOverlayLayer extends Layer {
builder.setCheckerboardRasterCacheImages(checkerboardRasterCacheImages);
builder.setCheckerboardOffscreenLayers(checkerboardOffscreenLayers);
}
@override
S find<S>(Offset regionOffset) => null;
}
/// A composited layer that has a list of children.
......@@ -316,6 +338,19 @@ class ContainerLayer extends Layer {
return child == equals;
}
@override
S find<S>(Offset regionOffset) {
Layer current = lastChild;
while (current != null) {
final Object value = current.find<S>(regionOffset);
if (value != null) {
return value;
}
current = current.previousSibling;
}
return null;
}
@override
void attach(Object owner) {
super.attach(owner);
......@@ -506,6 +541,11 @@ class OffsetLayer extends ContainerLayer {
/// pipeline.
Offset offset;
@override
S find<S>(Offset regionOffset) {
return super.find<S>(regionOffset - offset);
}
@override
void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
addChildrenToScene(builder, offset + layerOffset);
......@@ -573,6 +613,13 @@ class ClipRectLayer extends ContainerLayer {
/// (as described at [Layer]).
Rect clipRect;
@override
S find<S>(Offset regionOffset) {
if (!clipRect.contains(regionOffset))
return null;
return super.find<S>(regionOffset);
}
@override
void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
bool enabled = true;
......@@ -612,6 +659,13 @@ class ClipRRectLayer extends ContainerLayer {
/// (as described at [Layer]).
RRect clipRRect;
@override
S find<S>(Offset regionOffset) {
if (!clipRRect.contains(regionOffset))
return null;
return super.find<S>(regionOffset);
}
@override
void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
bool enabled = true;
......@@ -651,6 +705,13 @@ class ClipPathLayer extends ContainerLayer {
/// (as described at [Layer]).
Path clipPath;
@override
S find<S>(Offset regionOffset) {
if (!clipPath.contains(regionOffset))
return null;
return super.find<S>(regionOffset);
}
@override
void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
bool enabled = true;
......@@ -676,7 +737,9 @@ class TransformLayer extends OffsetLayer {
///
/// The [transform] and [offset] properties must be non-null before the
/// compositing phase of the pipeline.
TransformLayer({ this.transform, Offset offset = Offset.zero }) : super(offset: offset);
TransformLayer({ Matrix4 transform, Offset offset = Offset.zero })
: _transform = transform,
super(offset: offset);
/// The matrix to apply.
///
......@@ -687,9 +750,17 @@ class TransformLayer extends OffsetLayer {
///
/// The [transform] property must be non-null before the compositing phase of
/// the pipeline.
Matrix4 transform;
Matrix4 get transform => _transform;
Matrix4 _transform;
set transform(Matrix4 value) {
if (value == _transform)
return;
_transform = value;
_invertedTransform = null;
}
Matrix4 _lastEffectiveTransform;
Matrix4 _invertedTransform;
@override
void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
......@@ -704,6 +775,14 @@ class TransformLayer extends OffsetLayer {
builder.pop();
}
@override
S find<S>(Offset regionOffset) {
_invertedTransform ??= new Matrix4.inverted(transform);
final Vector4 vector = new Vector4(regionOffset.dx, regionOffset.dy, 0.0, 1.0);
final Vector4 result = _invertedTransform.transform(vector);
return super.find<S>(new Offset(result[0], result[1]));
}
@override
void applyTransform(Layer child, Matrix4 transform) {
assert(child != null);
......@@ -875,6 +954,13 @@ class PhysicalModelLayer extends ContainerLayer {
/// The shadow color.
Color shadowColor;
@override
S find<S>(Offset regionOffset) {
if (!clipPath.contains(regionOffset))
return null;
return super.find<S>(regionOffset);
}
@override
void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
bool enabled = true;
......@@ -978,6 +1064,11 @@ class LeaderLayer extends ContainerLayer {
/// not every case can be detected.
Offset _lastOffset;
@override
S find<S>(Offset regionOffset) {
return super.find(regionOffset - offset);
}
@override
void addToScene(ui.SceneBuilder builder, Offset layerOffset) {
assert(offset != null);
......@@ -1086,6 +1177,23 @@ class FollowerLayer extends ContainerLayer {
Offset _lastOffset;
Matrix4 _lastTransform;
Matrix4 _invertedTransform;
@override
S find<S>(Offset regionOffset) {
if (link.leader == null) {
return showWhenUnlinked ? super.find<S>(regionOffset - unlinkedOffset) : null;
}
if (_invertedTransform == null) {
final Matrix4 transform = getLastTransform();
assert(transform != null);
_invertedTransform = new Matrix4.zero();
transform.copyInverse(_invertedTransform);
}
final Vector4 vector = new Vector4(regionOffset.dx, regionOffset.dy, 0.0, 1.0);
final Vector4 result = _invertedTransform.transform(vector);
return super.find<S>(new Offset(result[0] - linkedOffset.dx, result[1] - linkedOffset.dy));
}
/// The transform that was used during the last composition phase.
///
......@@ -1199,3 +1307,46 @@ class FollowerLayer extends ContainerLayer {
properties.add(new TransformProperty('transform', getLastTransform(), defaultValue: null));
}
}
/// A composited layer which annotates its children with a value.
///
/// These values can be retrieved using [Layer.find] with a given [Offset]. If
/// a [Size] is provided to this layer, then find will check if the provided
/// offset is within the bounds of the layer.
class AnnotatedRegionLayer<T> extends ContainerLayer {
/// Creates a new layer annotated with [value] that clips to [size] if provided.
///
/// The value provided cannot be null.
AnnotatedRegionLayer(this.value, {this.size}) : assert(value != null);
/// The value returned by [find] if the offset is contained within this layer.
final T value;
/// The [size] is optionally used to clip the hit-testing of [find].
///
/// If not provided, all offsets are considered to be contained within this
/// layer, unless an ancestor layer applies a clip.
final Size size;
@override
S find<S>(Offset regionOffset) {
final S result = super.find<S>(regionOffset);
if (result != null)
return result;
if (size != null && !size.contains(regionOffset))
return null;
if (T == S) {
final Object untypedResult = value;
final S typedResult = untypedResult;
return typedResult;
}
return super.find<S>(regionOffset);
}
@override
void debugFillProperties(DiagnosticPropertiesBuilder properties) {
super.debugFillProperties(properties);
properties.add(new DiagnosticsProperty<T>('value', value));
properties.add(new DiagnosticsProperty<Size>('size', size, defaultValue: null));
}
}
......@@ -4235,3 +4235,58 @@ class RenderFollowerLayer extends RenderProxyBox {
properties.add(new TransformProperty('current transform matrix', getCurrentTransform()));
}
}
/// Render object which inserts an [AnnotatedRegionLayer] into the layer tree.
///
/// See also:
///
/// * [Layer.find], for an example of how this value is retrieved.
/// * [AnnotatedRegionLayer], the layer this render object creates.
class RenderAnnotatedRegion<T> extends RenderProxyBox {
/// Creates a new [RenderAnnotatedRegion] to insert [value] into the
/// layer tree.
///
/// If [sized] is true, the layer is provided with the size of this render
/// object to clip the results of [Layer.findRegion].
///
/// Neither [value] nor [sized] can be null.
RenderAnnotatedRegion({
@required T value,
@required bool sized,
RenderBox child,
}) : assert(value != null),
assert(sized != null),
_value = value,
_sized = sized,
super(child);
/// A value which can be retrieved using [Layer.find].
T get value => _value;
T _value;
set value (T newValue) {
if (_value == newValue)
return;
_value = newValue;
markNeedsPaint();
}
/// Whether the render object will pass its [size] to the [AnnotatedRegionLayer].
bool get sized => _sized;
bool _sized;
set sized(bool value) {
if (_sized == value)
return;
_sized = value;
markNeedsPaint();
}
@override
final bool alwaysNeedsCompositing = true;
@override
void paint(PaintingContext context, Offset offset) {
final AnnotatedRegionLayer<T> layer = new AnnotatedRegionLayer<T>(value, size: sized ? size : null);
context.pushLayer(layer, super.paint, offset);
}
}
......@@ -7,6 +7,7 @@ import 'dart:io' show Platform;
import 'dart:ui' as ui show Scene, SceneBuilder, window;
import 'package:flutter/foundation.dart';
import 'package:flutter/services.dart';
import 'package:vector_math/vector_math_64.dart';
import 'binding.dart';
......@@ -81,6 +82,27 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
markNeedsLayout();
}
/// Whether Flutter should automatically compute the desired system UI.
///
/// When this setting is enabled, Flutter will hit-test the layer tree at the
/// top and bottom of the screen on each frame looking for an
/// [AnnotatedRegionLayer] with an instance of a [SystemUiOverlayStyle]. The
/// hit-test result from the top of the screen provides the status bar settings
/// and the hit-test result from the bottom of the screen provides the system
/// nav bar settings.
///
/// Setting this to false does not cause previous automatic adjustments to be
/// reset, nor does setting it to true cause the app to update immediately.
///
/// If you want to imperatively set the system ui style instead, it is
/// recommended that [automaticSystemUiAdjustment] is set to false.
///
/// See also:
///
/// * [AnnotatedRegion], for placing [SystemUiOverlayStyle] in the layer tree.
/// * [SystemChrome.setSystemUIOverlayStyle], for imperatively setting the system ui style.
bool automaticSystemUiAdjustment = true;
/// Bootstrap the rendering pipeline by scheduling the first frame.
///
/// This should only be called once, and must be called before changing
......@@ -172,6 +194,8 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
final ui.SceneBuilder builder = new ui.SceneBuilder();
layer.addToScene(builder, Offset.zero);
final ui.Scene scene = builder.build();
if (automaticSystemUiAdjustment)
_updateSystemChrome();
ui.window.render(scene);
scene.dispose();
assert(() {
......@@ -184,6 +208,35 @@ class RenderView extends RenderObject with RenderObjectWithChildMixin<RenderBox>
}
}
void _updateSystemChrome() {
final Rect bounds = paintBounds;
final Offset top = new Offset(bounds.center.dx, ui.window.padding.top / ui.window.devicePixelRatio);
final Offset bottom = new Offset(bounds.center.dx, bounds.center.dy - ui.window.padding.bottom / ui.window.devicePixelRatio);
final SystemUiOverlayStyle upperOverlayStyle = layer.find<SystemUiOverlayStyle>(top);
// Only android has a customizable system navigation bar.
SystemUiOverlayStyle lowerOverlayStyle;
switch (defaultTargetPlatform) {
case TargetPlatform.android:
lowerOverlayStyle = layer.find<SystemUiOverlayStyle>(bottom);
break;
case TargetPlatform.iOS:
case TargetPlatform.fuchsia:
break;
}
// If there are no overlay styles in the UI don't bother updating.
if (upperOverlayStyle != null || lowerOverlayStyle != null) {
final SystemUiOverlayStyle overlayStyle = new SystemUiOverlayStyle(
statusBarBrightness: upperOverlayStyle?.statusBarBrightness,
statusBarIconBrightness: upperOverlayStyle?.statusBarIconBrightness,
statusBarColor: upperOverlayStyle?.statusBarColor,
systemNavigationBarColor: lowerOverlayStyle?.systemNavigationBarColor,
systemNavigationBarDividerColor: lowerOverlayStyle?.systemNavigationBarDividerColor,
systemNavigationBarIconBrightness: lowerOverlayStyle?.systemNavigationBarIconBrightness,
);
SystemChrome.setSystemUIOverlayStyle(overlayStyle);
}
}
@override
Rect get paintBounds => Offset.zero & (size * configuration.devicePixelRatio);
......
......@@ -171,6 +171,9 @@ class SystemUiOverlayStyle {
};
}
@override
String toString() => _toMap().toString();
/// Creates a copy of this theme with the given fields replaced with new values.
SystemUiOverlayStyle copyWith({
Color systemNavigationBarColor,
......@@ -302,19 +305,16 @@ class SystemChrome {
/// ```
static void setSystemUIOverlayStyle(SystemUiOverlayStyle style) {
assert(style != null);
if (_pendingStyle != null) {
// The microtask has already been queued; just update the pending value.
_pendingStyle = style;
return;
}
if (style == _latestStyle) {
// Trivial success: no microtask has been queued and the given style is
// already in effect, so no need to queue a microtask.
return;
}
_pendingStyle = style;
scheduleMicrotask(() {
assert(_pendingStyle != null);
......
// Copyright 2018 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/foundation.dart';
import 'package:flutter/rendering.dart';
import 'framework.dart';
/// Annotates a region of the layer tree with a value.
///
/// See also:
///
/// * [Layer.find], for an example of how this value is retrieved.
/// * [AnnotatedRegionLayer], the layer pushed into the layer tree.
class AnnotatedRegion<T> extends SingleChildRenderObjectWidget {
/// Creates a new annotated region to insert [value] into the layer tree.
///
/// Neither [child] nor [value] may be null.
///
/// [sized] defaults to true and controls whether the annotated region will
/// clip its child.
const AnnotatedRegion({
Key key,
@required Widget child,
@required this.value,
this.sized = true,
}) : assert(value != null),
assert(child != null),
super(key: key, child: child);
/// A value which can be retrieved using [Layer.find].
final T value;
/// If false, the layer pushed into the tree will not be provided with a size.
///
/// An [AnnotatedRegionLayer] with a size checks that the offset provided in
/// [Layer.find] is within the bounds, returning null otherwise.
///
/// See also:
///
/// * [AnnotatedRegionLayer], for a description of this behavior.
final bool sized;
@override
RenderObject createRenderObject(BuildContext context) {
return new RenderAnnotatedRegion<T>(value: value, sized: sized);
}
@override
void updateRenderObject(BuildContext context, RenderAnnotatedRegion<T> renderObject) {
renderObject
..value = value
..sized = sized;
}
}
......@@ -1703,6 +1703,9 @@ class _InspectorOverlayLayer extends Layer {
_textPainter.paint(canvas, tipOffset + const Offset(_kTooltipPadding, _kTooltipPadding));
canvas.restore();
}
@override
S find<S>(Offset regionOffset) => null;
}
const double _kScreenEdgeMargin = 10.0;
......
......@@ -18,6 +18,7 @@ export 'src/widgets/animated_cross_fade.dart';
export 'src/widgets/animated_list.dart';
export 'src/widgets/animated_size.dart';
export 'src/widgets/animated_switcher.dart';
export 'src/widgets/annotated_region.dart';
export 'src/widgets/app.dart';
export 'src/widgets/async.dart';
export 'src/widgets/automatic_keep_alive.dart';
......
......@@ -160,7 +160,7 @@ void main() {
),
),
);
expect(tester.renderObject<RenderProxyBox>(find.byType(PhysicalModel)).child, paintsNothing);
expect(tester.renderObject<RenderProxyBox>(find.byType(PhysicalModel)).child, isNot(paints..circle()));
await tester.tap(find.byType(InkWell));
await tester.pump();
await tester.pump(const Duration(milliseconds: 10));
......@@ -171,7 +171,7 @@ void main() {
await tester.pump(const Duration(milliseconds: 10));
expect(
tester.renderObject<RenderProxyBox>(find.byType(PhysicalModel)).child,
keepAlive ? (paints..circle()) : paintsNothing,
keepAlive ? (paints..circle()) : isNot(paints..circle()),
);
}
await runTest(true);
......
// Copyright 2018 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/widgets.dart';
import 'package:test/test.dart';
void main() {
group(AnnotatedRegion, () {
test('finds the first value in a OffsetLayer when sized', () {
final ContainerLayer containerLayer = new ContainerLayer();
final List<OffsetLayer> layers = <OffsetLayer>[
new OffsetLayer(offset: Offset.zero),
new OffsetLayer(offset: const Offset(0.0, 100.0)),
new OffsetLayer(offset: const Offset(0.0, 200.0)),
];
int i = 0;
for (OffsetLayer layer in layers) {
layer.append(new AnnotatedRegionLayer<int>(i, size: const Size(200.0, 100.0)));
containerLayer.append(layer);
i += 1;
}
expect(containerLayer.find<int>(const Offset(0.0, 1.0)), 0);
expect(containerLayer.find<int>(const Offset(0.0, 101.0)), 1);
expect(containerLayer.find<int>(const Offset(0.0, 201.0)), 2);
});
test('finds a value within the clip in a ClipRectLayer', () {
final ContainerLayer containerLayer = new ContainerLayer();
final List<ClipRectLayer> layers = <ClipRectLayer>[
new ClipRectLayer(clipRect: new Rect.fromLTRB(0.0, 0.0, 100.0, 100.0)),
new ClipRectLayer(clipRect: new Rect.fromLTRB(0.0, 100.0, 100.0, 200.0)),
new ClipRectLayer(clipRect: new Rect.fromLTRB(0.0, 200.0, 100.0, 300.0)),
];
int i = 0;
for (ClipRectLayer layer in layers) {
layer.append(new AnnotatedRegionLayer<int>(i));
containerLayer.append(layer);
i += 1;
}
expect(containerLayer.find<int>(const Offset(0.0, 1.0)), 0);
expect(containerLayer.find<int>(const Offset(0.0, 101.0)), 1);
expect(containerLayer.find<int>(const Offset(0.0, 201.0)), 2);
});
test('finds a value within the clip in a ClipRRectLayer', () {
final ContainerLayer containerLayer = new ContainerLayer();
final List<ClipRRectLayer> layers = <ClipRRectLayer>[
new ClipRRectLayer(clipRRect: new RRect.fromLTRBR(0.0, 0.0, 100.0, 100.0, const Radius.circular(4.0))),
new ClipRRectLayer(clipRRect: new RRect.fromLTRBR(0.0, 100.0, 100.0, 200.0, const Radius.circular(4.0))),
new ClipRRectLayer(clipRRect: new RRect.fromLTRBR(0.0, 200.0, 100.0, 300.0, const Radius.circular(4.0))),
];
int i = 0;
for (ClipRRectLayer layer in layers) {
layer.append(new AnnotatedRegionLayer<int>(i));
containerLayer.append(layer);
i += 1;
}
expect(containerLayer.find<int>(const Offset(5.0, 5.0)), 0);
expect(containerLayer.find<int>(const Offset(5.0, 105.0)), 1);
expect(containerLayer.find<int>(const Offset(5.0, 205.0)), 2);
});
test('finds a value under a TransformLayer', () {
final Matrix4 transform = new Matrix4(
2.625, 0.0, 0.0, 0.0,
0.0, 2.625, 0.0, 0.0,
0.0, 0.0, 1.0, 0.0,
0.0, 0.0, 0.0, 1.0,
);
final TransformLayer transformLayer = new TransformLayer(transform: transform);
final List<OffsetLayer> layers = <OffsetLayer>[
new OffsetLayer(),
new OffsetLayer(offset: const Offset(0.0, 100.0)),
new OffsetLayer(offset: const Offset(0.0, 200.0)),
];
int i = 0;
for (OffsetLayer layer in layers) {
final AnnotatedRegionLayer<int> annotatedRegionLayer = new AnnotatedRegionLayer<int>(i, size: const Size(100.0, 100.0));
layer.append(annotatedRegionLayer);
transformLayer.append(layer);
i += 1;
}
expect(transformLayer.find<int>(const Offset(0.0, 100.0)), 0);
expect(transformLayer.find<int>(const Offset(0.0, 200.0)), 0);
expect(transformLayer.find<int>(const Offset(0.0, 270.0)), 1);
expect(transformLayer.find<int>(const Offset(0.0, 400.0)), 1);
expect(transformLayer.find<int>(const Offset(0.0, 530.0)), 2);
});
test('looks for child AnnotatedRegions before parents', () {
final AnnotatedRegionLayer<int> parent = new AnnotatedRegionLayer<int>(1);
final AnnotatedRegionLayer<int> child = new AnnotatedRegionLayer<int>(2);
final ContainerLayer layer = new ContainerLayer();
parent.append(child);
layer.append(parent);
expect(parent.find<int>(Offset.zero), 2);
});
test('looks for correct type', () {
final AnnotatedRegionLayer<int> child1 = new AnnotatedRegionLayer<int>(1);
final AnnotatedRegionLayer<String> child2 = new AnnotatedRegionLayer<String>('hello');
final ContainerLayer layer = new ContainerLayer();
layer.append(child2);
layer.append(child1);
expect(layer.find<String>(Offset.zero), 'hello');
});
test('does not clip Layer.find on an AnnotatedRegion with an unrelated type', () {
final AnnotatedRegionLayer<int> child = new AnnotatedRegionLayer<int>(1);
final AnnotatedRegionLayer<String> parent = new AnnotatedRegionLayer<String>('hello', size: const Size(10.0, 10.0));
final ContainerLayer layer = new ContainerLayer();
parent.append(child);
layer.append(parent);
expect(layer.find<int>(const Offset(100.0, 100.0)), 1);
});
});
}
......@@ -135,6 +135,11 @@ class TestRecordingPaintingContext implements PaintingContext {
canvas.restore();
}
@override
void pushLayer(Layer childLayer, PaintingContextCallback painter, Offset offset, {Rect childPaintBounds}) {
painter(this, offset);
}
@override
void noSuchMethod(Invocation invocation) { }
}
......
// Copyright 2018 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/widgets.dart';
import 'package:flutter_test/flutter_test.dart';
void main() {
testWidgets('provides a value to the layer tree', (WidgetTester tester) async {
await tester.pumpWidget(
const AnnotatedRegion<int>(
child: const SizedBox(width: 100.0, height: 100.0),
value: 1,
),
);
final List<Layer> layers = tester.layers;
final AnnotatedRegionLayer<int> layer = layers.firstWhere((Layer layer) => layer is AnnotatedRegionLayer<int>);
expect(layer.value, 1);
});
}
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