Unverified Commit 3ac9449a authored by Ian Hickson's avatar Ian Hickson Committed by GitHub

Fix the confusing-zero case with NestedScrollView. (#14133)

* Fix the confusing-zero case with NestedScrollView.

* Update mock_canvas.dart

* Update tabs_demo.dart

* more tweaks
parent c5cbc0df
...@@ -13,6 +13,8 @@ class _Page { ...@@ -13,6 +13,8 @@ class _Page {
_Page({ this.label }); _Page({ this.label });
final String label; final String label;
String get id => label[0]; String get id => label[0];
@override
String toString() => '$runtimeType("$label")';
} }
class _CardData { class _CardData {
...@@ -69,6 +71,13 @@ final Map<_Page, List<_CardData>> _allPages = <_Page, List<_CardData>>{ ...@@ -69,6 +71,13 @@ final Map<_Page, List<_CardData>> _allPages = <_Page, List<_CardData>>{
imageAsset: 'shrine/products/chucks.png', imageAsset: 'shrine/products/chucks.png',
imageAssetPackage: _kGalleryAssetsPackage, imageAssetPackage: _kGalleryAssetsPackage,
), ),
],
new _Page(label: 'RIGHT'): <_CardData>[
const _CardData(
title: 'Beachball',
imageAsset: 'shrine/products/beachball.png',
imageAssetPackage: _kGalleryAssetsPackage,
),
const _CardData( const _CardData(
title: 'Dipped Brush', title: 'Dipped Brush',
imageAsset: 'shrine/products/brush.png', imageAsset: 'shrine/products/brush.png',
...@@ -80,13 +89,6 @@ final Map<_Page, List<_CardData>> _allPages = <_Page, List<_CardData>>{ ...@@ -80,13 +89,6 @@ final Map<_Page, List<_CardData>> _allPages = <_Page, List<_CardData>>{
imageAssetPackage: _kGalleryAssetsPackage, imageAssetPackage: _kGalleryAssetsPackage,
), ),
], ],
new _Page(label: 'RIGHT'): <_CardData>[
const _CardData(
title: 'Beachball',
imageAsset: 'shrine/products/beachball.png',
imageAssetPackage: _kGalleryAssetsPackage,
),
],
}; };
class _CardDataItem extends StatelessWidget { class _CardDataItem extends StatelessWidget {
...@@ -121,7 +123,10 @@ class _CardDataItem extends StatelessWidget { ...@@ -121,7 +123,10 @@ class _CardDataItem extends StatelessWidget {
), ),
), ),
new Center( new Center(
child: new Text(data.title, style: Theme.of(context).textTheme.title), child: new Text(
data.title,
style: Theme.of(context).textTheme.title,
),
), ),
], ],
), ),
...@@ -141,13 +146,18 @@ class TabsDemo extends StatelessWidget { ...@@ -141,13 +146,18 @@ class TabsDemo extends StatelessWidget {
body: new NestedScrollView( body: new NestedScrollView(
headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) { headerSliverBuilder: (BuildContext context, bool innerBoxIsScrolled) {
return <Widget>[ return <Widget>[
new SliverAppBar( new SliverOverlapAbsorber(
title: const Text('Tabs and scrolling'), handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
pinned: true, child: new SliverAppBar(
expandedHeight: 150.0, title: const Text('Tabs and scrolling'),
forceElevated: innerBoxIsScrolled, pinned: true,
bottom: new TabBar( expandedHeight: 150.0,
tabs: _allPages.keys.map((_Page page) => new Tab(text: page.label)).toList(), forceElevated: innerBoxIsScrolled,
bottom: new TabBar(
tabs: _allPages.keys.map(
(_Page page) => new Tab(text: page.label),
).toList(),
),
), ),
), ),
]; ];
...@@ -157,15 +167,41 @@ class TabsDemo extends StatelessWidget { ...@@ -157,15 +167,41 @@ class TabsDemo extends StatelessWidget {
return new SafeArea( return new SafeArea(
top: false, top: false,
bottom: false, bottom: false,
child: new ListView( child: new Builder(
padding: const EdgeInsets.symmetric(vertical: 8.0, horizontal: 16.0), builder: (BuildContext context) {
itemExtent: _CardDataItem.height, return new CustomScrollView(
children: _allPages[page].map((_CardData data) { key: new PageStorageKey<_Page>(page),
return new Padding( slivers: <Widget>[
padding: const EdgeInsets.symmetric(vertical: 8.0), new SliverOverlapInjector(
child: new _CardDataItem(page: page, data: data), handle: NestedScrollView.sliverOverlapAbsorberHandleFor(context),
),
new SliverPadding(
padding: const EdgeInsets.symmetric(
vertical: 8.0,
horizontal: 16.0,
),
sliver: new SliverFixedExtentList(
itemExtent: _CardDataItem.height,
delegate: new SliverChildBuilderDelegate(
(BuildContext context, int index) {
final _CardData data = _allPages[page][index];
return new Padding(
padding: const EdgeInsets.symmetric(
vertical: 8.0,
),
child: new _CardDataItem(
page: page,
data: data,
),
);
},
childCount: _allPages[page].length,
),
),
),
],
); );
}).toList(), },
), ),
); );
}).toList(), }).toList(),
......
...@@ -41,6 +41,7 @@ export 'src/painting/image_provider.dart'; ...@@ -41,6 +41,7 @@ export 'src/painting/image_provider.dart';
export 'src/painting/image_resolution.dart'; export 'src/painting/image_resolution.dart';
export 'src/painting/image_stream.dart'; export 'src/painting/image_stream.dart';
export 'src/painting/matrix_utils.dart'; export 'src/painting/matrix_utils.dart';
export 'src/painting/paint_utilities.dart';
export 'src/painting/rounded_rectangle_border.dart'; export 'src/painting/rounded_rectangle_border.dart';
export 'src/painting/shape_decoration.dart'; export 'src/painting/shape_decoration.dart';
export 'src/painting/stadium_border.dart'; export 'src/painting/stadium_border.dart';
......
...@@ -38,7 +38,7 @@ typedef void AnimationStatusListener(AnimationStatus status); ...@@ -38,7 +38,7 @@ typedef void AnimationStatusListener(AnimationStatus status);
/// ///
/// To create a new animation that you can run forward and backward, consider /// To create a new animation that you can run forward and backward, consider
/// using [AnimationController]. /// using [AnimationController].
abstract class Animation<T> extends Listenable { abstract class Animation<T> extends Listenable implements ValueListenable<T> {
/// Abstract const constructor. This constructor enables subclasses to provide /// Abstract const constructor. This constructor enables subclasses to provide
/// const constructors so that they can be used in const expressions. /// const constructors so that they can be used in const expressions.
const Animation(); const Animation();
...@@ -71,6 +71,7 @@ abstract class Animation<T> extends Listenable { ...@@ -71,6 +71,7 @@ abstract class Animation<T> extends Listenable {
AnimationStatus get status; AnimationStatus get status;
/// The current value of the animation. /// The current value of the animation.
@override
T get value; T get value;
/// Whether this animation is stopped at the beginning. /// Whether this animation is stopped at the beginning.
......
...@@ -32,6 +32,16 @@ abstract class Listenable { ...@@ -32,6 +32,16 @@ abstract class Listenable {
void removeListener(VoidCallback listener); void removeListener(VoidCallback listener);
} }
/// An interface for subclasses of [Listenable] that expose a [value].
///
/// This interface is implemented by [ValueNotifier<T>] and [Animation<T>], and
/// allows other APIs to accept either of those implementations interchangeably.
abstract class ValueListenable<T> extends Listenable {
/// The current value of the object. When the value changes, the callbacks
/// registered with [addListener] will be invoked.
T get value;
}
/// A class that can be extended or mixed in that provides a change notification /// A class that can be extended or mixed in that provides a change notification
/// API using [VoidCallback] for notifications. /// API using [VoidCallback] for notifications.
/// ///
...@@ -169,13 +179,14 @@ class _MergingListenable extends ChangeNotifier { ...@@ -169,13 +179,14 @@ class _MergingListenable extends ChangeNotifier {
/// A [ChangeNotifier] that holds a single value. /// A [ChangeNotifier] that holds a single value.
/// ///
/// When [value] is replaced, this class notifies its listeners. /// When [value] is replaced, this class notifies its listeners.
class ValueNotifier<T> extends ChangeNotifier { class ValueNotifier<T> extends ChangeNotifier implements ValueListenable<T> {
/// Creates a [ChangeNotifier] that wraps this value. /// Creates a [ChangeNotifier] that wraps this value.
ValueNotifier(this._value); ValueNotifier(this._value);
/// The current value stored in this notifier. /// The current value stored in this notifier.
/// ///
/// When the value is replaced, this class notifies its listeners. /// When the value is replaced, this class notifies its listeners.
@override
T get value => _value; T get value => _value;
T _value; T _value;
set value(T newValue) { set value(T newValue) {
......
// 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 'dart:math' as math;
import 'basic_types.dart';
/// Draw a line between two points, which cuts diagonally back and forth across
/// the line that connects the two points.
///
/// The line will cross the line `zigs - 1` times.
///
/// If `zigs` is 1, then this will draw two sides of a triangle from `start` to
/// `end`, with the third point being `width` away from the line, as measured
/// perpendicular to that line.
///
/// If `width` is positive, the first `zig` will be to the left of the `start`
/// point when facing the `end` point. To reverse the zigging polarity, provide
/// a negative `width`.
///
/// The line is drawn using the provided `paint` on the provided `canvas`.
void paintZigZag(Canvas canvas, Paint paint, Offset start, Offset end, int zigs, double width) {
assert(zigs.isFinite);
assert(zigs > 0);
canvas.save();
canvas.translate(start.dx, start.dy);
end = end - start;
canvas.rotate(math.atan2(end.dy, end.dx));
final double length = end.distance;
final double spacing = length / (zigs * 2.0);
final Path path = new Path()
..moveTo(0.0, 0.0);
for (int index = 0; index < zigs; index += 1) {
final double x = (index * 2.0 + 1.0) * spacing;
final double y = width * ((index % 2.0) * 2.0 - 1.0);
path.lineTo(x, y);
}
path.lineTo(length, 0.0);
canvas.drawPath(path, paint);
canvas.restore();
}
\ No newline at end of file
...@@ -242,7 +242,7 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R ...@@ -242,7 +242,7 @@ class RenderSliverPadding extends RenderSliver with RenderObjectWithChildMixin<R
@override @override
bool hitTestChildren(HitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) { bool hitTestChildren(HitTestResult result, { @required double mainAxisPosition, @required double crossAxisPosition }) {
if (child.geometry.hitTestExtent > 0.0) if (child != null && child.geometry.hitTestExtent > 0.0)
return child.hitTest(result, mainAxisPosition: mainAxisPosition - childMainAxisPosition(child), crossAxisPosition: crossAxisPosition - childCrossAxisPosition(child)); return child.hitTest(result, mainAxisPosition: mainAxisPosition - childMainAxisPosition(child), crossAxisPosition: crossAxisPosition - childCrossAxisPosition(child));
return false; return false;
} }
......
...@@ -580,12 +580,27 @@ abstract class SchedulerBinding extends BindingBase with ServicesBinding { ...@@ -580,12 +580,27 @@ abstract class SchedulerBinding extends BindingBase with ServicesBinding {
/// Schedules a new frame using [scheduleFrame] if this object is not /// Schedules a new frame using [scheduleFrame] if this object is not
/// currently producing a frame. /// currently producing a frame.
/// ///
/// After this is called, the framework ensures that the end of the /// Calling this method ensures that [handleDrawFrame] will eventually be
/// [handleBeginFrame] function will (eventually) be reached. /// called, unless it's already in progress.
///
/// This has no effect if [schedulerPhase] is
/// [SchedulerPhase.transientCallbacks] or [SchedulerPhase.midFrameMicrotasks]
/// (because a frame is already being prepared in that case), or
/// [SchedulerPhase.persistentCallbacks] (because a frame is actively being
/// rendered in that case). It will schedule a frame if the [schedulerPhase]
/// is [SchedulerPhase.idle] (in between frames) or
/// [SchedulerPhase.postFrameCallbacks] (after a frame).
void ensureVisualUpdate() { void ensureVisualUpdate() {
if (schedulerPhase != SchedulerPhase.idle) switch (schedulerPhase) {
return; case SchedulerPhase.idle:
scheduleFrame(); case SchedulerPhase.postFrameCallbacks:
scheduleFrame();
return;
case SchedulerPhase.transientCallbacks:
case SchedulerPhase.midFrameMicrotasks:
case SchedulerPhase.persistentCallbacks:
return;
}
} }
/// If necessary, schedules a new frame by calling /// If necessary, schedules a new frame by calling
......
...@@ -508,7 +508,7 @@ abstract class WidgetsBinding extends BindingBase with SchedulerBinding, Gesture ...@@ -508,7 +508,7 @@ abstract class WidgetsBinding extends BindingBase with SchedulerBinding, Gesture
_deferFirstFrameReportCount -= 1; _deferFirstFrameReportCount -= 1;
} }
void _handleBuildScheduled() { void _handleBuildScheduled() {
// If we're in the process of building dirty elements, then changes // If we're in the process of building dirty elements, then changes
// should not trigger a new frame. // should not trigger a new frame.
assert(() { assert(() {
......
...@@ -26,7 +26,8 @@ import 'scroll_position_with_single_context.dart'; ...@@ -26,7 +26,8 @@ import 'scroll_position_with_single_context.dart';
/// ///
/// A [ScrollController] is a [Listenable]. It notifies its listeners whenever /// A [ScrollController] is a [Listenable]. It notifies its listeners whenever
/// any of the attached [ScrollPosition]s notify _their_ listeners (i.e. /// any of the attached [ScrollPosition]s notify _their_ listeners (i.e.
/// whenever any of them scroll). /// whenever any of them scroll). It does not notify its listeners when the list
/// of attached [ScrollPosition]s changes.
/// ///
/// Typically used with [ListView], [GridView], [CustomScrollView]. /// Typically used with [ListView], [GridView], [CustomScrollView].
/// ///
......
...@@ -479,7 +479,7 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics { ...@@ -479,7 +479,7 @@ abstract class ScrollPosition extends ViewportOffset with ScrollMetrics {
/// This notifier's value is true if a scroll is underway and false if the scroll /// This notifier's value is true if a scroll is underway and false if the scroll
/// position is idle. /// position is idle.
/// ///
/// Listeners added by stateful widgets should be in the widget's /// Listeners added by stateful widgets should be removed in the widget's
/// [State.dispose] method. /// [State.dispose] method.
final ValueNotifier<bool> isScrollingNotifier = new ValueNotifier<bool>(false); final ValueNotifier<bool> isScrollingNotifier = new ValueNotifier<bool>(false);
......
...@@ -182,11 +182,39 @@ abstract class ScrollView extends StatelessWidget { ...@@ -182,11 +182,39 @@ abstract class ScrollView extends StatelessWidget {
return getAxisDirectionFromAxisReverseAndDirectionality(context, scrollDirection, reverse); return getAxisDirectionFromAxisReverseAndDirectionality(context, scrollDirection, reverse);
} }
/// Build the list of widgets to place inside the viewport.
///
/// Subclasses should override this method to build the slivers for the inside /// Subclasses should override this method to build the slivers for the inside
/// of the viewport. /// of the viewport.
@protected @protected
List<Widget> buildSlivers(BuildContext context); List<Widget> buildSlivers(BuildContext context);
/// Build the viewport.
///
/// Subclasses may override this method to change how the viewport is built.
/// The default implementation uses a [ShrinkWrappingViewport] if [shrinkWrap]
/// is true, and a regular [Viewport] otherwise.
@protected
Widget buildViewport(
BuildContext context,
ViewportOffset offset,
AxisDirection axisDirection,
List<Widget> slivers,
) {
if (shrinkWrap) {
return new ShrinkWrappingViewport(
axisDirection: axisDirection,
offset: offset,
slivers: slivers,
);
}
return new Viewport(
axisDirection: axisDirection,
offset: offset,
slivers: slivers,
);
}
@override @override
Widget build(BuildContext context) { Widget build(BuildContext context) {
final List<Widget> slivers = buildSlivers(context); final List<Widget> slivers = buildSlivers(context);
...@@ -200,20 +228,8 @@ abstract class ScrollView extends StatelessWidget { ...@@ -200,20 +228,8 @@ abstract class ScrollView extends StatelessWidget {
controller: scrollController, controller: scrollController,
physics: physics, physics: physics,
viewportBuilder: (BuildContext context, ViewportOffset offset) { viewportBuilder: (BuildContext context, ViewportOffset offset) {
if (shrinkWrap) { return buildViewport(context, offset, axisDirection, slivers);
return new ShrinkWrappingViewport( },
axisDirection: axisDirection,
offset: offset,
slivers: slivers,
);
} else {
return new Viewport(
axisDirection: axisDirection,
offset: offset,
slivers: slivers,
);
}
}
); );
return primary && scrollController != null return primary && scrollController != null
? new PrimaryScrollController.none(child: scrollable) ? new PrimaryScrollController.none(child: scrollable)
......
...@@ -12,21 +12,6 @@ export 'package:flutter/rendering.dart' show ...@@ -12,21 +12,6 @@ export 'package:flutter/rendering.dart' show
AxisDirection, AxisDirection,
GrowthDirection; GrowthDirection;
AxisDirection _getDefaultCrossAxisDirection(BuildContext context, AxisDirection axisDirection) {
assert(axisDirection != null);
switch (axisDirection) {
case AxisDirection.up:
return textDirectionToAxisDirection(Directionality.of(context));
case AxisDirection.right:
return AxisDirection.down;
case AxisDirection.down:
return textDirectionToAxisDirection(Directionality.of(context));
case AxisDirection.left:
return AxisDirection.down;
}
return null;
}
/// A widget that is bigger on the inside. /// A widget that is bigger on the inside.
/// ///
/// [Viewport] is the visual workhorse of the scrolling machinery. It displays a /// [Viewport] is the visual workhorse of the scrolling machinery. It displays a
...@@ -124,11 +109,31 @@ class Viewport extends MultiChildRenderObjectWidget { ...@@ -124,11 +109,31 @@ class Viewport extends MultiChildRenderObjectWidget {
/// The [center] must be the key of a child of the viewport. /// The [center] must be the key of a child of the viewport.
final Key center; final Key center;
/// Given a [BuildContext] and an [AxisDirection], determine the correct cross
/// axis direction.
///
/// This depends on the [Directionality] if the `axisDirection` is vertical;
/// otherwise, the default cross axis direction is downwards.
static AxisDirection getDefaultCrossAxisDirection(BuildContext context, AxisDirection axisDirection) {
assert(axisDirection != null);
switch (axisDirection) {
case AxisDirection.up:
return textDirectionToAxisDirection(Directionality.of(context));
case AxisDirection.right:
return AxisDirection.down;
case AxisDirection.down:
return textDirectionToAxisDirection(Directionality.of(context));
case AxisDirection.left:
return AxisDirection.down;
}
return null;
}
@override @override
RenderViewport createRenderObject(BuildContext context) { RenderViewport createRenderObject(BuildContext context) {
return new RenderViewport( return new RenderViewport(
axisDirection: axisDirection, axisDirection: axisDirection,
crossAxisDirection: crossAxisDirection ?? _getDefaultCrossAxisDirection(context, axisDirection), crossAxisDirection: crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection),
anchor: anchor, anchor: anchor,
offset: offset, offset: offset,
); );
...@@ -138,7 +143,7 @@ class Viewport extends MultiChildRenderObjectWidget { ...@@ -138,7 +143,7 @@ class Viewport extends MultiChildRenderObjectWidget {
void updateRenderObject(BuildContext context, RenderViewport renderObject) { void updateRenderObject(BuildContext context, RenderViewport renderObject) {
renderObject renderObject
..axisDirection = axisDirection ..axisDirection = axisDirection
..crossAxisDirection = crossAxisDirection ?? _getDefaultCrossAxisDirection(context, axisDirection) ..crossAxisDirection = crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection)
..anchor = anchor ..anchor = anchor
..offset = offset; ..offset = offset;
} }
...@@ -271,7 +276,7 @@ class ShrinkWrappingViewport extends MultiChildRenderObjectWidget { ...@@ -271,7 +276,7 @@ class ShrinkWrappingViewport extends MultiChildRenderObjectWidget {
RenderShrinkWrappingViewport createRenderObject(BuildContext context) { RenderShrinkWrappingViewport createRenderObject(BuildContext context) {
return new RenderShrinkWrappingViewport( return new RenderShrinkWrappingViewport(
axisDirection: axisDirection, axisDirection: axisDirection,
crossAxisDirection: crossAxisDirection ?? _getDefaultCrossAxisDirection(context, axisDirection), crossAxisDirection: crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection),
offset: offset, offset: offset,
); );
} }
...@@ -280,7 +285,7 @@ class ShrinkWrappingViewport extends MultiChildRenderObjectWidget { ...@@ -280,7 +285,7 @@ class ShrinkWrappingViewport extends MultiChildRenderObjectWidget {
void updateRenderObject(BuildContext context, RenderShrinkWrappingViewport renderObject) { void updateRenderObject(BuildContext context, RenderShrinkWrappingViewport renderObject) {
renderObject renderObject
..axisDirection = axisDirection ..axisDirection = axisDirection
..crossAxisDirection = crossAxisDirection ?? _getDefaultCrossAxisDirection(context, axisDirection) ..crossAxisDirection = crossAxisDirection ?? Viewport.getDefaultCrossAxisDirection(context, axisDirection)
..offset = offset; ..offset = offset;
} }
......
...@@ -292,6 +292,23 @@ abstract class PaintPattern { ...@@ -292,6 +292,23 @@ abstract class PaintPattern {
/// If no call to [Canvas.drawParagraph] was made, then this results in failure. /// If no call to [Canvas.drawParagraph] was made, then this results in failure.
void paragraph({ ui.Paragraph paragraph, dynamic offset }); void paragraph({ ui.Paragraph paragraph, dynamic offset });
/// Indicates that a shadow is expected next.
///
/// The next shadow is examined. Any arguments that are passed to this method
/// are compared to the actual [Canvas.drawShadow] call's `paint` argument,
/// and any mismatches result in failure.
///
/// To introspect the Path object (as it stands after the painting has
/// completed), the `includes` and `excludes` arguments can be provided to
/// specify points that should be considered inside or outside the path
/// (respectively).
///
/// If no call to [Canvas.drawShadow] was made, then this results in failure.
///
/// Any calls made between the last matched call (if any) and the
/// [Canvas.drawShadow] call are ignored.
void shadow({ Iterable<Offset> includes, Iterable<Offset> excludes, Color color, double elevation, bool transparentOccluder });
/// Indicates that an image is expected next. /// Indicates that an image is expected next.
/// ///
/// The next call to [Canvas.drawImage] is examined, and its arguments /// The next call to [Canvas.drawImage] is examined, and its arguments
...@@ -637,6 +654,11 @@ class _TestRecordingCanvasPatternMatcher extends _TestRecordingCanvasMatcher imp ...@@ -637,6 +654,11 @@ class _TestRecordingCanvasPatternMatcher extends _TestRecordingCanvasMatcher imp
_predicates.add(new _FunctionPaintPredicate(#drawParagraph, <dynamic>[paragraph, offset])); _predicates.add(new _FunctionPaintPredicate(#drawParagraph, <dynamic>[paragraph, offset]));
} }
@override
void shadow({ Iterable<Offset> includes, Iterable<Offset> excludes, Color color, double elevation, bool transparentOccluder }) {
_predicates.add(new _ShadowPredicate(includes: includes, excludes: excludes, color: color, elevation: elevation, transparentOccluder: transparentOccluder));
}
@override @override
void image({ ui.Image image, double x, double y, Color color, double strokeWidth, bool hasMaskFilter, PaintingStyle style }) { void image({ ui.Image image, double x, double y, Color color, double strokeWidth, bool hasMaskFilter, PaintingStyle style }) {
_predicates.add(new _DrawImagePaintPredicate(image: image, x: x, y: y, color: color, strokeWidth: strokeWidth, hasMaskFilter: hasMaskFilter, style: style)); _predicates.add(new _DrawImagePaintPredicate(image: image, x: x, y: y, color: color, strokeWidth: strokeWidth, hasMaskFilter: hasMaskFilter, style: style));
...@@ -709,6 +731,24 @@ class _TestRecordingCanvasPatternMatcher extends _TestRecordingCanvasMatcher imp ...@@ -709,6 +731,24 @@ class _TestRecordingCanvasPatternMatcher extends _TestRecordingCanvasMatcher imp
abstract class _PaintPredicate { abstract class _PaintPredicate {
void match(Iterator<RecordedInvocation> call); void match(Iterator<RecordedInvocation> call);
@protected
void checkMethod(Iterator<RecordedInvocation> call, Symbol symbol) {
int others = 0;
final RecordedInvocation firstCall = call.current;
while (!call.current.invocation.isMethod || call.current.invocation.memberName != symbol) {
others += 1;
if (!call.moveNext())
throw new _MismatchedCall(
'It called $others other method${ others == 1 ? "" : "s" } on the canvas, '
'the first of which was $firstCall, but did not '
'call ${_symbolName(symbol)}() at the time where $this was expected.',
'The first method that was called when the call to ${_symbolName(symbol)}() '
'was expected, $firstCall, was called with the following stack:',
firstCall,
);
}
}
@override @override
String toString() { String toString() {
throw new FlutterError('$runtimeType does not implement toString.'); throw new FlutterError('$runtimeType does not implement toString.');
...@@ -734,19 +774,7 @@ abstract class _DrawCommandPaintPredicate extends _PaintPredicate { ...@@ -734,19 +774,7 @@ abstract class _DrawCommandPaintPredicate extends _PaintPredicate {
@override @override
void match(Iterator<RecordedInvocation> call) { void match(Iterator<RecordedInvocation> call) {
int others = 0; checkMethod(call, symbol);
final RecordedInvocation firstCall = call.current;
while (!call.current.invocation.isMethod || call.current.invocation.memberName != symbol) {
others += 1;
if (!call.moveNext())
throw new _MismatchedCall(
'It called $others other method${ others == 1 ? "" : "s" } on the canvas, '
'the first of which was $firstCall, but did not '
'call $methodName at the time where $this was expected.',
'The stack for the call to $firstCall was:',
firstCall,
);
}
final int actualArgumentCount = call.current.invocation.positionalArguments.length; final int actualArgumentCount = call.current.invocation.positionalArguments.length;
if (actualArgumentCount != argumentCount) if (actualArgumentCount != argumentCount)
throw 'It called $methodName with $actualArgumentCount argument${actualArgumentCount == 1 ? "" : "s"}; expected $argumentCount.'; throw 'It called $methodName with $actualArgumentCount argument${actualArgumentCount == 1 ? "" : "s"}; expected $argumentCount.';
...@@ -1008,6 +1036,81 @@ class _ArcPaintPredicate extends _DrawCommandPaintPredicate { ...@@ -1008,6 +1036,81 @@ class _ArcPaintPredicate extends _DrawCommandPaintPredicate {
); );
} }
class _ShadowPredicate extends _PaintPredicate {
_ShadowPredicate({ this.includes, this.excludes, this.color, this.elevation, this.transparentOccluder });
final Iterable<Offset> includes;
final Iterable<Offset> excludes;
final Color color;
final double elevation;
final bool transparentOccluder;
static const Symbol symbol = #drawShadow;
String get methodName => _symbolName(symbol);
@protected
void verifyArguments(List<dynamic> arguments) {
if (arguments.length != 4)
throw 'It called $methodName with ${arguments.length} arguments; expected 4.';
final Path pathArgument = arguments[0];
if (includes != null) {
for (Offset offset in includes) {
if (!pathArgument.contains(offset))
throw 'It called $methodName with a path that unexpectedly did not contain $offset.';
}
}
if (excludes != null) {
for (Offset offset in excludes) {
if (pathArgument.contains(offset))
throw 'It called $methodName with a path that unexpectedly contained $offset.';
}
}
final Color actualColor = arguments[1];
if (color != null && actualColor != color)
throw 'It called $methodName with a color, $actualColor, which was not exactly the expected color ($color).';
final double actualElevation = arguments[2];
if (elevation != null && actualElevation != elevation)
throw 'It called $methodName with an elevation, $actualElevation, which was not exactly the expected value ($elevation).';
final bool actualTransparentOccluder = arguments[3];
if (transparentOccluder != null && actualTransparentOccluder != transparentOccluder)
throw 'It called $methodName with a transparentOccluder value, $actualTransparentOccluder, which was not exactly the expected value ($transparentOccluder).';
}
@override
void match(Iterator<RecordedInvocation> call) {
checkMethod(call, symbol);
verifyArguments(call.current.invocation.positionalArguments);
call.moveNext();
}
@protected
void debugFillDescription(List<String> description) {
if (includes != null && excludes != null) {
description.add('that contains $includes and does not contain $excludes');
} else if (includes != null) {
description.add('that contains $includes');
} else if (excludes != null) {
description.add('that does not contain $excludes');
}
if (color != null)
description.add('$color');
if (elevation != null)
description.add('elevation: $elevation');
if (transparentOccluder != null)
description.add('transparentOccluder: $transparentOccluder');
}
@override
String toString() {
final List<String> description = <String>[];
debugFillDescription(description);
String result = methodName;
if (description.isNotEmpty)
result += ' with ${description.join(", ")}';
return result;
}
}
class _DrawImagePaintPredicate extends _DrawCommandPaintPredicate { class _DrawImagePaintPredicate extends _DrawCommandPaintPredicate {
_DrawImagePaintPredicate({ this.image, this.x, this.y, Color color, double strokeWidth, bool hasMaskFilter, PaintingStyle style }) : super( _DrawImagePaintPredicate({ this.image, this.x, this.y, Color color, double strokeWidth, bool hasMaskFilter, PaintingStyle style }) : super(
#drawImage, 'an image', 3, 2, color: color, strokeWidth: strokeWidth, hasMaskFilter: hasMaskFilter, style: style #drawImage, 'an image', 3, 2, color: color, strokeWidth: strokeWidth, hasMaskFilter: hasMaskFilter, style: style
...@@ -1128,20 +1231,7 @@ class _FunctionPaintPredicate extends _PaintPredicate { ...@@ -1128,20 +1231,7 @@ class _FunctionPaintPredicate extends _PaintPredicate {
@override @override
void match(Iterator<RecordedInvocation> call) { void match(Iterator<RecordedInvocation> call) {
int others = 0; checkMethod(call, symbol);
final RecordedInvocation firstCall = call.current;
while (!call.current.invocation.isMethod || call.current.invocation.memberName != symbol) {
others += 1;
if (!call.moveNext())
throw new _MismatchedCall(
'It called $others other method${ others == 1 ? "" : "s" } on the canvas, '
'the first of which was $firstCall, but did not '
'call ${_symbolName(symbol)}() at the time where $this was expected.',
'The first method that was called when the call to ${_symbolName(symbol)}() '
'was expected, $firstCall, was called with the following stack:',
firstCall,
);
}
if (call.current.invocation.positionalArguments.length != arguments.length) if (call.current.invocation.positionalArguments.length != arguments.length)
throw 'It called ${_symbolName(symbol)} with ${call.current.invocation.positionalArguments.length} arguments; expected ${arguments.length}.'; throw 'It called ${_symbolName(symbol)} with ${call.current.invocation.positionalArguments.length} arguments; expected ${arguments.length}.';
for (int index = 0; index < arguments.length; index += 1) { for (int index = 0; index < arguments.length; index += 1) {
...@@ -1169,20 +1259,7 @@ class _FunctionPaintPredicate extends _PaintPredicate { ...@@ -1169,20 +1259,7 @@ class _FunctionPaintPredicate extends _PaintPredicate {
class _SaveRestorePairPaintPredicate extends _PaintPredicate { class _SaveRestorePairPaintPredicate extends _PaintPredicate {
@override @override
void match(Iterator<RecordedInvocation> call) { void match(Iterator<RecordedInvocation> call) {
int others = 0; checkMethod(call, #save);
final RecordedInvocation firstCall = call.current;
while (!call.current.invocation.isMethod || call.current.invocation.memberName != #save) {
others += 1;
if (!call.moveNext())
throw new _MismatchedCall(
'It called $others other method${ others == 1 ? "" : "s" } on the canvas, '
'the first of which was $firstCall, but did not '
'call save() at the time where $this was expected.',
'The first method that was called when the call to save() '
'was expected, $firstCall, was called with the following stack:',
firstCall,
);
}
int depth = 1; int depth = 1;
while (depth > 0) { while (depth > 0) {
if (!call.moveNext()) if (!call.moveNext())
......
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