Initial implementation of AnnotatedRegion for system chrome (#17672)
diff --git a/packages/flutter/lib/src/cupertino/nav_bar.dart b/packages/flutter/lib/src/cupertino/nav_bar.dart index cf69e2d..dc91c61 100644 --- a/packages/flutter/lib/src/cupertino/nav_bar.dart +++ b/packages/flutter/lib/src/cupertino/nav_bar.dart
@@ -292,28 +292,23 @@ 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;
diff --git a/packages/flutter/lib/src/material/app.dart b/packages/flutter/lib/src/material/app.dart index 7ba0242..6a7079c 100644 --- a/packages/flutter/lib/src/material/app.dart +++ b/packages/flutter/lib/src/material/app.dart
@@ -702,7 +702,7 @@ }, ) ); - + assert(() { if (widget.debugShowMaterialGrid) { result = new GridPaper(
diff --git a/packages/flutter/lib/src/material/app_bar.dart b/packages/flutter/lib/src/material/app_bar.dart index ca472f3..e02cf13 100644 --- a/packages/flutter/lib/src/material/app_bar.dart +++ b/packages/flutter/lib/src/material/app_bar.dart
@@ -345,21 +345,6 @@ 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 @@ ), ), ); - if (widget.bottom != null) { appBar = new Column( mainAxisAlignment: MainAxisAlignment.spaceBetween, @@ -492,14 +476,21 @@ ], ); } + 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, + ), ), ); }
diff --git a/packages/flutter/lib/src/rendering/layer.dart b/packages/flutter/lib/src/rendering/layer.dart index 8dce68b..3ee6643 100644 --- a/packages/flutter/lib/src/rendering/layer.dart +++ b/packages/flutter/lib/src/rendering/layer.dart
@@ -89,6 +89,19 @@ 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 @@ 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 @@ 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 @@ builder.setCheckerboardRasterCacheImages(checkerboardRasterCacheImages); builder.setCheckerboardOffscreenLayers(checkerboardOffscreenLayers); } + + @override + S find<S>(Offset regionOffset) => null; } /// A composited layer that has a list of children. @@ -317,6 +339,19 @@ } @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); Layer child = firstChild; @@ -507,6 +542,11 @@ 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); } @@ -574,6 +614,13 @@ 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; assert(() { @@ -613,6 +660,13 @@ 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; assert(() { @@ -652,6 +706,13 @@ 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; assert(() { @@ -676,7 +737,9 @@ /// /// 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 @@ /// /// 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) { @@ -705,6 +776,14 @@ } @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); assert(transform != null); @@ -876,6 +955,13 @@ 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; assert(() { @@ -979,6 +1065,11 @@ Offset _lastOffset; @override + S find<S>(Offset regionOffset) { + return super.find(regionOffset - offset); + } + + @override void addToScene(ui.SceneBuilder builder, Offset layerOffset) { assert(offset != null); _lastOffset = offset + layerOffset; @@ -1086,6 +1177,23 @@ 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 @@ 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)); + } +}
diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index 2705c06..dd12748 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart
@@ -4235,3 +4235,58 @@ 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); + } +}
diff --git a/packages/flutter/lib/src/rendering/view.dart b/packages/flutter/lib/src/rendering/view.dart index 268773b..bdc0a17 100644 --- a/packages/flutter/lib/src/rendering/view.dart +++ b/packages/flutter/lib/src/rendering/view.dart
@@ -7,6 +7,7 @@ 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 @@ 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 @@ 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 @@ } } + 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);
diff --git a/packages/flutter/lib/src/services/system_chrome.dart b/packages/flutter/lib/src/services/system_chrome.dart index 622b890..0c9f864 100644 --- a/packages/flutter/lib/src/services/system_chrome.dart +++ b/packages/flutter/lib/src/services/system_chrome.dart
@@ -171,6 +171,9 @@ }; } + @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 @@ /// ``` 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);
diff --git a/packages/flutter/lib/src/widgets/annotated_region.dart b/packages/flutter/lib/src/widgets/annotated_region.dart new file mode 100644 index 0000000..f14d7fd --- /dev/null +++ b/packages/flutter/lib/src/widgets/annotated_region.dart
@@ -0,0 +1,57 @@ +// 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; + } +} +
diff --git a/packages/flutter/lib/src/widgets/widget_inspector.dart b/packages/flutter/lib/src/widgets/widget_inspector.dart index 248f610..f100ca3 100644 --- a/packages/flutter/lib/src/widgets/widget_inspector.dart +++ b/packages/flutter/lib/src/widgets/widget_inspector.dart
@@ -1703,6 +1703,9 @@ _textPainter.paint(canvas, tipOffset + const Offset(_kTooltipPadding, _kTooltipPadding)); canvas.restore(); } + + @override + S find<S>(Offset regionOffset) => null; } const double _kScreenEdgeMargin = 10.0;
diff --git a/packages/flutter/lib/widgets.dart b/packages/flutter/lib/widgets.dart index 95aad44..9b64a7b 100644 --- a/packages/flutter/lib/widgets.dart +++ b/packages/flutter/lib/widgets.dart
@@ -18,6 +18,7 @@ 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';
diff --git a/packages/flutter/test/material/ink_well_test.dart b/packages/flutter/test/material/ink_well_test.dart index 6801c68..04a3896 100644 --- a/packages/flutter/test/material/ink_well_test.dart +++ b/packages/flutter/test/material/ink_well_test.dart
@@ -160,7 +160,7 @@ ), ), ); - 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 @@ 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);
diff --git a/packages/flutter/test/rendering/annotated_region_test.dart b/packages/flutter/test/rendering/annotated_region_test.dart new file mode 100644 index 0000000..d27ad93 --- /dev/null +++ b/packages/flutter/test/rendering/annotated_region_test.dart
@@ -0,0 +1,128 @@ +// 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); + }); + }); +} +
diff --git a/packages/flutter/test/rendering/recording_canvas.dart b/packages/flutter/test/rendering/recording_canvas.dart index 952fbdc..9803b89 100644 --- a/packages/flutter/test/rendering/recording_canvas.dart +++ b/packages/flutter/test/rendering/recording_canvas.dart
@@ -136,6 +136,11 @@ } @override + void pushLayer(Layer childLayer, PaintingContextCallback painter, Offset offset, {Rect childPaintBounds}) { + painter(this, offset); + } + + @override void noSuchMethod(Invocation invocation) { } }
diff --git a/packages/flutter/test/widgets/annotated_region_test.dart b/packages/flutter/test/widgets/annotated_region_test.dart new file mode 100644 index 0000000..144c6ca --- /dev/null +++ b/packages/flutter/test/widgets/annotated_region_test.dart
@@ -0,0 +1,21 @@ +// 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); + }); +}