From 846a073ab97d7c4fed8be63ff9ef736163076757 Mon Sep 17 00:00:00 2001 From: Hixie Date: Fri, 2 Oct 2015 11:22:16 -0700 Subject: [PATCH 01/27] Regression test for #1215 I'm not sure this specific incarnation of the test ever crashed, since the original test depended on user interaction and now works fine, but just in case, here's a regression test for it so I can close that issue. This also slightly changes the Widget.toString() output to include the key since that will make debugging easier. --- .../flutter/lib/src/widgets/framework.dart | 6 +- .../unit/test/widget/dismissable_test.dart | 57 +++++++++++++++++-- 2 files changed, 58 insertions(+), 5 deletions(-) diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index 617ff6057a..c0cce2b28f 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -181,7 +181,11 @@ abstract class Widget { /// Inflates this configuration to a concrete instance. Element createElement(); - String toString() => '$runtimeType'; + String toString() { + if (key == null) + return '$runtimeType'; + return '$runtimeType-$key'; + } } /// RenderObjectWidgets provide the configuration for [RenderObjectElement]s, diff --git a/packages/unit/test/widget/dismissable_test.dart b/packages/unit/test/widget/dismissable_test.dart index 8d436dd1d2..bb4086e65f 100644 --- a/packages/unit/test/widget/dismissable_test.dart +++ b/packages/unit/test/widget/dismissable_test.dart @@ -1,3 +1,4 @@ +import 'package:sky/rendering.dart'; import 'package:sky/widgets.dart'; import 'package:test/test.dart'; @@ -44,13 +45,11 @@ Widget widgetBuilder() { ); } -void dismissItem(WidgetTester tester, int item, { DismissDirection gestureDirection }) { +void dismissElement(WidgetTester tester, Element itemElement, { DismissDirection gestureDirection }) { + assert(itemElement != null); assert(gestureDirection != DismissDirection.horizontal); assert(gestureDirection != DismissDirection.vertical); - Element itemElement = tester.findText(item.toString()); - expect(itemElement, isNotNull); - Point downLocation; Point upLocation; switch(gestureDirection) { @@ -84,12 +83,35 @@ void dismissItem(WidgetTester tester, int item, { DismissDirection gestureDirect tester.dispatchEvent(pointer.down(downLocation), downLocation); tester.dispatchEvent(pointer.move(upLocation), downLocation); tester.dispatchEvent(pointer.up(), downLocation); +} + +void dismissItem(WidgetTester tester, int item, { DismissDirection gestureDirection }) { + assert(gestureDirection != DismissDirection.horizontal); + assert(gestureDirection != DismissDirection.vertical); + + Element itemElement = tester.findText(item.toString()); + expect(itemElement, isNotNull); + + dismissElement(tester, itemElement, gestureDirection: gestureDirection); tester.pumpWidget(widgetBuilder()); // start the resize animation tester.pumpWidget(widgetBuilder(), const Duration(seconds: 1)); // finish the resize animation tester.pumpWidget(widgetBuilder(), const Duration(seconds: 1)); // dismiss } +class Test1215DismissableComponent extends StatelessComponent { + Test1215DismissableComponent(this.text); + final String text; + Widget build(BuildContext context) { + return new Dismissable( + child: new AspectRatio( + aspectRatio: 1.0, + child: new Text(this.text) + ) + ); + } +} + void main() { test('Horizontal drag triggers dismiss scrollDirection=vertical', () { testWidgets((WidgetTester tester) { @@ -230,4 +252,31 @@ void main() { tester.pumpWidget(widgetBuilder()); }); }); + + // This one is for + // https://github.com/flutter/engine/issues/1215 + test('dismissing bottom then top (smoketest)', () { + testWidgets((WidgetTester tester) { + tester.pumpWidget(new Center( + child: new Container( + width: 100.0, + height: 1000.0, + child: new Column([ + new Test1215DismissableComponent('1'), + new Test1215DismissableComponent('2') + ]) + ) + )); + expect(tester.findText('1'), isNotNull); + expect(tester.findText('2'), isNotNull); + dismissElement(tester, tester.findText('2'), gestureDirection: DismissDirection.right); + tester.pump(new Duration(seconds: 1)); + expect(tester.findText('1'), isNotNull); + expect(tester.findText('2'), isNull); + dismissElement(tester, tester.findText('1'), gestureDirection: DismissDirection.right); + tester.pump(new Duration(seconds: 1)); + expect(tester.findText('1'), isNull); + expect(tester.findText('2'), isNull); + }); + }); } From 48a6cd839ac95fe489ec3395ba132ad28888efcb Mon Sep 17 00:00:00 2001 From: Viktor Lidholt Date: Mon, 28 Sep 2015 15:44:47 -0700 Subject: [PATCH 02/27] Sprite physics, first iteration --- examples/game/pubspec.yaml | 3 + examples/game/test_bed.dart | 59 ++++ examples/game/test_physics.dart | 104 +++++++ packages/flutter_sprites/lib/node.dart | 46 +++ .../flutter_sprites/lib/physics_body.dart | 138 +++++++++ .../flutter_sprites/lib/physics_node.dart | 283 ++++++++++++++++++ .../flutter_sprites/lib/physics_shape.dart | 65 ++++ packages/flutter_sprites/lib/skysprites.dart | 4 + packages/flutter_sprites/lib/sprite_box.dart | 34 ++- packages/flutter_sprites/pubspec.yaml | 3 + 10 files changed, 732 insertions(+), 7 deletions(-) create mode 100644 examples/game/test_bed.dart create mode 100644 examples/game/test_physics.dart create mode 100644 packages/flutter_sprites/lib/physics_body.dart create mode 100644 packages/flutter_sprites/lib/physics_node.dart create mode 100644 packages/flutter_sprites/lib/physics_shape.dart diff --git a/examples/game/pubspec.yaml b/examples/game/pubspec.yaml index 69c481bc3f..07125ee96b 100644 --- a/examples/game/pubspec.yaml +++ b/examples/game/pubspec.yaml @@ -3,6 +3,7 @@ dependencies: sky: any sky_tools: any skysprites: any + box2d: any dependency_overrides: material_design_icons: path: ../../sky/packages/material_design_icons @@ -10,3 +11,5 @@ dependency_overrides: path: ../../sky/packages/sky skysprites: path: ../../skysprites + box2d: + path: ../../../../box2d.dart diff --git a/examples/game/test_bed.dart b/examples/game/test_bed.dart new file mode 100644 index 0000000000..653a566c0e --- /dev/null +++ b/examples/game/test_bed.dart @@ -0,0 +1,59 @@ +import 'dart:sky'; + +import 'package:sky/material.dart'; +import 'package:sky/rendering.dart'; +import 'package:sky/services.dart'; +import 'package:sky/widgets.dart'; +import 'package:skysprites/skysprites.dart'; + +AssetBundle _initBundle() { + if (rootBundle != null) + return rootBundle; + return new NetworkAssetBundle(Uri.base); +} + +final AssetBundle _bundle = _initBundle(); + +ImageMap _images; +SpriteSheet _spriteSheet; +TestBedApp _app; + +main() async { + _images = new ImageMap(_bundle); + + await _images.load([ + 'assets/sprites.png' + ]); + + String json = await _bundle.loadString('assets/sprites.json'); + _spriteSheet = new SpriteSheet(_images['assets/sprites.png'], json); + + _app = new TestBedApp(); + runApp(_app); +} + +class TestBedApp extends App { + + Widget build() { + ThemeData theme = new ThemeData( + brightness: ThemeBrightness.light, + primarySwatch: Colors.purple + ); + + return new Theme( + data: theme, + child: new Title( + title: 'Test Bed', + child: new SpriteWidget( + new TestBed(), + SpriteBoxTransformMode.letterbox + ) + ) + ); + } +} + +class TestBed extends NodeWithSize { + TestBed() : super(new Size(1024.0, 1024.0)) { + } +} diff --git a/examples/game/test_physics.dart b/examples/game/test_physics.dart new file mode 100644 index 0000000000..1ba81aa33b --- /dev/null +++ b/examples/game/test_physics.dart @@ -0,0 +1,104 @@ +import 'dart:sky'; + +import 'package:sky/material.dart'; +import 'package:sky/rendering.dart'; +import 'package:sky/services.dart'; +import 'package:sky/widgets.dart'; +import 'package:skysprites/skysprites.dart'; + +AssetBundle _initBundle() { + if (rootBundle != null) + return rootBundle; + return new NetworkAssetBundle(Uri.base); +} + +final AssetBundle _bundle = _initBundle(); + +ImageMap _images; +SpriteSheet _spriteSheet; +TestBedApp _app; + +main() async { + _images = new ImageMap(_bundle); + + await _images.load([ + 'assets/sprites.png' + ]); + + String json = await _bundle.loadString('assets/sprites.json'); + _spriteSheet = new SpriteSheet(_images['assets/sprites.png'], json); + + _app = new TestBedApp(); + runApp(_app); +} + +class TestBedApp extends App { + + Widget build() { + ThemeData theme = new ThemeData( + brightness: ThemeBrightness.light, + primarySwatch: Colors.purple + ); + + return new Theme( + data: theme, + child: new Title( + title: 'Test Physics', + child: new SpriteWidget( + new TestBed(), + SpriteBoxTransformMode.letterbox + ) + ) + ); + } +} + +class TestBed extends NodeWithSize { + Sprite _ship; + Sprite _obstacle; + + TestBed() : super(new Size(1024.0, 1024.0)) { + PhysicsNode physicsNode = new PhysicsNode(new Offset(0.0, 100.0)); + + _ship = new Sprite(_spriteSheet["ship.png"]); + _ship.position = new Point(512.0, 512.0); + _ship.size = new Size(64.0, 64.0); + _ship.physicsBody = new PhysicsBody( + new PhysicsShapeGroup([ + new PhysicsShapeCircle(Point.origin, 32.0), + new PhysicsShapePolygon([new Point(0.0, 0.0), new Point(50.0, 0.0), new Point(50.0, 50.0), new Point(0.0, 50.0)]) + ]), + friction: 0.5, + tag: "ship" + ); + physicsNode.addChild(_ship); + + _obstacle = new Sprite(_spriteSheet["ship.png"]); + _obstacle.position = new Point(532.0, 800.0); + _obstacle.size = new Size(64.0, 64.0); + _obstacle.physicsBody = new PhysicsBody( + new PhysicsShapeCircle(Point.origin, 32.0), + type: PhysicsBodyType.static, + friction: 0.5, + tag: "obstacle" + ); + physicsNode.addChild(_obstacle); + physicsNode.addContactCallback(myCallback, "obstacle", "ship", PhysicsContactType.begin); + + addChild(physicsNode); + + userInteractionEnabled = true; + } + + void myCallback(PhysicsContactType type, PhysicsContact contact) { + print("CONTACT type: $type"); + } + + bool handleEvent(SpriteBoxEvent event) { + if (event.type == "pointerdown") { + Point pos = convertPointToNodeSpace(event.boxPosition); + _ship.position = pos; + } + return true; + } +} diff --git a/packages/flutter_sprites/lib/node.dart b/packages/flutter_sprites/lib/node.dart index 9d51195a92..0896b9c123 100644 --- a/packages/flutter_sprites/lib/node.dart +++ b/packages/flutter_sprites/lib/node.dart @@ -131,6 +131,19 @@ class Node { void set rotation(double rotation) { assert(rotation != null); + + if (_physicsBody != null && parent is PhysicsNode) { + PhysicsNode physicsNode = parent; + physicsNode._updateRotation(this.physicsBody, rotation); + return; + } + + _rotation = rotation; + invalidateTransformMatrix(); + } + + void _setRotationFromPhysics(double rotation) { + assert(rotation != null); _rotation = rotation; invalidateTransformMatrix(); } @@ -142,6 +155,19 @@ class Node { void set position(Point position) { assert(position != null); + + if (_physicsBody != null && parent is PhysicsNode) { + PhysicsNode physicsNode = parent; + physicsNode._updatePosition(this.physicsBody, position); + return; + } + + _position = position; + invalidateTransformMatrix(); + } + + void _setPositionFromPhysics(Point position) { + assert(position != null); _position = position; invalidateTransformMatrix(); } @@ -609,4 +635,24 @@ class Node { bool handleEvent(SpriteBoxEvent event) { return false; } + + // Physics + + PhysicsBody _physicsBody; + + PhysicsBody get physicsBody => _physicsBody; + + set physicsBody(PhysicsBody physicsBody) { + if (parent != null) { + assert(parent is PhysicsNode); + + if (physicsBody == null) { + physicsBody._detach(); + } else { + physicsBody._attach(parent, this); + } + } + + _physicsBody = physicsBody; + } } diff --git a/packages/flutter_sprites/lib/physics_body.dart b/packages/flutter_sprites/lib/physics_body.dart new file mode 100644 index 0000000000..99c8ea426e --- /dev/null +++ b/packages/flutter_sprites/lib/physics_body.dart @@ -0,0 +1,138 @@ +part of skysprites; + +enum PhysicsBodyType { + static, + dynamic +} + +class PhysicsBody { + PhysicsBody(this.shape, { + this.tag: null, + this.type: PhysicsBodyType.dynamic, + this.density: 1.0, + this.friction: 0.0, + this.restitution: 0.0, + this.isSensor: false, + this.linearVelocity: Offset.zero, + this.angularVelocity: 0.0, + this.linearDampening: 0.0, + this.angularDampening: 0.0, + this.allowSleep: true, + this.awake: true, + this.fixedRotation: false, + this.bullet: false, + this.active: true, + this.gravityScale: 1.0 + }); + + Object tag; + + PhysicsShape shape; + + PhysicsBodyType type; + + double density; + double friction; + double restitution; + bool isSensor; + + Offset linearVelocity; + double angularVelocity; + + double linearDampening; + + double angularDampening; + + bool allowSleep; + + bool awake; + + bool fixedRotation; + + bool bullet; + + bool active; + + double gravityScale; + + PhysicsNode _physicsNode; + Node _node; + + box2d.Body _body; + + bool _attached = false; + + void _attach(PhysicsNode physicsNode, Node node) { + assert(_attached == false); + + // Create BodyDef + box2d.BodyDef bodyDef = new box2d.BodyDef(); + bodyDef.linearVelocity = new Vector2(linearVelocity.dx, linearVelocity.dy); + bodyDef.angularVelocity = angularVelocity; + bodyDef.linearDamping = linearDampening; + bodyDef.angularDamping = angularDampening; + bodyDef.allowSleep = allowSleep; + bodyDef.awake = awake; + bodyDef.fixedRotation = fixedRotation; + bodyDef.bullet = bullet; + bodyDef.active = active; + bodyDef.gravityScale = gravityScale; + if (type == PhysicsBodyType.dynamic) + bodyDef.type = box2d.BodyType.DYNAMIC; + else + bodyDef.type = box2d.BodyType.STATIC; + + double conv = physicsNode.b2WorldToNodeConversionFactor; + bodyDef.position = new Vector2(node.position.x / conv, node.position.y / conv); + bodyDef.angle = radians(node.rotation); + + // Create Body + _body = physicsNode.b2World.createBody(bodyDef); + + // Create FixtureDef + box2d.FixtureDef fixtureDef = new box2d.FixtureDef(); + fixtureDef.friction = friction; + fixtureDef.restitution = restitution; + fixtureDef.density = density; + fixtureDef.isSensor = isSensor; + + // Get shapes + List b2Shapes = []; + List physicsShapes = []; + _addB2Shapes(physicsNode, shape, b2Shapes, physicsShapes); + + // Create fixtures + for (int i = 0; i < b2Shapes.length; i++) { + box2d.Shape b2Shape = b2Shapes[i]; + PhysicsShape physicsShape = physicsShapes[i]; + + fixtureDef.shape = b2Shape; + box2d.Fixture fixture = _body.createFixtureFromFixtureDef(fixtureDef); + fixture.userData = physicsShape; + } + _body.userData = this; + + _physicsNode = physicsNode; + _node = node; + + _attached = true; + } + + void _detach() { + if (_attached) { + _physicsNode.b2World.destroyBody(_body); + _attached = false; + } + } + + void _addB2Shapes(PhysicsNode physicsNode, PhysicsShape shape, List b2Shapes, List physicsShapes) { + if (shape is PhysicsShapeGroup) { + for (PhysicsShape child in shape.shapes) { + _addB2Shapes(physicsNode, child, b2Shapes, physicsShapes); + } + } else { + b2Shapes.add(shape.getB2Shape(physicsNode)); + physicsShapes.add(shape); + } + } +} diff --git a/packages/flutter_sprites/lib/physics_node.dart b/packages/flutter_sprites/lib/physics_node.dart new file mode 100644 index 0000000000..bc19c6adbd --- /dev/null +++ b/packages/flutter_sprites/lib/physics_node.dart @@ -0,0 +1,283 @@ +part of skysprites; + +enum PhysicsContactType { + preSolve, + postSolve, + begin, + end +} + +typedef void PhysicsContactCallback(PhysicsContactType type, PhysicsContact contact); + +class PhysicsNode extends Node { + PhysicsNode(Offset gravity) { + b2World = new box2d.World.withGravity( + new Vector2( + gravity.dx / b2WorldToNodeConversionFactor, + gravity.dy / b2WorldToNodeConversionFactor)); + _init(); + } + + PhysicsNode.fromB2World(this.b2World, this.b2WorldToNodeConversionFactor) { + _init(); + } + + void _init() { + _contactHandler = new _ContactHandler(this); + b2World.setContactListener(_contactHandler); + } + + box2d.World b2World; + + _ContactHandler _contactHandler; + + double b2WorldToNodeConversionFactor = 500.0; + + Offset get gravity { + Vector2 g = b2World.getGravity(); + return new Offset(g.x, g.y); + } + + set gravity(Offset gravity) { + // Convert from points/s^2 to m/s^2 + b2World.setGravity(new Vector2(gravity.dx / b2WorldToNodeConversionFactor, + gravity.dy / b2WorldToNodeConversionFactor)); + } + + bool get allowSleep => b2World.isAllowSleep(); + + set allowSleep(bool allowSleep) { + b2World.setAllowSleep(allowSleep); + } + + bool get subStepping => b2World.isSubStepping(); + + set subStepping(bool subStepping) { + b2World.setSubStepping(subStepping); + } + + void _stepPhysics(double dt) { + // Calculate a step in the simulation + b2World.stepDt(dt, 10, 10); + + // Iterate over the bodies + for (box2d.Body b2Body = b2World.bodyList; b2Body != null; b2Body = b2Body.getNext()) { + // Update visual position and rotation + PhysicsBody body = b2Body.userData; + body._node._setPositionFromPhysics(new Point( + b2Body.position.x * b2WorldToNodeConversionFactor, + b2Body.position.y * b2WorldToNodeConversionFactor + )); + + body._node._setRotationFromPhysics(degrees(b2Body.getAngle())); + } + } + + void _updatePosition(PhysicsBody body, Point position) { + Vector2 newPos = new Vector2( + position.x / b2WorldToNodeConversionFactor, + position.y / b2WorldToNodeConversionFactor + ); + double angle = body._body.getAngle(); + body._body.setTransform(newPos, angle); + body._body.setAwake(true); + } + + void _updateRotation(PhysicsBody body, double rotation) { + Vector2 pos = body._body.position; + double newAngle = radians(rotation); + body._body.setTransform(pos, newAngle); + body._body.setAwake(true); + } + + void addChild(Node node) { + super.addChild(node); + if (node.physicsBody != null) { + node.physicsBody._attach(this, node); + } + } + + void removeChild(Node node) { + super.removeChild(node); + if (node.physicsBody != null) { + node.physicsBody._detach(); + } + } + + void addContactCallback(PhysicsContactCallback callback, Object tagA, Object tagB, [PhysicsContactType type]) { + _contactHandler.addContactCallback(callback, tagA, tagB, type); + } + + void paint(PaintingCanvas canvas) { + super.paint(canvas); + paintDebug(canvas); + } + + void paintDebug(PaintingCanvas canvas) { + Paint shapePaint = new Paint(); + shapePaint.setStyle(sky.PaintingStyle.stroke); + shapePaint.strokeWidth = 1.0; + + for (box2d.Body body = b2World.bodyList; body != null; body = body.getNext()) { + canvas.save(); + + Point point = new Point( + body.position.x * b2WorldToNodeConversionFactor, + body.position.y * b2WorldToNodeConversionFactor); + + canvas.translate(point.x, point.y); + canvas.rotate(body.getAngle()); + + if (body.getType() == box2d.BodyType.DYNAMIC) { + if (body.isAwake()) + shapePaint.color = new Color(0xff00ff00); + else + shapePaint.color = new Color(0xff666666); + } + else if (body.getType() == box2d.BodyType.STATIC) + shapePaint.color = new Color(0xffff0000); + else if (body.getType() == box2d.BodyType.KINEMATIC) + shapePaint.color = new Color(0xffff9900); + + for (box2d.Fixture fixture = body.getFixtureList(); fixture != null; fixture = fixture.getNext()) { + box2d.Shape shape = fixture.getShape(); + + if (shape.shapeType == box2d.ShapeType.CIRCLE) { + box2d.CircleShape circle = shape; + Point cp = new Point( + circle.p.x * b2WorldToNodeConversionFactor, + circle.p.y * b2WorldToNodeConversionFactor + ); + double radius = circle.radius * b2WorldToNodeConversionFactor; + canvas.drawCircle(cp, radius, shapePaint); + } else if (shape.shapeType == box2d.ShapeType.POLYGON) { + box2d.PolygonShape poly = shape; + List points = []; + for (int i = 0; i < poly.getVertexCount(); i++) { + Vector2 vertex = poly.getVertex(i); + Point pt = new Point( + vertex.x * b2WorldToNodeConversionFactor, + vertex.y * b2WorldToNodeConversionFactor + ); + points.add(pt); + } + if (points.length >= 2) { + for (int i = 0; i < points.length; i++) { + canvas.drawLine(points[i], points[(i + 1) % points.length], shapePaint); + } + } + } + } + canvas.restore(); + } + } +} + +class PhysicsContact { + PhysicsContact( + this.nodeA, + this.nodeB, + this.shapeA, + this.shapeB, + this.isTouching, + this.isEnabled + ); + + final Node nodeA; + final Node nodeB; + final PhysicsShape shapeA; + final PhysicsShape shapeB; + final isTouching; + bool isEnabled; +} + +class _ContactCallbackInfo { + _ContactCallbackInfo(this.callback, this.tagA, this.tagB, this.type); + + PhysicsContactCallback callback; + Object tagA; + Object tagB; + PhysicsContactType type; +} + +class _ContactHandler extends box2d.ContactListener { + _ContactHandler(this.physicsNode); + + PhysicsNode physicsNode; + + List<_ContactCallbackInfo> callbackInfos = []; + + void addContactCallback(PhysicsContactCallback callback, Object tagA, Object tagB, PhysicsContactType type) { + callbackInfos.add(new _ContactCallbackInfo(callback, tagA, tagB, type)); + } + + void handleCallback(PhysicsContactType type, box2d.Contact b2Contact, box2d.Manifold oldManifold, box2d.ContactImpulse impulse) { + // Get info about the contact + PhysicsBody bodyA = b2Contact.fixtureA.getBody().userData; + PhysicsBody bodyB = b2Contact.fixtureB.getBody().userData; + box2d.Fixture fixtureA = b2Contact.fixtureA; + box2d.Fixture fixtureB = b2Contact.fixtureB; + + // Match callback with added callbacks + for (_ContactCallbackInfo info in callbackInfos) { + // Check that type is matching + if (info.type != null && info.type != type) + continue; + + // Check if there is a match + bool matchA = (info.tagA == null) || info.tagA == bodyA.tag; + bool matchB = (info.tagB == null) || info.tagB == bodyB.tag; + + bool match = (matchA && matchB); + if (!match) { + // Check if there is a match if we swap a & b + bool matchA = (info.tagA == null) || info.tagA == bodyB.tag; + bool matchB = (info.tagB == null) || info.tagB == bodyA.tag; + + match = (matchA && matchB); + if (match) { + // Swap a & b + PhysicsBody tempBody = bodyA; + bodyA = bodyB; + bodyB = tempBody; + + box2d.Fixture tempFixture = fixtureA; + fixtureA = fixtureB; + fixtureB = tempFixture; + } + } + + if (match) { + // We have contact and a matched callback, setup contact info + PhysicsContact contact = new PhysicsContact( + bodyA._node, + bodyB._node, + fixtureA.userData, + fixtureB.userData, + b2Contact.isTouching(), + b2Contact.isEnabled() + ); + // Make callback + info.callback(type, contact); + + // Update Box2D contact + b2Contact.setEnabled(contact.isEnabled); + } + } + } + + void beginContact(box2d.Contact contact) { + handleCallback(PhysicsContactType.begin, contact, null, null); + } + + void endContact(box2d.Contact contact) { + handleCallback(PhysicsContactType.end, contact, null, null); + } + + void preSolve(box2d.Contact contact, box2d.Manifold oldManifold) { + handleCallback(PhysicsContactType.preSolve, contact, oldManifold, null); + } + void postSolve(box2d.Contact contact, box2d.ContactImpulse impulse) { + handleCallback(PhysicsContactType.postSolve, contact, null, impulse); + } +} diff --git a/packages/flutter_sprites/lib/physics_shape.dart b/packages/flutter_sprites/lib/physics_shape.dart new file mode 100644 index 0000000000..657b85d1ec --- /dev/null +++ b/packages/flutter_sprites/lib/physics_shape.dart @@ -0,0 +1,65 @@ +part of skysprites; + +abstract class PhysicsShape { + + box2d.Shape _b2Shape; + + Object userObject; + + box2d.Shape getB2Shape(PhysicsNode node) { + if (_b2Shape == null) { + _b2Shape = _createB2Shape(node); + } + return _b2Shape; + } + + box2d.Shape _createB2Shape(PhysicsNode node); +} + +class PhysicsShapeCircle extends PhysicsShape { + PhysicsShapeCircle(this.point, this.radius); + + final Point point; + final double radius; + + box2d.Shape _createB2Shape(PhysicsNode node) { + box2d.CircleShape shape = new box2d.CircleShape(); + shape.p.x = point.x / node.b2WorldToNodeConversionFactor; + shape.p.y = point.y / node.b2WorldToNodeConversionFactor; + shape.radius = radius / node.b2WorldToNodeConversionFactor; + return shape; + } +} + +class PhysicsShapePolygon extends PhysicsShape { + + PhysicsShapePolygon(this.points); + + final List points; + + box2d.Shape _createB2Shape(PhysicsNode node) { + List vectors = []; + for (Point point in points) { + Vector2 vec = new Vector2( + point.x / node.b2WorldToNodeConversionFactor, + point.y / node.b2WorldToNodeConversionFactor + ); + vectors.add(vec); + } + + box2d.PolygonShape shape = new box2d.PolygonShape(); + shape.set(vectors, vectors.length); + return shape; + } +} + +class PhysicsShapeGroup extends PhysicsShape { + + PhysicsShapeGroup(this.shapes); + + final List shapes; + + box2d.Shape _createB2Shape(PhysicsNode node) { + return null; + } +} diff --git a/packages/flutter_sprites/lib/skysprites.dart b/packages/flutter_sprites/lib/skysprites.dart index 08fd7cc2df..ad662c65d0 100644 --- a/packages/flutter_sprites/lib/skysprites.dart +++ b/packages/flutter_sprites/lib/skysprites.dart @@ -10,6 +10,7 @@ import 'dart:math' as math; import 'dart:typed_data'; import 'dart:sky' as sky; +import 'package:box2d/box2d.dart' as box2d; import 'package:mojo/core.dart'; import 'package:sky_services/media/media.mojom.dart'; import 'package:sky/animation.dart'; @@ -31,6 +32,9 @@ part 'node.dart'; part 'node3d.dart'; part 'node_with_size.dart'; part 'particle_system.dart'; +part 'physics_body.dart'; +part 'physics_node.dart'; +part 'physics_shape.dart'; part 'sound.dart'; part 'sound_manager.dart'; part 'sprite.dart'; diff --git a/packages/flutter_sprites/lib/sprite_box.dart b/packages/flutter_sprites/lib/sprite_box.dart index aeecfd0168..07e6d317d0 100644 --- a/packages/flutter_sprites/lib/sprite_box.dart +++ b/packages/flutter_sprites/lib/sprite_box.dart @@ -76,6 +76,8 @@ class SpriteBox extends RenderBox { List _constrainedNodes; + List _physicsNodes; + Rect _visibleArea; Rect get visibleArea { @@ -138,15 +140,17 @@ class SpriteBox extends RenderBox { // Adding and removing nodes - _registerNode(Node node) { + void _registerNode(Node node) { _actionControllers = null; _eventTargets = null; + _physicsNodes = null; if (node == null || node.constraints != null) _constrainedNodes = null; } - _deregisterNode(Node node) { + void _deregisterNode(Node node) { _actionControllers = null; _eventTargets = null; + _physicsNodes = null; if (node == null || node.constraints != null) _constrainedNodes = null; } @@ -359,6 +363,7 @@ class SpriteBox extends RenderBox { _callConstraintsPreUpdate(delta); _runActions(delta); _callUpdate(_rootNode, delta); + _callStepPhysics(delta); _callConstraintsConstrain(delta); // Schedule next update @@ -370,20 +375,26 @@ class SpriteBox extends RenderBox { void _runActions(double dt) { if (_actionControllers == null) { - _actionControllers = []; - _addActionControllers(_rootNode, _actionControllers); + _rebuildActionControllersAndPhysicsNodes(); } for (ActionController actions in _actionControllers) { actions.step(dt); } } - void _addActionControllers(Node node, List controllers) { - if (node._actions != null) controllers.add(node._actions); + void _rebuildActionControllersAndPhysicsNodes() { + _actionControllers = []; + _physicsNodes = []; + _addActionControllersAndPhysicsNodes(_rootNode); + } + + void _addActionControllersAndPhysicsNodes(Node node) { + if (node._actions != null) _actionControllers.add(node._actions); + if (node is PhysicsNode) _physicsNodes.add(node); for (int i = node.children.length - 1; i >= 0; i--) { Node child = node.children[i]; - _addActionControllers(child, controllers); + _addActionControllersAndPhysicsNodes(child); } } @@ -397,6 +408,15 @@ class SpriteBox extends RenderBox { } } + void _callStepPhysics(double dt) { + if (_physicsNodes == null) + _rebuildActionControllersAndPhysicsNodes(); + + for (PhysicsNode physicsNode in _physicsNodes) { + physicsNode._stepPhysics(dt); + } + } + void _callConstraintsPreUpdate(double dt) { if (_constrainedNodes == null) { _constrainedNodes = []; diff --git a/packages/flutter_sprites/pubspec.yaml b/packages/flutter_sprites/pubspec.yaml index a557cc8dd8..418a5eb9d1 100644 --- a/packages/flutter_sprites/pubspec.yaml +++ b/packages/flutter_sprites/pubspec.yaml @@ -6,6 +6,9 @@ homepage: http://flutter.io dependencies: sky: ">=0.0.36 < 0.1.0" sky_tools: ">=0.0.10 < 0.1.0" + box2d: any dependency_overrides: sky: path: ../sky/packages/sky + box2d: + path: ../../../box2d.dart From 75f10ba6195afef4574a45bafc1a5ff3bd0db126 Mon Sep 17 00:00:00 2001 From: Viktor Lidholt Date: Fri, 2 Oct 2015 15:10:02 -0700 Subject: [PATCH 03/27] Updates work with latest Flutter changes --- examples/game/pubspec.yaml | 2 -- examples/game/test_physics.dart | 31 ++++++++++----------------- packages/flutter_sprites/pubspec.yaml | 2 -- 3 files changed, 11 insertions(+), 24 deletions(-) diff --git a/examples/game/pubspec.yaml b/examples/game/pubspec.yaml index 07125ee96b..8f12e91948 100644 --- a/examples/game/pubspec.yaml +++ b/examples/game/pubspec.yaml @@ -11,5 +11,3 @@ dependency_overrides: path: ../../sky/packages/sky skysprites: path: ../../skysprites - box2d: - path: ../../../../box2d.dart diff --git a/examples/game/test_physics.dart b/examples/game/test_physics.dart index 1ba81aa33b..a7c23f9b11 100644 --- a/examples/game/test_physics.dart +++ b/examples/game/test_physics.dart @@ -16,7 +16,6 @@ final AssetBundle _bundle = _initBundle(); ImageMap _images; SpriteSheet _spriteSheet; -TestBedApp _app; main() async { _images = new ImageMap(_bundle); @@ -28,29 +27,21 @@ main() async { String json = await _bundle.loadString('assets/sprites.json'); _spriteSheet = new SpriteSheet(_images['assets/sprites.png'], json); - _app = new TestBedApp(); - runApp(_app); -} - -class TestBedApp extends App { - - Widget build() { - ThemeData theme = new ThemeData( + runApp(new App( + title: 'Test Physics', + theme: new ThemeData( brightness: ThemeBrightness.light, primarySwatch: Colors.purple - ); - - return new Theme( - data: theme, - child: new Title( - title: 'Test Physics', - child: new SpriteWidget( + ), + routes: { + '/': (navigator, route) { + return new SpriteWidget( new TestBed(), SpriteBoxTransformMode.letterbox - ) - ) - ); - } + ); + } + } + )); } class TestBed extends NodeWithSize { diff --git a/packages/flutter_sprites/pubspec.yaml b/packages/flutter_sprites/pubspec.yaml index 418a5eb9d1..b2fbf54410 100644 --- a/packages/flutter_sprites/pubspec.yaml +++ b/packages/flutter_sprites/pubspec.yaml @@ -10,5 +10,3 @@ dependencies: dependency_overrides: sky: path: ../sky/packages/sky - box2d: - path: ../../../box2d.dart From cf0fe5faf986efabe8a791e9c771eeb0dc376226 Mon Sep 17 00:00:00 2001 From: Hixie Date: Fri, 2 Oct 2015 15:27:11 -0700 Subject: [PATCH 04/27] Assert that App(routes) is not null. This is probably a sign that you're using fn2 still. --- packages/flutter/lib/src/widgets/app.dart | 10 +++++++++- 1 file changed, 9 insertions(+), 1 deletion(-) diff --git a/packages/flutter/lib/src/widgets/app.dart b/packages/flutter/lib/src/widgets/app.dart index 79f9bbab10..64eb172acf 100644 --- a/packages/flutter/lib/src/widgets/app.dart +++ b/packages/flutter/lib/src/widgets/app.dart @@ -32,7 +32,15 @@ class App extends StatefulComponent { this.theme, this.routes, this.onGenerateRoute - }): super(key: key); + }): super(key: key) { + assert(() { + 'The "routes" argument to App() is required.'; + 'This might be a sign that you have not upgraded to our new Widgets framework.'; + 'For more details see: https://groups.google.com/forum/#!topic/flutter-dev/hcX3OvLws9c'; + '...or look at our examples: https://github.com/flutter/engine/tree/master/examples'; + return routes != null; + }); + } final String title; final ThemeData theme; From 56c8e60b04e3207b556a4d3b43fbb758bd90cda9 Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Fri, 2 Oct 2015 15:36:41 -0700 Subject: [PATCH 05/27] Detecting just Pan events causes an exception in GestureDetector Fixes #1320 --- .../flutter/lib/src/gestures/recognizer.dart | 4 ++- .../lib/src/widgets/gesture_detector.dart | 2 +- .../widget/fractionally_sized_box_test.dart | 4 ++- .../test/widget/gesture_detector_test.dart | 34 +++++++++++++++++++ packages/unit/test/widget/widget_tester.dart | 5 ++- 5 files changed, 45 insertions(+), 4 deletions(-) diff --git a/packages/flutter/lib/src/gestures/recognizer.dart b/packages/flutter/lib/src/gestures/recognizer.dart index 4705e4c611..bdc081a16a 100644 --- a/packages/flutter/lib/src/gestures/recognizer.dart +++ b/packages/flutter/lib/src/gestures/recognizer.dart @@ -12,7 +12,9 @@ import 'package:sky/src/gestures/pointer_router.dart'; export 'package:sky/src/gestures/pointer_router.dart' show PointerRouter; abstract class GestureRecognizer extends GestureArenaMember { - GestureRecognizer({ PointerRouter router }) : _router = router; + GestureRecognizer({ PointerRouter router }) : _router = router { + assert(_router != null); + } PointerRouter _router; diff --git a/packages/flutter/lib/src/widgets/gesture_detector.dart b/packages/flutter/lib/src/widgets/gesture_detector.dart index 88cacddbbf..cc1d35376a 100644 --- a/packages/flutter/lib/src/widgets/gesture_detector.dart +++ b/packages/flutter/lib/src/widgets/gesture_detector.dart @@ -190,7 +190,7 @@ class GestureDetectorState extends State { void _syncScale() { if (config.onScaleStart == null && config.onScaleUpdate == null && config.onScaleEnd == null) { - _scale = _ensureDisposed(_pan); + _scale = _ensureDisposed(_scale); } else { _ensureScale() ..onStart = config.onScaleStart diff --git a/packages/unit/test/widget/fractionally_sized_box_test.dart b/packages/unit/test/widget/fractionally_sized_box_test.dart index 4343f5a950..6ab3d21d5d 100644 --- a/packages/unit/test/widget/fractionally_sized_box_test.dart +++ b/packages/unit/test/widget/fractionally_sized_box_test.dart @@ -1,3 +1,4 @@ +import 'package:sky/rendering.dart'; import 'package:sky/widgets.dart'; import 'package:test/test.dart'; @@ -29,7 +30,8 @@ void main() { ) )); expect(detectedSize, equals(const Size(50.0, 25.0))); - expect(inner.currentContext.findRenderObject().localToGlobal(Point.origin), equals(const Point(25.0, 37.5))); + RenderBox box = inner.currentContext.findRenderObject(); + expect(box.localToGlobal(Point.origin), equals(const Point(25.0, 37.5))); }); }); } diff --git a/packages/unit/test/widget/gesture_detector_test.dart b/packages/unit/test/widget/gesture_detector_test.dart index 26d45ecc08..206b8e866f 100644 --- a/packages/unit/test/widget/gesture_detector_test.dart +++ b/packages/unit/test/widget/gesture_detector_test.dart @@ -88,4 +88,38 @@ void main() { tester.pumpWidget(new Container()); }); }); + + test('Pan doesn\'t crash', () { + testWidgets((WidgetTester tester) { + bool didStartPan = false; + Offset panDelta; + bool didEndPan = false; + + tester.pumpWidget( + new GestureDetector( + onPanStart: () { + didStartPan = true; + }, + onPanUpdate: (Offset delta) { + panDelta = delta; + }, + onPanEnd: (_) { + didEndPan = true; + }, + child: new Container() + ) + ); + + expect(didStartPan, isFalse); + expect(panDelta, isNull); + expect(didEndPan, isFalse); + + tester.scrollAt(new Point(10.0, 10.0), new Offset(20.0, 30.0)); + + expect(didStartPan, isTrue); + expect(panDelta.dx, 20.0); + expect(panDelta.dy, 30.0); + expect(didEndPan, isTrue); + }); + }); } diff --git a/packages/unit/test/widget/widget_tester.dart b/packages/unit/test/widget/widget_tester.dart index 465a14094e..cf8f127ee1 100644 --- a/packages/unit/test/widget/widget_tester.dart +++ b/packages/unit/test/widget/widget_tester.dart @@ -145,7 +145,10 @@ class WidgetTester { } void scroll(Element element, Offset offset, { int pointer: 1 }) { - Point startLocation = getCenter(element); + scrollAt(getCenter(element), offset, pointer: pointer); + } + + void scrollAt(Point startLocation, Offset offset, { int pointer: 1 }) { Point endLocation = startLocation + offset; TestPointer p = new TestPointer(pointer); // Events for the entire press-drag-release gesture are dispatched From c88ca5dbdcd1dd1c863fb6e469d2669876b71f2b Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Fri, 2 Oct 2015 14:38:36 -0700 Subject: [PATCH 06/27] Add AnimatedContainer This widget is used in Material and Drawer. We don't currently support animating towards null, but we can add that in a future patch. --- packages/flutter/lib/src/rendering/box.dart | 54 +++++ .../lib/src/widgets/animated_container.dart | 226 ++++++++++++++++++ packages/flutter/lib/src/widgets/basic.dart | 9 +- packages/flutter/lib/src/widgets/drawer.dart | 7 +- .../flutter/lib/src/widgets/material.dart | 7 +- packages/flutter/lib/widgets.dart | 1 + .../test/widget/animated_container_test.dart | 48 ++++ 7 files changed, 343 insertions(+), 9 deletions(-) create mode 100644 packages/flutter/lib/src/widgets/animated_container.dart create mode 100644 packages/unit/test/widget/animated_container_test.dart diff --git a/packages/flutter/lib/src/rendering/box.dart b/packages/flutter/lib/src/rendering/box.dart index 91ba89c548..4baf5c12e8 100644 --- a/packages/flutter/lib/src/rendering/box.dart +++ b/packages/flutter/lib/src/rendering/box.dart @@ -178,6 +178,60 @@ class BoxConstraints extends Constraints { (minHeight <= size.height) && (size.height <= math.max(minHeight, maxHeight)); } + BoxConstraints operator*(double other) { + return new BoxConstraints( + minWidth: minWidth * other, + maxWidth: maxWidth * other, + minHeight: minHeight * other, + maxHeight: maxHeight * other + ); + } + + BoxConstraints operator/(double other) { + return new BoxConstraints( + minWidth: minWidth / other, + maxWidth: maxWidth / other, + minHeight: minHeight / other, + maxHeight: maxHeight / other + ); + } + + BoxConstraints operator~/(double other) { + return new BoxConstraints( + minWidth: (minWidth ~/ other).toDouble(), + maxWidth: (maxWidth ~/ other).toDouble(), + minHeight: (minHeight ~/ other).toDouble(), + maxHeight: (maxHeight ~/ other).toDouble() + ); + } + + BoxConstraints operator%(double other) { + return new BoxConstraints( + minWidth: minWidth % other, + maxWidth: maxWidth % other, + minHeight: minHeight % other, + maxHeight: maxHeight % other + ); + } + + /// Linearly interpolate between two BoxConstraints + /// + /// If either is null, this function interpolates from [BoxConstraints.zero]. + static BoxConstraints lerp(BoxConstraints a, BoxConstraints b, double t) { + if (a == null && b == null) + return null; + if (a == null) + return b * t; + if (b == null) + return a * (1.0 - t); + return new BoxConstraints( + minWidth: sky.lerpDouble(a.minWidth, b.minWidth, t), + maxWidth: sky.lerpDouble(a.maxWidth, b.maxWidth, t), + minHeight: sky.lerpDouble(a.minHeight, b.minHeight, t), + maxHeight: sky.lerpDouble(a.maxHeight, b.maxHeight, t) + ); + } + bool operator ==(other) { if (identical(this, other)) return true; diff --git a/packages/flutter/lib/src/widgets/animated_container.dart b/packages/flutter/lib/src/widgets/animated_container.dart new file mode 100644 index 0000000000..291023e9f2 --- /dev/null +++ b/packages/flutter/lib/src/widgets/animated_container.dart @@ -0,0 +1,226 @@ +// 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:sky/animation.dart'; +import 'package:sky/src/widgets/basic.dart'; +import 'package:sky/src/widgets/framework.dart'; + +import 'package:vector_math/vector_math_64.dart'; + +class AnimatedBoxConstraintsValue extends AnimatedValue { + AnimatedBoxConstraintsValue(BoxConstraints begin, { BoxConstraints end, Curve curve: linear }) + : super(begin, end: end, curve: curve); + + BoxConstraints lerp(double t) => BoxConstraints.lerp(begin, end, t); +} + +class AnimatedBoxDecorationValue extends AnimatedValue { + AnimatedBoxDecorationValue(BoxDecoration begin, { BoxDecoration end, Curve curve: linear }) + : super(begin, end: end, curve: curve); + + BoxDecoration lerp(double t) => BoxDecoration.lerp(begin, end, t); +} + +class AnimatedEdgeDimsValue extends AnimatedValue { + AnimatedEdgeDimsValue(EdgeDims begin, { EdgeDims end, Curve curve: linear }) + : super(begin, end: end, curve: curve); + + EdgeDims lerp(double t) => EdgeDims.lerp(begin, end, t); +} + +class AnimatedMatrix4Value extends AnimatedValue { + AnimatedMatrix4Value(Matrix4 begin, { Matrix4 end, Curve curve: linear }) + : super(begin, end: end, curve: curve); + + Matrix4 lerp(double t) { + // TODO(mpcomplete): Animate the full matrix. Will animating the cells + // separately work? + Vector3 beginT = begin.getTranslation(); + Vector3 endT = end.getTranslation(); + Vector3 lerpT = beginT*(1.0-t) + endT*t; + return new Matrix4.identity()..translate(lerpT); + } +} + +class AnimatedContainer extends StatefulComponent { + AnimatedContainer({ + Key key, + this.child, + this.constraints, + this.decoration, + this.foregroundDecoration, + this.margin, + this.padding, + this.transform, + this.width, + this.height, + this.curve: linear, + this.duration + }) : super(key: key) { + assert(margin == null || margin.isNonNegative); + assert(padding == null || padding.isNonNegative); + assert(curve != null); + assert(duration != null); + } + + final Widget child; + final BoxConstraints constraints; + final BoxDecoration decoration; + final BoxDecoration foregroundDecoration; + final EdgeDims margin; + final EdgeDims padding; + final Matrix4 transform; + final double width; + final double height; + + final Curve curve; + final Duration duration; + + AnimatedContainerState createState() => new AnimatedContainerState(); +} + +class AnimatedContainerState extends State { + AnimatedBoxConstraintsValue _constraints; + AnimatedBoxDecorationValue _decoration; + AnimatedBoxDecorationValue _foregroundDecoration; + AnimatedEdgeDimsValue _margin; + AnimatedEdgeDimsValue _padding; + AnimatedMatrix4Value _transform; + AnimatedValue _width; + AnimatedValue _height; + + AnimationPerformance _performance; + + void initState() { + super.initState(); + _performance = new AnimationPerformance(duration: config.duration) + ..timing = new AnimationTiming(curve: config.curve) + ..addListener(_updateAllVariables); + _configAllVariables(); + } + + void didUpdateConfig(AnimatedContainer oldConfig) { + _performance + ..duration = config.duration + ..timing.curve = config.curve; + if (_configAllVariables()) { + _performance.progress = 0.0; + _performance.play(); + } + } + + void dispose() { + _performance.stop(); + super.dispose(); + } + + void _updateVariable(AnimatedVariable variable) { + if (variable != null) + _performance.updateVariable(variable); + } + + void _updateAllVariables() { + setState(() { + _updateVariable(_constraints); + _updateVariable(_constraints); + _updateVariable(_decoration); + _updateVariable(_foregroundDecoration); + _updateVariable(_margin); + _updateVariable(_padding); + _updateVariable(_transform); + _updateVariable(_width); + _updateVariable(_height); + }); + } + + bool _configVariable(AnimatedValue variable, dynamic targetValue) { + dynamic currentValue = variable.value; + variable.end = targetValue; + variable.begin = currentValue; + return currentValue != targetValue; + } + + bool _configAllVariables() { + bool needsAnimation = false; + if (config.constraints != null) { + _constraints ??= new AnimatedBoxConstraintsValue(config.constraints); + if (_configVariable(_constraints, config.constraints)) + needsAnimation = true; + } else { + _constraints = null; + } + + if (config.decoration != null) { + _decoration ??= new AnimatedBoxDecorationValue(config.decoration); + if (_configVariable(_decoration, config.decoration)) + needsAnimation = true; + } else { + _decoration = null; + } + + if (config.foregroundDecoration != null) { + _foregroundDecoration ??= new AnimatedBoxDecorationValue(config.foregroundDecoration); + if (_configVariable(_foregroundDecoration, config.foregroundDecoration)) + needsAnimation = true; + } else { + _foregroundDecoration = null; + } + + if (config.margin != null) { + _margin ??= new AnimatedEdgeDimsValue(config.margin); + if (_configVariable(_margin, config.margin)) + needsAnimation = true; + } else { + _margin = null; + } + + if (config.padding != null) { + _padding ??= new AnimatedEdgeDimsValue(config.padding); + if (_configVariable(_padding, config.padding)) + needsAnimation = true; + } else { + _padding = null; + } + + if (config.transform != null) { + _transform ??= new AnimatedMatrix4Value(config.transform); + if (_configVariable(_transform, config.transform)) + needsAnimation = true; + } else { + _transform = null; + } + + if (config.width != null) { + _width ??= new AnimatedValue(config.width); + if (_configVariable(_width, config.width)) + needsAnimation = true; + } else { + _width = null; + } + + if (config.height != null) { + _height ??= new AnimatedValue(config.height); + if (_configVariable(_height, config.height)) + needsAnimation = true; + } else { + _height = null; + } + + return needsAnimation; + } + + Widget build(BuildContext context) { + return new Container( + child: config.child, + constraints: _constraints?.value, + decoration: _decoration?.value, + foregroundDecoration: _foregroundDecoration?.value, + margin: _margin?.value, + padding: _padding?.value, + transform: _transform?.value, + width: _width?.value, + height: _height?.value + ); + } +} diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index 66bc316745..e5f087760a 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -22,6 +22,7 @@ export 'package:sky/rendering.dart' show FlexAlignItems, FlexDirection, FlexJustifyContent, + Matrix4, Offset, Paint, Path, @@ -392,11 +393,11 @@ class Container extends StatelessComponent { this.constraints, this.decoration, this.foregroundDecoration, - this.width, - this.height, this.margin, this.padding, - this.transform + this.transform, + this.width, + this.height }) : super(key: key) { assert(margin == null || margin.isNonNegative); assert(padding == null || padding.isNonNegative); @@ -940,4 +941,4 @@ class KeyedSubtree extends StatelessComponent { final Widget child; Widget build(BuildContext context) => child; -} \ No newline at end of file +} diff --git a/packages/flutter/lib/src/widgets/drawer.dart b/packages/flutter/lib/src/widgets/drawer.dart index 04435cccca..edee253af6 100644 --- a/packages/flutter/lib/src/widgets/drawer.dart +++ b/packages/flutter/lib/src/widgets/drawer.dart @@ -6,6 +6,7 @@ import 'dart:async'; import 'package:sky/animation.dart'; import 'package:sky/material.dart'; +import 'package:sky/src/widgets/animated_container.dart'; import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/gesture_detector.dart'; @@ -102,9 +103,9 @@ class DrawerState extends State { Widget content = new SlideTransition( performance: _performance.view, position: new AnimatedValue(_kClosedPosition, end: _kOpenPosition), - // TODO(abarth): Use AnimatedContainer - child: new Container( - // behavior: implicitlyAnimate(const Duration(milliseconds: 200)), + child: new AnimatedContainer( + curve: ease, + duration: const Duration(milliseconds: 200), decoration: new BoxDecoration( backgroundColor: Theme.of(context).canvasColor, boxShadow: shadows[config.level]), diff --git a/packages/flutter/lib/src/widgets/material.dart b/packages/flutter/lib/src/widgets/material.dart index 02ea4ce80e..eb79290ec9 100644 --- a/packages/flutter/lib/src/widgets/material.dart +++ b/packages/flutter/lib/src/widgets/material.dart @@ -2,8 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:sky/animation.dart'; import 'package:sky/painting.dart'; import 'package:sky/material.dart'; +import 'package:sky/src/widgets/animated_container.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/theme.dart'; @@ -61,10 +63,11 @@ class Material extends StatelessComponent { ); } } - // TODO(abarth): This should use AnimatedContainer. return new DefaultTextStyle( style: Theme.of(context).text.body1, - child: new Container( + child: new AnimatedContainer( + curve: ease, + duration: const Duration(milliseconds: 200), decoration: new BoxDecoration( backgroundColor: getBackgroundColor(context), borderRadius: edges[type], diff --git a/packages/flutter/lib/widgets.dart b/packages/flutter/lib/widgets.dart index 3e7f257d57..e3fa205fc9 100644 --- a/packages/flutter/lib/widgets.dart +++ b/packages/flutter/lib/widgets.dart @@ -6,6 +6,7 @@ library widgets; export 'src/widgets/animated_component.dart'; +export 'src/widgets/animated_container.dart'; export 'src/widgets/app.dart'; export 'src/widgets/basic.dart'; export 'src/widgets/binding.dart'; diff --git a/packages/unit/test/widget/animated_container_test.dart b/packages/unit/test/widget/animated_container_test.dart new file mode 100644 index 0000000000..b98cee45e9 --- /dev/null +++ b/packages/unit/test/widget/animated_container_test.dart @@ -0,0 +1,48 @@ +import 'package:sky/rendering.dart'; +import 'package:sky/widgets.dart'; +import 'package:test/test.dart'; + +import 'widget_tester.dart'; + +void main() { + test('AnimatedContainer control test', () { + testWidgets((WidgetTester tester) { + GlobalKey key = new GlobalKey(); + + BoxDecoration decorationA = new BoxDecoration( + backgroundColor: new Color(0xFF00FF00) + ); + + BoxDecoration decorationB = new BoxDecoration( + backgroundColor: new Color(0xFF0000FF) + ); + + tester.pumpWidget( + new AnimatedContainer( + key: key, + duration: const Duration(milliseconds: 200), + decoration: decorationA + ) + ); + + RenderDecoratedBox box = key.currentState.context.findRenderObject(); + expect(box.decoration.backgroundColor, equals(decorationA.backgroundColor)); + + tester.pumpWidget( + new AnimatedContainer( + key: key, + duration: const Duration(milliseconds: 200), + decoration: decorationB + ) + ); + + expect(key.currentState.context.findRenderObject(), equals(box)); + expect(box.decoration.backgroundColor, equals(decorationA.backgroundColor)); + + tester.pump(const Duration(seconds: 1)); + + expect(box.decoration.backgroundColor, equals(decorationB.backgroundColor)); + + }); + }); +} From 4de0a99b3ff9f724095ff108b8ee5bbf3099e978 Mon Sep 17 00:00:00 2001 From: Hans Muller Date: Fri, 2 Oct 2015 15:57:37 -0700 Subject: [PATCH 07/27] ShaderMask The ShaderMask widget enables rendering its child with an alpha channel defined by a Shader. For example if the Shader was a linear gradient in alpha then the component behind the ShaderMask's child would appear wherever the gradient's alpha value was not fully opaque. The card_collection.dart example demonstrates this. Select the "Let the sun shine" checkbox in the app's drawer. --- examples/widgets/card_collection.dart | 29 +++++++++++++++++ .../flutter/lib/src/rendering/object.dart | 30 ++++++++++++++++++ .../flutter/lib/src/rendering/proxy_box.dart | 31 +++++++++++++++++++ packages/flutter/lib/src/widgets/basic.dart | 27 ++++++++++++++++ .../unit/test/widget/shader_mask_test.dart | 27 ++++++++++++++++ 5 files changed, 144 insertions(+) create mode 100644 packages/unit/test/widget/shader_mask_test.dart diff --git a/examples/widgets/card_collection.dart b/examples/widgets/card_collection.dart index 8a69ab8321..ba5a53c975 100644 --- a/examples/widgets/card_collection.dart +++ b/examples/widgets/card_collection.dart @@ -2,6 +2,8 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:sky' as sky; + import 'package:sky/animation.dart'; import 'package:sky/material.dart'; import 'package:sky/painting.dart'; @@ -25,6 +27,9 @@ class CardCollectionAppState extends State { static const TextStyle cardLabelStyle = const TextStyle(color: Colors.white, fontSize: 18.0, fontWeight: bold); + // TODO(hansmuller): need a local image asset + static const _sunshineURL = "http://www.walltor.com/images/wallpaper/good-morning-sunshine-58540.jpg"; + final TextStyle backgroundTextStyle = Typography.white.title.copyWith(textAlign: TextAlign.center); @@ -33,6 +38,7 @@ class CardCollectionAppState extends State { bool _snapToCenter = false; bool _fixedSizeCards = false; bool _drawerShowing = false; + bool _sunshine = false; AnimationStatus _drawerStatus = AnimationStatus.dismissed; InvalidatorCallback _invalidator; Size _cardCollectionSize = new Size(200.0, 200.0); @@ -139,6 +145,12 @@ class CardCollectionAppState extends State { }); } + void _toggleSunshine() { + setState(() { + _sunshine = !_sunshine; + }); + } + _changeDismissDirection(DismissDirection newDismissDirection) { setState(() { _dismissDirection = newDismissDirection; @@ -185,6 +197,7 @@ class CardCollectionAppState extends State { new DrawerHeader(child: new Text('Options')), buildDrawerCheckbox("Snap fling scrolls to center", _snapToCenter, _toggleSnapToCenter), buildDrawerCheckbox("Fixed size cards", _fixedSizeCards, _toggleFixedSizeCards), + buildDrawerCheckbox("Let the sun shine", _sunshine, _toggleSunshine), new DrawerDivider(), buildDrawerRadioItem(DismissDirection.horizontal, 'action/code'), buildDrawerRadioItem(DismissDirection.left, 'navigation/arrow_back'), @@ -285,6 +298,16 @@ class CardCollectionAppState extends State { }); } + sky.Shader _createShader(Rect bounds) { + return new LinearGradient( + begin: Point.origin, + end: new Point(0.0, bounds.height), + colors: [const Color(0x00FFFFFF), const Color(0xFFFFFFFF)], + stops: [0.1, 0.35] + ) + .createShader(); + } + Widget build(BuildContext context) { Widget cardCollection; @@ -306,6 +329,12 @@ class CardCollectionAppState extends State { ); } + if (_sunshine) + cardCollection = new Stack([ + new Column([new NetworkImage(src: _sunshineURL)]), + new ShaderMask(child: cardCollection, shaderCallback: _createShader) + ]); + Widget body = new SizeObserver( callback: _updateCardCollectionSize, child: new Container( diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index 07cee15b5f..1bd1419f73 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -16,6 +16,8 @@ import 'package:vector_math/vector_math_64.dart'; export 'dart:sky' show Point, Offset, Size, Rect, Color, Paint, Path; export 'package:sky/src/rendering/hit_test.dart' show HitTestTarget, HitTestEntry, HitTestResult; +typedef sky.Shader ShaderCallback(Rect bounds); + /// Base class for data associated with a [RenderObject] by its parent /// /// Some render objects wish to store data on their children, such as their @@ -300,6 +302,34 @@ class PaintingContext { } } + static Paint _getPaintForShaderMask(Rect bounds, + ShaderCallback shaderCallback, + sky.TransferMode transferMode) { + return new Paint() + ..transferMode = transferMode + ..shader = shaderCallback(bounds); + } + + void paintChildWithShaderMask(RenderObject child, + Point childPosition, + Rect bounds, + ShaderCallback shaderCallback, + sky.TransferMode transferMode) { + assert(debugCanPaintChild(child)); + final Offset childOffset = childPosition.toOffset(); + if (!child.needsCompositing) { + canvas.saveLayer(bounds, new Paint()); + canvas.translate(childOffset.dx, childOffset.dy); + insertChild(child, Offset.zero); + Paint shaderPaint = _getPaintForShaderMask(bounds, shaderCallback, transferMode); + canvas.drawRect(Offset.zero & new Size(bounds.width, bounds.height), shaderPaint); + canvas.restore(); + } else { + // TODO(hansmuller) support compositing ShaderMasks + assert('Support for compositing ShaderMasks is TBD' is String); + } + } + /// Instructs the child to draw itself onto this context at the given offset /// /// Do not call directly. This function is visible so that it can be diff --git a/packages/flutter/lib/src/rendering/proxy_box.dart b/packages/flutter/lib/src/rendering/proxy_box.dart index 7bd0b9a425..1f9a7e3353 100644 --- a/packages/flutter/lib/src/rendering/proxy_box.dart +++ b/packages/flutter/lib/src/rendering/proxy_box.dart @@ -659,6 +659,37 @@ class RenderColorFilter extends RenderProxyBox { } } +class RenderShaderMask extends RenderProxyBox { + RenderShaderMask({ RenderBox child, ShaderCallback shaderCallback, sky.TransferMode transferMode }) + : _shaderCallback = shaderCallback, _transferMode = transferMode, super(child) { + } + + ShaderCallback get shaderCallback => _shaderCallback; + ShaderCallback _shaderCallback; + void set shaderCallback (ShaderCallback newShaderCallback) { + assert(newShaderCallback != null); + if (_shaderCallback == newShaderCallback) + return; + _shaderCallback = newShaderCallback; + markNeedsPaint(); + } + + sky.TransferMode get transferMode => _transferMode; + sky.TransferMode _transferMode; + void set transferMode (sky.TransferMode newTransferMode) { + assert(newTransferMode != null); + if (_transferMode == newTransferMode) + return; + _transferMode = newTransferMode; + markNeedsPaint(); + } + + void paint(PaintingContext context, Offset offset) { + if (child != null) + context.paintChildWithShaderMask(child, offset.toPoint(), offset & size, _shaderCallback, _transferMode); + } +} + /// Clips its child using a rectangle /// /// Prevents its child from painting outside its bounds. diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index e5f087760a..6a6127f7f6 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -70,6 +70,33 @@ class ColorFilter extends OneChildRenderObjectWidget { } } +class ShaderMask extends OneChildRenderObjectWidget { + ShaderMask({ + Key key, + this.shaderCallback, + this.transferMode: sky.TransferMode.modulate, + Widget child + }) : super(key: key, child: child) { + assert(shaderCallback != null); + assert(transferMode != null); + } + + final ShaderCallback shaderCallback; + final sky.TransferMode transferMode; + + RenderShaderMask createRenderObject() { + return new RenderShaderMask( + shaderCallback: shaderCallback, + transferMode: transferMode + ); + } + + void updateRenderObject(RenderShaderMask renderObject, ShaderMask oldWidget) { + renderObject.shaderCallback = shaderCallback; + renderObject.transferMode = transferMode; + } +} + class DecoratedBox extends OneChildRenderObjectWidget { DecoratedBox({ Key key, diff --git a/packages/unit/test/widget/shader_mask_test.dart b/packages/unit/test/widget/shader_mask_test.dart new file mode 100644 index 0000000000..c92122c663 --- /dev/null +++ b/packages/unit/test/widget/shader_mask_test.dart @@ -0,0 +1,27 @@ +import 'dart:sky' as sky; + +import 'package:sky/painting.dart'; +import 'package:sky/widgets.dart'; +import 'package:test/test.dart'; + +import 'widget_tester.dart'; + +sky.Shader createShader(Rect bounds) { + return new LinearGradient( + begin: Point.origin, + end: new Point(0.0, bounds.height), + colors: [const Color(0x00FFFFFF), const Color(0xFFFFFFFF)], + stops: [0.1, 0.35] + ) + .createShader(); +} + + +void main() { + test('Can be constructed', () { + testWidgets((WidgetTester tester) { + Widget child = new Container(width: 100.0, height: 100.0); + tester.pumpWidget(new ShaderMask(child: child, shaderCallback: createShader)); + }); + }); +} From e9aabcd5c69a4aa1eb2dc3a523bea4a2f666be1e Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Fri, 2 Oct 2015 23:10:15 -0700 Subject: [PATCH 08/27] Use `}) : super` consistently Fixes #1372 --- examples/address_book/lib/main.dart | 2 +- examples/widgets/drag_and_drop.dart | 4 ++-- packages/flutter/lib/src/widgets/app.dart | 2 +- packages/flutter/lib/src/widgets/basic.dart | 2 +- packages/flutter/lib/src/widgets/checkbox.dart | 2 +- packages/flutter/lib/src/widgets/dialog.dart | 2 +- packages/flutter/lib/src/widgets/drag_target.dart | 2 +- packages/flutter/lib/src/widgets/input.dart | 2 +- packages/flutter/lib/src/widgets/mixed_viewport.dart | 2 +- 9 files changed, 10 insertions(+), 10 deletions(-) diff --git a/examples/address_book/lib/main.dart b/examples/address_book/lib/main.dart index 636df80513..c12a7d2114 100644 --- a/examples/address_book/lib/main.dart +++ b/examples/address_book/lib/main.dart @@ -11,7 +11,7 @@ class Field extends StatelessComponent { this.inputKey, this.icon, this.placeholder - }): super(key: key); + }) : super(key: key); final GlobalKey inputKey; final String icon; diff --git a/examples/widgets/drag_and_drop.dart b/examples/widgets/drag_and_drop.dart index 70b68ef38d..c7a618763d 100644 --- a/examples/widgets/drag_and_drop.dart +++ b/examples/widgets/drag_and_drop.dart @@ -50,7 +50,7 @@ class ExampleDragTargetState extends State { } class Dot extends StatelessComponent { - Dot({ Key key, this.color, this.size }): super(key: key); + Dot({ Key key, this.color, this.size }) : super(key: key); final Color color; final double size; Widget build(BuildContext context) { @@ -66,7 +66,7 @@ class Dot extends StatelessComponent { } class ExampleDragSource extends StatelessComponent { - ExampleDragSource({ Key key, this.navigator, this.name, this.color }): super(key: key); + ExampleDragSource({ Key key, this.navigator, this.name, this.color }) : super(key: key); final NavigatorState navigator; final String name; final Color color; diff --git a/packages/flutter/lib/src/widgets/app.dart b/packages/flutter/lib/src/widgets/app.dart index 64eb172acf..ea317a4f0b 100644 --- a/packages/flutter/lib/src/widgets/app.dart +++ b/packages/flutter/lib/src/widgets/app.dart @@ -32,7 +32,7 @@ class App extends StatefulComponent { this.theme, this.routes, this.onGenerateRoute - }): super(key: key) { + }) : super(key: key) { assert(() { 'The "routes" argument to App() is required.'; 'This might be a sign that you have not upgraded to our new Widgets framework.'; diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index 6a6127f7f6..d5dbb44004 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -910,7 +910,7 @@ class Listener extends OneChildRenderObjectWidget { this.onPointerMove, this.onPointerUp, this.onPointerCancel - }): super(key: key, child: child); + }) : super(key: key, child: child); final PointerEventListener onPointerDown; final PointerEventListener onPointerMove; diff --git a/packages/flutter/lib/src/widgets/checkbox.dart b/packages/flutter/lib/src/widgets/checkbox.dart index 2f69665986..89cdaee293 100644 --- a/packages/flutter/lib/src/widgets/checkbox.dart +++ b/packages/flutter/lib/src/widgets/checkbox.dart @@ -61,7 +61,7 @@ class _CheckboxWrapper extends LeafRenderObjectWidget { this.onChanged, this.uncheckedColor, this.accentColor - }): super(key: key) { + }) : super(key: key) { assert(uncheckedColor != null); assert(accentColor != null); } diff --git a/packages/flutter/lib/src/widgets/dialog.dart b/packages/flutter/lib/src/widgets/dialog.dart index 9098bebc1d..9b6ca201bf 100644 --- a/packages/flutter/lib/src/widgets/dialog.dart +++ b/packages/flutter/lib/src/widgets/dialog.dart @@ -30,7 +30,7 @@ class Dialog extends StatelessComponent { this.contentPadding, this.actions, this.onDismiss - }): super(key: key); + }) : super(key: key); /// The (optional) title of the dialog is displayed in a large font at the top /// of the dialog. diff --git a/packages/flutter/lib/src/widgets/drag_target.dart b/packages/flutter/lib/src/widgets/drag_target.dart index 19f372cf6d..accac1bb13 100644 --- a/packages/flutter/lib/src/widgets/drag_target.dart +++ b/packages/flutter/lib/src/widgets/drag_target.dart @@ -45,7 +45,7 @@ class Draggable extends StatefulComponent { this.feedback, this.feedbackOffset: Offset.zero, this.dragAnchor: DragAnchor.child - }): super(key: key) { + }) : super(key: key) { assert(navigator != null); assert(child != null); assert(feedback != null); diff --git a/packages/flutter/lib/src/widgets/input.dart b/packages/flutter/lib/src/widgets/input.dart index b9bcfbaf92..d892944b1d 100644 --- a/packages/flutter/lib/src/widgets/input.dart +++ b/packages/flutter/lib/src/widgets/input.dart @@ -28,7 +28,7 @@ class Input extends Scrollable { this.placeholder, this.onChanged, this.keyboardType: KeyboardType.TEXT - }): super( + }) : super( key: key, initialScrollOffset: 0.0, scrollDirection: ScrollDirection.horizontal diff --git a/packages/flutter/lib/src/widgets/mixed_viewport.dart b/packages/flutter/lib/src/widgets/mixed_viewport.dart index 505aa7a988..5336e80c71 100644 --- a/packages/flutter/lib/src/widgets/mixed_viewport.dart +++ b/packages/flutter/lib/src/widgets/mixed_viewport.dart @@ -22,7 +22,7 @@ class MixedViewport extends RenderObjectWidget { this.token, this.onExtentsUpdate, this.onInvalidatorAvailable - }): super(key: key); + }) : super(key: key); final double startOffset; final ScrollDirection direction; From 426ce9374e0fe89aaf63cc74eef7acde6505b4c9 Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Fri, 2 Oct 2015 23:50:23 -0700 Subject: [PATCH 09/27] Clean up some style in GestureDetector * Rename GestureTapListener (and friends) To GestureTapCallback to match the other gesture callbacks. * Replace "ensureFoo" pattern with ??= operator. --- examples/stocks/lib/stock_row.dart | 4 +- .../flutter/lib/src/gestures/long_press.dart | 4 +- .../flutter/lib/src/gestures/show_press.dart | 4 +- packages/flutter/lib/src/gestures/tap.dart | 4 +- .../flutter/lib/src/widgets/drawer_item.dart | 2 +- .../src/widgets/floating_action_button.dart | 2 +- .../lib/src/widgets/gesture_detector.dart | 96 +++++++------------ .../flutter/lib/src/widgets/ink_well.dart | 1 + 8 files changed, 44 insertions(+), 73 deletions(-) diff --git a/examples/stocks/lib/stock_row.dart b/examples/stocks/lib/stock_row.dart index c985f82e12..bc522bd9d3 100644 --- a/examples/stocks/lib/stock_row.dart +++ b/examples/stocks/lib/stock_row.dart @@ -37,13 +37,13 @@ class StockRow extends StatelessComponent { static const double kHeight = 79.0; - GestureTapListener _getTapHandler(StockRowActionCallback callback) { + GestureTapCallback _getTapHandler(StockRowActionCallback callback) { if (callback == null) return null; return () => callback(stock, key, arrowKey, symbolKey, priceKey); } - GestureLongPressListener _getLongPressHandler(StockRowActionCallback callback) { + GestureLongPressCallback _getLongPressHandler(StockRowActionCallback callback) { if (callback == null) return null; return () => callback(stock, key, arrowKey, symbolKey, priceKey); diff --git a/packages/flutter/lib/src/gestures/long_press.dart b/packages/flutter/lib/src/gestures/long_press.dart index f7e280b7a7..ec1945dea6 100644 --- a/packages/flutter/lib/src/gestures/long_press.dart +++ b/packages/flutter/lib/src/gestures/long_press.dart @@ -9,13 +9,13 @@ import 'package:sky/src/gestures/constants.dart'; import 'package:sky/src/gestures/pointer_router.dart'; import 'package:sky/src/gestures/recognizer.dart'; -typedef void GestureLongPressListener(); +typedef void GestureLongPressCallback(); class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer { LongPressGestureRecognizer({ PointerRouter router, this.onLongPress }) : super(router: router, deadline: kTapTimeout + kLongPressTimeout); - GestureLongPressListener onLongPress; + GestureLongPressCallback onLongPress; void didExceedDeadline() { resolve(GestureDisposition.accepted); diff --git a/packages/flutter/lib/src/gestures/show_press.dart b/packages/flutter/lib/src/gestures/show_press.dart index 1f56cba4d0..299b6ae540 100644 --- a/packages/flutter/lib/src/gestures/show_press.dart +++ b/packages/flutter/lib/src/gestures/show_press.dart @@ -8,13 +8,13 @@ import 'package:sky/src/gestures/arena.dart'; import 'package:sky/src/gestures/constants.dart'; import 'package:sky/src/gestures/recognizer.dart'; -typedef void GestureShowPressListener(); +typedef void GestureShowPressCallback(); class ShowPressGestureRecognizer extends PrimaryPointerGestureRecognizer { ShowPressGestureRecognizer({ PointerRouter router, this.onShowPress }) : super(router: router, deadline: kTapTimeout); - GestureShowPressListener onShowPress; + GestureShowPressCallback onShowPress; void didExceedDeadline() { // Show press isn't an exclusive gesture. We can recognize a show press diff --git a/packages/flutter/lib/src/gestures/tap.dart b/packages/flutter/lib/src/gestures/tap.dart index fe107b149d..8c5683fbdf 100644 --- a/packages/flutter/lib/src/gestures/tap.dart +++ b/packages/flutter/lib/src/gestures/tap.dart @@ -7,13 +7,13 @@ import 'dart:sky' as sky; import 'package:sky/src/gestures/arena.dart'; import 'package:sky/src/gestures/recognizer.dart'; -typedef void GestureTapListener(); +typedef void GestureTapCallback(); class TapGestureRecognizer extends PrimaryPointerGestureRecognizer { TapGestureRecognizer({ PointerRouter router, this.onTap }) : super(router: router); - GestureTapListener onTap; + GestureTapCallback onTap; void handlePrimaryPointer(sky.PointerEvent event) { if (event.type == 'pointerup') { diff --git a/packages/flutter/lib/src/widgets/drawer_item.dart b/packages/flutter/lib/src/widgets/drawer_item.dart index cd670b5aef..e7a609d06a 100644 --- a/packages/flutter/lib/src/widgets/drawer_item.dart +++ b/packages/flutter/lib/src/widgets/drawer_item.dart @@ -21,7 +21,7 @@ class DrawerItem extends StatefulComponent { final String icon; final Widget child; - final GestureTapListener onPressed; + final GestureTapCallback onPressed; final bool selected; DrawerItemState createState() => new DrawerItemState(); diff --git a/packages/flutter/lib/src/widgets/floating_action_button.dart b/packages/flutter/lib/src/widgets/floating_action_button.dart index b61993abe4..24f57bdc25 100644 --- a/packages/flutter/lib/src/widgets/floating_action_button.dart +++ b/packages/flutter/lib/src/widgets/floating_action_button.dart @@ -26,7 +26,7 @@ class FloatingActionButton extends StatefulComponent { final Widget child; final Color backgroundColor; - final GestureTapListener onPressed; + final GestureTapCallback onPressed; FloatingActionButtonState createState() => new FloatingActionButtonState(); } diff --git a/packages/flutter/lib/src/widgets/gesture_detector.dart b/packages/flutter/lib/src/widgets/gesture_detector.dart index cc1d35376a..4efbd6d260 100644 --- a/packages/flutter/lib/src/widgets/gesture_detector.dart +++ b/packages/flutter/lib/src/widgets/gesture_detector.dart @@ -31,9 +31,10 @@ class GestureDetector extends StatefulComponent { }) : super(key: key); final Widget child; - final GestureTapListener onTap; - final GestureShowPressListener onShowPress; - final GestureLongPressListener onLongPress; + + final GestureTapCallback onTap; + final GestureShowPressCallback onShowPress; + final GestureLongPressCallback onLongPress; final GestureDragStartCallback onVerticalDragStart; final GestureDragUpdateCallback onVerticalDragUpdate; @@ -55,62 +56,23 @@ class GestureDetector extends StatefulComponent { } class GestureDetectorState extends State { - void initState() { - super.initState(); - didUpdateConfig(null); - } - final PointerRouter _router = FlutterBinding.instance.pointerRouter; TapGestureRecognizer _tap; - TapGestureRecognizer _ensureTap() { - if (_tap == null) - _tap = new TapGestureRecognizer(router: _router); - return _tap; - } - ShowPressGestureRecognizer _showPress; - ShowPressGestureRecognizer _ensureShowPress() { - if (_showPress == null) - _showPress = new ShowPressGestureRecognizer(router: _router); - return _showPress; - } - LongPressGestureRecognizer _longPress; - LongPressGestureRecognizer _ensureLongPress() { - if (_longPress == null) - _longPress = new LongPressGestureRecognizer(router: _router); - return _longPress; - } - VerticalDragGestureRecognizer _verticalDrag; - VerticalDragGestureRecognizer _ensureVerticalDrag() { - if (_verticalDrag == null) - _verticalDrag = new VerticalDragGestureRecognizer(router: _router); - return _verticalDrag; - } - HorizontalDragGestureRecognizer _horizontalDrag; - HorizontalDragGestureRecognizer _ensureHorizontalDrag() { - if (_horizontalDrag == null) - _horizontalDrag = new HorizontalDragGestureRecognizer(router: _router); - return _horizontalDrag; - } - PanGestureRecognizer _pan; - PanGestureRecognizer _ensurePan() { - assert(_scale == null); // Scale is a superset of pan; just use scale - if (_pan == null) - _pan = new PanGestureRecognizer(router: _router); - return _pan; + ScaleGestureRecognizer _scale; + + void initState() { + super.initState(); + _syncAll(); } - ScaleGestureRecognizer _scale; - ScaleGestureRecognizer _ensureScale() { - assert(_pan == null); // Scale is a superset of pan; just use scale - if (_scale == null) - _scale = new ScaleGestureRecognizer(router: _router); - return _scale; + void didUpdateConfig(GestureDetector oldConfig) { + _syncAll(); } void dispose() { @@ -124,7 +86,7 @@ class GestureDetectorState extends State { super.dispose(); } - void didUpdateConfig(GestureDetector oldConfig) { + void _syncAll() { _syncTap(); _syncShowPress(); _syncLongPress(); @@ -135,31 +97,37 @@ class GestureDetectorState extends State { } void _syncTap() { - if (config.onTap == null) + if (config.onTap == null) { _tap = _ensureDisposed(_tap); - else - _ensureTap().onTap = config.onTap; + } else { + _tap ??= new TapGestureRecognizer(router: _router) + ..onTap = config.onTap; + } } void _syncShowPress() { - if (config.onShowPress == null) + if (config.onShowPress == null) { _showPress = _ensureDisposed(_showPress); - else - _ensureShowPress().onShowPress = config.onShowPress; + } else { + _showPress ??= new ShowPressGestureRecognizer(router: _router) + ..onShowPress = config.onShowPress; + } } void _syncLongPress() { - if (config.onLongPress == null) + if (config.onLongPress == null) { _longPress = _ensureDisposed(_longPress); - else - _ensureLongPress().onLongPress = config.onLongPress; + } else { + _longPress ??= new LongPressGestureRecognizer(router: _router) + ..onLongPress = config.onLongPress; + } } void _syncVerticalDrag() { if (config.onVerticalDragStart == null && config.onVerticalDragUpdate == null && config.onVerticalDragEnd == null) { _verticalDrag = _ensureDisposed(_verticalDrag); } else { - _ensureVerticalDrag() + _verticalDrag ??= new VerticalDragGestureRecognizer(router: _router) ..onStart = config.onVerticalDragStart ..onUpdate = config.onVerticalDragUpdate ..onEnd = config.onVerticalDragEnd; @@ -170,7 +138,7 @@ class GestureDetectorState extends State { if (config.onHorizontalDragStart == null && config.onHorizontalDragUpdate == null && config.onHorizontalDragEnd == null) { _horizontalDrag = _ensureDisposed(_horizontalDrag); } else { - _ensureHorizontalDrag() + _horizontalDrag ??= new HorizontalDragGestureRecognizer(router: _router) ..onStart = config.onHorizontalDragStart ..onUpdate = config.onHorizontalDragUpdate ..onEnd = config.onHorizontalDragEnd; @@ -181,7 +149,8 @@ class GestureDetectorState extends State { if (config.onPanStart == null && config.onPanUpdate == null && config.onPanEnd == null) { _pan = _ensureDisposed(_pan); } else { - _ensurePan() + assert(_scale == null); // Scale is a superset of pan; just use scale + _pan ??= new PanGestureRecognizer(router: _router) ..onStart = config.onPanStart ..onUpdate = config.onPanUpdate ..onEnd = config.onPanEnd; @@ -192,7 +161,8 @@ class GestureDetectorState extends State { if (config.onScaleStart == null && config.onScaleUpdate == null && config.onScaleEnd == null) { _scale = _ensureDisposed(_scale); } else { - _ensureScale() + assert(_pan == null); // Scale is a superset of pan; just use scale + _scale ??= new ScaleGestureRecognizer(router: _router) ..onStart = config.onScaleStart ..onUpdate = config.onScaleUpdate ..onEnd = config.onScaleEnd; diff --git a/packages/flutter/lib/src/widgets/ink_well.dart b/packages/flutter/lib/src/widgets/ink_well.dart index 7f2b2e0e13..4e44e0517b 100644 --- a/packages/flutter/lib/src/widgets/ink_well.dart +++ b/packages/flutter/lib/src/widgets/ink_well.dart @@ -127,5 +127,6 @@ class RenderInkWell extends RenderProxyBox { class InkWell extends OneChildRenderObjectWidget { InkWell({ Key key, Widget child }) : super(key: key, child: child); + RenderInkWell createRenderObject() => new RenderInkWell(); } From 445c512d2c89a8474b6b7f621cf99df33b923a3c Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Sat, 3 Oct 2015 01:37:01 -0700 Subject: [PATCH 10/27] Be a bit less clever with ??= Turns out .. binds tigher than ??= according to https://www.dartlang.org/docs/dart-up-and-running/ch02.html#operators, which means we were only updating the callbacks when we first created the recognizers. Now we update them unconditionally. --- .../lib/src/widgets/gesture_detector.dart | 24 +++++++++++-------- 1 file changed, 14 insertions(+), 10 deletions(-) diff --git a/packages/flutter/lib/src/widgets/gesture_detector.dart b/packages/flutter/lib/src/widgets/gesture_detector.dart index 4efbd6d260..a479f5fc3f 100644 --- a/packages/flutter/lib/src/widgets/gesture_detector.dart +++ b/packages/flutter/lib/src/widgets/gesture_detector.dart @@ -100,8 +100,8 @@ class GestureDetectorState extends State { if (config.onTap == null) { _tap = _ensureDisposed(_tap); } else { - _tap ??= new TapGestureRecognizer(router: _router) - ..onTap = config.onTap; + _tap ??= new TapGestureRecognizer(router: _router); + _tap.onTap = config.onTap; } } @@ -109,8 +109,8 @@ class GestureDetectorState extends State { if (config.onShowPress == null) { _showPress = _ensureDisposed(_showPress); } else { - _showPress ??= new ShowPressGestureRecognizer(router: _router) - ..onShowPress = config.onShowPress; + _showPress ??= new ShowPressGestureRecognizer(router: _router); + _showPress.onShowPress = config.onShowPress; } } @@ -118,8 +118,8 @@ class GestureDetectorState extends State { if (config.onLongPress == null) { _longPress = _ensureDisposed(_longPress); } else { - _longPress ??= new LongPressGestureRecognizer(router: _router) - ..onLongPress = config.onLongPress; + _longPress ??= new LongPressGestureRecognizer(router: _router); + _longPress.onLongPress = config.onLongPress; } } @@ -127,7 +127,8 @@ class GestureDetectorState extends State { if (config.onVerticalDragStart == null && config.onVerticalDragUpdate == null && config.onVerticalDragEnd == null) { _verticalDrag = _ensureDisposed(_verticalDrag); } else { - _verticalDrag ??= new VerticalDragGestureRecognizer(router: _router) + _verticalDrag ??= new VerticalDragGestureRecognizer(router: _router); + _verticalDrag ..onStart = config.onVerticalDragStart ..onUpdate = config.onVerticalDragUpdate ..onEnd = config.onVerticalDragEnd; @@ -138,7 +139,8 @@ class GestureDetectorState extends State { if (config.onHorizontalDragStart == null && config.onHorizontalDragUpdate == null && config.onHorizontalDragEnd == null) { _horizontalDrag = _ensureDisposed(_horizontalDrag); } else { - _horizontalDrag ??= new HorizontalDragGestureRecognizer(router: _router) + _horizontalDrag ??= new HorizontalDragGestureRecognizer(router: _router); + _horizontalDrag ..onStart = config.onHorizontalDragStart ..onUpdate = config.onHorizontalDragUpdate ..onEnd = config.onHorizontalDragEnd; @@ -150,7 +152,8 @@ class GestureDetectorState extends State { _pan = _ensureDisposed(_pan); } else { assert(_scale == null); // Scale is a superset of pan; just use scale - _pan ??= new PanGestureRecognizer(router: _router) + _pan ??= new PanGestureRecognizer(router: _router); + _pan ..onStart = config.onPanStart ..onUpdate = config.onPanUpdate ..onEnd = config.onPanEnd; @@ -162,7 +165,8 @@ class GestureDetectorState extends State { _scale = _ensureDisposed(_scale); } else { assert(_pan == null); // Scale is a superset of pan; just use scale - _scale ??= new ScaleGestureRecognizer(router: _router) + _scale ??= new ScaleGestureRecognizer(router: _router); + _scale ..onStart = config.onScaleStart ..onUpdate = config.onScaleUpdate ..onEnd = config.onScaleEnd; From cf88993492cc32c03c4e58760052a95f87ea7ca2 Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Sat, 3 Oct 2015 01:09:14 -0700 Subject: [PATCH 11/27] RenderInkWell should use gestures After this patch, InkWell is driven by gesture recognizers, which lets us cleanly cancel splashes when the user actually scrolls. I've also refactored all the clients of InkWell to use InkWell to detect gestures instead of wrapping InkWell in a GestureDetector. Fixes #1271 --- examples/demo_launcher/lib/main.dart | 79 ++++----- examples/fitness/lib/feed.dart | 16 +- examples/fitness/lib/meal.dart | 7 +- examples/fitness/lib/measurement.dart | 7 +- examples/stocks/lib/stock_row.dart | 82 +++++---- packages/flutter/lib/src/gestures/tap.dart | 19 ++- .../flutter/lib/src/rendering/toggleable.dart | 2 +- .../flutter/lib/src/widgets/date_picker.dart | 20 +-- packages/flutter/lib/src/widgets/dialog.dart | 3 +- .../flutter/lib/src/widgets/drawer_item.dart | 15 +- .../flutter/lib/src/widgets/flat_button.dart | 3 +- .../src/widgets/floating_action_button.dart | 21 +-- .../lib/src/widgets/gesture_detector.dart | 12 +- .../flutter/lib/src/widgets/icon_button.dart | 3 +- .../flutter/lib/src/widgets/ink_well.dart | 156 ++++++++++++++---- .../lib/src/widgets/material_button.dart | 26 +-- .../flutter/lib/src/widgets/popup_menu.dart | 4 +- .../lib/src/widgets/popup_menu_item.dart | 17 +- .../lib/src/widgets/raised_button.dart | 3 +- .../flutter/lib/src/widgets/snack_bar.dart | 6 +- packages/flutter/lib/src/widgets/tabs.dart | 26 +-- 21 files changed, 320 insertions(+), 207 deletions(-) diff --git a/examples/demo_launcher/lib/main.dart b/examples/demo_launcher/lib/main.dart index 3a80555ccc..4fc089c33d 100644 --- a/examples/demo_launcher/lib/main.dart +++ b/examples/demo_launcher/lib/main.dart @@ -42,8 +42,8 @@ void launch(String relativeUrl, String bundle) { activity.startActivity(intent); } -class SkyDemo { - SkyDemo({ +class FlutterDemo { + FlutterDemo({ name, this.href, this.bundle, @@ -60,8 +60,8 @@ class SkyDemo { final BoxDecoration decoration; } -List demos = [ - new SkyDemo( +List demos = [ + new FlutterDemo( name: 'Stocks', href: '../../stocks/lib/main.dart', bundle: 'stocks.skyx', @@ -74,7 +74,7 @@ List demos = [ ) ) ), - new SkyDemo( + new FlutterDemo( name: 'Asteroids', href: '../../game/lib/main.dart', bundle: 'game.skyx', @@ -87,7 +87,7 @@ List demos = [ ) ) ), - new SkyDemo( + new FlutterDemo( name: 'Fitness', href: '../../fitness/lib/main.dart', bundle: 'fitness.skyx', @@ -97,7 +97,7 @@ List demos = [ backgroundColor: Colors.indigo[500] ) ), - new SkyDemo( + new FlutterDemo( name: 'Swipe Away', href: '../../widgets/card_collection.dart', bundle: 'cards.skyx', @@ -107,7 +107,7 @@ List demos = [ backgroundColor: Colors.redAccent[200] ) ), - new SkyDemo( + new FlutterDemo( name: 'Interactive Text', href: '../../rendering/interactive_flex.dart', bundle: 'interactive_flex.skyx', @@ -120,7 +120,7 @@ List demos = [ // new SkyDemo( // 'Touch Demo', '../../rendering/touch_demo.dart', 'Simple example showing handling of touch events at a low level'), - new SkyDemo( + new FlutterDemo( name: 'Minedigger Game', href: '../../mine_digger/lib/main.dart', bundle: 'mine_digger.skyx', @@ -138,44 +138,47 @@ List demos = [ const double kCardHeight = 120.0; const EdgeDims kListPadding = const EdgeDims.all(4.0); -class DemoList extends StatelessComponent { - Widget buildCardContents(SkyDemo demo) { - return new Container( - decoration: demo.decoration, - child: new InkWell( - child: new Container( - margin: const EdgeDims.only(top: 24.0, left: 24.0), - child: new Column([ - new Text(demo.name, style: demo.textTheme.title), - new Flexible( - child: new Text(demo.description, style: demo.textTheme.subhead) - ) - ], - alignItems: FlexAlignItems.start +class DemoCard extends StatelessComponent { + DemoCard({ Key key, this.demo }) : super(key: key); + + final FlutterDemo demo; + + Widget build(BuildContext context) { + return new Container( + height: kCardHeight, + child: new Card( + child: new Container( + decoration: demo.decoration, + child: new InkWell( + onTap: () => launch(demo.href, demo.bundle), + child: new Container( + margin: const EdgeDims.only(top: 24.0, left: 24.0), + child: new Column([ + new Text(demo.name, style: demo.textTheme.title), + new Flexible( + child: new Text(demo.description, style: demo.textTheme.subhead) + ) + ], + alignItems: FlexAlignItems.start + ) ) ) ) - ); - } - - Widget buildDemo(BuildContext context, SkyDemo demo) { - return new GestureDetector( - key: demo.key, - onTap: () => launch(demo.href, demo.bundle), - child: new Container( - height: kCardHeight, - child: new Card( - child: buildCardContents(demo) - ) ) ); } +} + +class DemoList extends StatelessComponent { + Widget _buildDemoCard(BuildContext context, FlutterDemo demo) { + return new DemoCard(key: demo.key, demo: demo); + } Widget build(BuildContext context) { - return new ScrollableList( + return new ScrollableList( items: demos, itemExtent: kCardHeight, - itemBuilder: buildDemo, + itemBuilder: _buildDemoCard, padding: kListPadding ); } @@ -200,7 +203,7 @@ class DemoHome extends StatelessComponent { void main() { runApp(new App( - title: 'Sky Demos', + title: 'Flutter Demos', theme: _theme, routes: { '/': (NavigatorState navigator, Route route) => new DemoHome() diff --git a/examples/fitness/lib/feed.dart b/examples/fitness/lib/feed.dart index b689531dcf..a3c0275f7e 100644 --- a/examples/fitness/lib/feed.dart +++ b/examples/fitness/lib/feed.dart @@ -33,15 +33,13 @@ class DialogMenuItem extends StatelessComponent { Function onPressed; Widget build(BuildContext context) { - return new GestureDetector( - onTap: onPressed, - child: new Container( - height: 48.0, - child: new InkWell( - child: new Padding( - padding: const EdgeDims.symmetric(horizontal: 16.0), - child: new Row(children) - ) + return new Container( + height: 48.0, + child: new InkWell( + onTap: onPressed, + child: new Padding( + padding: const EdgeDims.symmetric(horizontal: 16.0), + child: new Row(children) ) ) ); diff --git a/examples/fitness/lib/meal.dart b/examples/fitness/lib/meal.dart index 5e65025a5c..e66fb9f900 100644 --- a/examples/fitness/lib/meal.dart +++ b/examples/fitness/lib/meal.dart @@ -65,12 +65,13 @@ class MealFragmentState extends State { icon: "navigation/close", onPressed: config.navigator.pop), center: new Text('New Meal'), - right: [new InkWell( - child: new GestureDetector( + right: [ + // TODO(abarth): Should this be a FlatButton? + new InkWell( onTap: _handleSave, child: new Text('SAVE') ) - )] + ] ); } diff --git a/examples/fitness/lib/measurement.dart b/examples/fitness/lib/measurement.dart index 573228656e..695d82a4c6 100644 --- a/examples/fitness/lib/measurement.dart +++ b/examples/fitness/lib/measurement.dart @@ -136,12 +136,13 @@ class MeasurementFragmentState extends State { icon: "navigation/close", onPressed: config.navigator.pop), center: new Text('New Measurement'), - right: [new InkWell( - child: new GestureDetector( + right: [ + // TODO(abarth): Should this be a FlatButton? + new InkWell( onTap: _handleSave, child: new Text('SAVE') ) - )] + ] ); } diff --git a/examples/stocks/lib/stock_row.dart b/examples/stocks/lib/stock_row.dart index bc522bd9d3..da95327cf1 100644 --- a/examples/stocks/lib/stock_row.dart +++ b/examples/stocks/lib/stock_row.dart @@ -55,52 +55,50 @@ class StockRow extends StatelessComponent { String changeInPrice = "${stock.percentChange.toStringAsFixed(2)}%"; if (stock.percentChange > 0) changeInPrice = "+" + changeInPrice; - return new GestureDetector( + return new InkWell( onTap: _getTapHandler(onPressed), onLongPress: _getLongPressHandler(onLongPressed), - child: new InkWell( - child: new Container( - padding: const EdgeDims.TRBL(16.0, 16.0, 20.0, 16.0), - decoration: new BoxDecoration( - border: new Border( - bottom: new BorderSide(color: Theme.of(context).dividerColor) - ) + child: new Container( + padding: const EdgeDims.TRBL(16.0, 16.0, 20.0, 16.0), + decoration: new BoxDecoration( + border: new Border( + bottom: new BorderSide(color: Theme.of(context).dividerColor) + ) + ), + child: new Row([ + new Container( + key: arrowKey, + child: new StockArrow(percentChange: stock.percentChange), + margin: const EdgeDims.only(right: 5.0) ), - child: new Row([ - new Container( - key: arrowKey, - child: new StockArrow(percentChange: stock.percentChange), - margin: const EdgeDims.only(right: 5.0) - ), - new Flexible( - child: new Row([ - new Flexible( - flex: 2, - child: new Text( - stock.symbol, - key: symbolKey - ) - ), - new Flexible( - child: new Text( - lastSale, - style: const TextStyle(textAlign: TextAlign.right), - key: priceKey - ) - ), - new Flexible( - child: new Text( - changeInPrice, - style: Theme.of(context).text.caption.copyWith(textAlign: TextAlign.right) - ) - ), - ], - alignItems: FlexAlignItems.baseline, - textBaseline: DefaultTextStyle.of(context).textBaseline - ) + new Flexible( + child: new Row([ + new Flexible( + flex: 2, + child: new Text( + stock.symbol, + key: symbolKey + ) + ), + new Flexible( + child: new Text( + lastSale, + style: const TextStyle(textAlign: TextAlign.right), + key: priceKey + ) + ), + new Flexible( + child: new Text( + changeInPrice, + style: Theme.of(context).text.caption.copyWith(textAlign: TextAlign.right) + ) + ), + ], + alignItems: FlexAlignItems.baseline, + textBaseline: DefaultTextStyle.of(context).textBaseline ) - ]) - ) + ) + ]) ) ); } diff --git a/packages/flutter/lib/src/gestures/tap.dart b/packages/flutter/lib/src/gestures/tap.dart index 8c5683fbdf..092874c3da 100644 --- a/packages/flutter/lib/src/gestures/tap.dart +++ b/packages/flutter/lib/src/gestures/tap.dart @@ -14,11 +14,26 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer { : super(router: router); GestureTapCallback onTap; + GestureTapCallback onTapDown; + GestureTapCallback onTapCancel; void handlePrimaryPointer(sky.PointerEvent event) { - if (event.type == 'pointerup') { + if (event.type == 'pointerdown') { + if (onTapDown != null) + onTapDown(); + } else if (event.type == 'pointerup') { resolve(GestureDisposition.accepted); - onTap(); + if (onTap != null) + onTap(); + } + } + + void rejectGesture(int pointer) { + super.rejectGesture(pointer); + if (pointer == primaryPointer) { + assert(state == GestureRecognizerState.defunct); + if (onTapCancel != null) + onTapCancel(); } } } diff --git a/packages/flutter/lib/src/rendering/toggleable.dart b/packages/flutter/lib/src/rendering/toggleable.dart index ad9d7bc132..1a2bbae9ed 100644 --- a/packages/flutter/lib/src/rendering/toggleable.dart +++ b/packages/flutter/lib/src/rendering/toggleable.dart @@ -51,7 +51,7 @@ abstract class RenderToggleable extends RenderConstrainedBox { ); } - void detatch() { + void detach() { _tap.dispose(); _tap = null; super.detach(); diff --git a/packages/flutter/lib/src/widgets/date_picker.dart b/packages/flutter/lib/src/widgets/date_picker.dart index ed3a91c8b7..5773c0a264 100644 --- a/packages/flutter/lib/src/widgets/date_picker.dart +++ b/packages/flutter/lib/src/widgets/date_picker.dart @@ -375,22 +375,20 @@ class YearPickerState extends ScrollableWidgetListState { for(int i = start; i < start + count; i++) { int year = config.firstDate.year + i; String label = year.toString(); - Widget item = new GestureDetector( + Widget item = new InkWell( key: new Key(label), onTap: () { DateTime result = new DateTime(year, config.selectedDate.month, config.selectedDate.day); config.onChanged(result); }, - child: new InkWell( - child: new Container( - height: config.itemExtent, - decoration: year == config.selectedDate.year ? new BoxDecoration( - backgroundColor: Theme.of(context).primarySwatch[100], - shape: Shape.circle - ) : null, - child: new Center( - child: new Text(label, style: style) - ) + child: new Container( + height: config.itemExtent, + decoration: year == config.selectedDate.year ? new BoxDecoration( + backgroundColor: Theme.of(context).primarySwatch[100], + shape: Shape.circle + ) : null, + child: new Center( + child: new Text(label, style: style) ) ) ); diff --git a/packages/flutter/lib/src/widgets/dialog.dart b/packages/flutter/lib/src/widgets/dialog.dart index 9b6ca201bf..ef142d0baa 100644 --- a/packages/flutter/lib/src/widgets/dialog.dart +++ b/packages/flutter/lib/src/widgets/dialog.dart @@ -5,6 +5,7 @@ import 'dart:async'; import 'package:sky/animation.dart'; +import 'package:sky/gestures.dart'; import 'package:sky/material.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/focus.dart'; @@ -52,7 +53,7 @@ class Dialog extends StatelessComponent { final List actions; /// An (optional) callback that is called when the dialog is dismissed. - final Function onDismiss; + final GestureTapCallback onDismiss; Color _getColor(BuildContext context) { switch (Theme.of(context).brightness) { diff --git a/packages/flutter/lib/src/widgets/drawer_item.dart b/packages/flutter/lib/src/widgets/drawer_item.dart index e7a609d06a..2850d9418d 100644 --- a/packages/flutter/lib/src/widgets/drawer_item.dart +++ b/packages/flutter/lib/src/widgets/drawer_item.dart @@ -10,7 +10,6 @@ import 'package:sky/painting.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/button_state.dart'; import 'package:sky/src/widgets/framework.dart'; -import 'package:sky/src/widgets/gesture_detector.dart'; import 'package:sky/src/widgets/icon.dart'; import 'package:sky/src/widgets/ink_well.dart'; import 'package:sky/src/widgets/theme.dart'; @@ -76,14 +75,12 @@ class DrawerItemState extends ButtonState { ) ); - return new GestureDetector( - onTap: config.onPressed, - child: new Container( - height: 48.0, - decoration: new BoxDecoration(backgroundColor: _getBackgroundColor(themeData)), - child: new InkWell( - child: new Row(flexChildren) - ) + return new Container( + height: 48.0, + decoration: new BoxDecoration(backgroundColor: _getBackgroundColor(themeData)), + child: new InkWell( + onTap: config.onPressed, + child: new Row(flexChildren) ) ); } diff --git a/packages/flutter/lib/src/widgets/flat_button.dart b/packages/flutter/lib/src/widgets/flat_button.dart index 501799005c..02beead355 100644 --- a/packages/flutter/lib/src/widgets/flat_button.dart +++ b/packages/flutter/lib/src/widgets/flat_button.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:sky/gestures.dart'; import 'package:sky/material.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/framework.dart'; @@ -13,7 +14,7 @@ class FlatButton extends MaterialButton { Key key, Widget child, bool enabled: true, - Function onPressed + GestureTapCallback onPressed }) : super(key: key, child: child, enabled: enabled, diff --git a/packages/flutter/lib/src/widgets/floating_action_button.dart b/packages/flutter/lib/src/widgets/floating_action_button.dart index 24f57bdc25..6b7d4b66c3 100644 --- a/packages/flutter/lib/src/widgets/floating_action_button.dart +++ b/packages/flutter/lib/src/widgets/floating_action_button.dart @@ -6,7 +6,6 @@ import 'package:sky/gestures.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/button_state.dart'; import 'package:sky/src/widgets/framework.dart'; -import 'package:sky/src/widgets/gesture_detector.dart'; import 'package:sky/src/widgets/icon.dart'; import 'package:sky/src/widgets/ink_well.dart'; import 'package:sky/src/widgets/material.dart'; @@ -46,17 +45,15 @@ class FloatingActionButtonState extends ButtonState { type: MaterialType.circle, level: highlight ? 3 : 2, child: new ClipOval( - child: new GestureDetector( - onTap: config.onPressed, - child: new Container( - width: _kSize, - height: _kSize, - child: new InkWell( - child: new Center( - child: new IconTheme( - data: new IconThemeData(color: iconThemeColor), - child: config.child - ) + child: new Container( + width: _kSize, + height: _kSize, + child: new InkWell( + onTap: config.onPressed, + child: new Center( + child: new IconTheme( + data: new IconThemeData(color: iconThemeColor), + child: config.child ) ) ) diff --git a/packages/flutter/lib/src/widgets/gesture_detector.dart b/packages/flutter/lib/src/widgets/gesture_detector.dart index a479f5fc3f..fb3407908b 100644 --- a/packages/flutter/lib/src/widgets/gesture_detector.dart +++ b/packages/flutter/lib/src/widgets/gesture_detector.dart @@ -14,6 +14,8 @@ class GestureDetector extends StatefulComponent { Key key, this.child, this.onTap, + this.onTapDown, + this.onTapCancel, this.onShowPress, this.onLongPress, this.onVerticalDragStart, @@ -33,6 +35,9 @@ class GestureDetector extends StatefulComponent { final Widget child; final GestureTapCallback onTap; + final GestureTapCallback onTapDown; + final GestureTapCallback onTapCancel; + final GestureShowPressCallback onShowPress; final GestureLongPressCallback onLongPress; @@ -97,11 +102,14 @@ class GestureDetectorState extends State { } void _syncTap() { - if (config.onTap == null) { + if (config.onTap == null && config.onTapDown == null && config.onTapCancel == null) { _tap = _ensureDisposed(_tap); } else { _tap ??= new TapGestureRecognizer(router: _router); - _tap.onTap = config.onTap; + _tap + ..onTap = config.onTap + ..onTapDown = config.onTapDown + ..onTapCancel = config.onTapCancel; } } diff --git a/packages/flutter/lib/src/widgets/icon_button.dart b/packages/flutter/lib/src/widgets/icon_button.dart index fcb42fd327..662ee5fb52 100644 --- a/packages/flutter/lib/src/widgets/icon_button.dart +++ b/packages/flutter/lib/src/widgets/icon_button.dart @@ -4,6 +4,7 @@ import 'dart:sky' as sky; +import 'package:sky/gestures.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/icon.dart'; import 'package:sky/src/widgets/framework.dart'; @@ -13,8 +14,8 @@ class IconButton extends StatelessComponent { const IconButton({ Key key, this.icon, this.onPressed, this.color }) : super(key: key); final String icon; - final Function onPressed; final Color color; + final GestureTapCallback onPressed; Widget build(BuildContext context) { Widget child = new Icon(type: icon, size: 24); diff --git a/packages/flutter/lib/src/widgets/ink_well.dart b/packages/flutter/lib/src/widgets/ink_well.dart index 4e44e0517b..a0f36726d1 100644 --- a/packages/flutter/lib/src/widgets/ink_well.dart +++ b/packages/flutter/lib/src/widgets/ink_well.dart @@ -2,16 +2,18 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'dart:async'; import 'dart:math' as math; import 'dart:sky' as sky; import 'package:sky/animation.dart'; +import 'package:sky/gestures.dart'; import 'package:sky/rendering.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/framework.dart'; const int _kSplashInitialOpacity = 0x30; -const double _kSplashCancelledVelocity = 0.7; +const double _kSplashCanceledVelocity = 0.7; const double _kSplashConfirmedVelocity = 0.7; const double _kSplashInitialSize = 0.0; const double _kSplashUnconfirmedVelocity = 0.2; @@ -25,7 +27,7 @@ double _getSplashTargetSize(Size bounds, Point position) { } class InkSplash { - InkSplash(this.pointer, this.position, this.well) { + InkSplash(this.position, this.well) { _targetRadius = _getSplashTargetSize(well.size, position); _radius = new AnimatedValue( _kSplashInitialSize, end: _targetRadius, curve: easeOut); @@ -33,11 +35,12 @@ class InkSplash { _performance = new ValueAnimation( variable: _radius, duration: new Duration(milliseconds: (_targetRadius / _kSplashUnconfirmedVelocity).floor()) - )..addListener(_handleRadiusChange) - ..play(); + )..addListener(_handleRadiusChange); + + // Wait kTapTimeout to avoid creating tiny splashes during scrolls. + _startTimer = new Timer(kTapTimeout, _play); } - final int pointer; final Point position; final RenderInkWell well; @@ -45,20 +48,39 @@ class InkSplash { double _pinnedRadius; AnimatedValue _radius; AnimationPerformance _performance; + Timer _startTimer; + + bool _cancelStartTimer() { + if (_startTimer != null) { + _startTimer.cancel(); + _startTimer = null; + return true; + } + return false; + } + + void _play() { + _cancelStartTimer(); + _performance.play(); + } void _updateVelocity(double velocity) { int duration = (_targetRadius / velocity).floor(); - _performance - ..duration = new Duration(milliseconds: duration) - ..play(); + _performance.duration = new Duration(milliseconds: duration); + _play(); } void confirm() { + if (_cancelStartTimer()) + return; _updateVelocity(_kSplashConfirmedVelocity); + _pinnedRadius = null; } void cancel() { - _updateVelocity(_kSplashCancelledVelocity); + if (_cancelStartTimer()) + return; + _updateVelocity(_kSplashCanceledVelocity); _pinnedRadius = _radius.value; } @@ -77,38 +99,95 @@ class InkSplash { } class RenderInkWell extends RenderProxyBox { - RenderInkWell({ RenderBox child }) : super(child); + RenderInkWell({ + RenderBox child, + GestureTapCallback onTap, + GestureLongPressCallback onLongPress + }) : super(child) { + this.onTap = onTap; + this.onLongPress = onLongPress; + } + + GestureTapCallback get onTap => _onTap; + GestureTapCallback _onTap; + void set onTap (GestureTapCallback value) { + _onTap = value; + _syncTapRecognizer(); + } + + GestureTapCallback get onLongPress => _onLongPress; + GestureTapCallback _onLongPress; + void set onLongPress (GestureTapCallback value) { + _onLongPress = value; + _syncLongPressRecognizer(); + } final List _splashes = new List(); + TapGestureRecognizer _tap; + LongPressGestureRecognizer _longPress; + void handleEvent(sky.Event event, BoxHitTestEntry entry) { - // TODO(abarth): We should trigger these effects based on gestures. - // https://github.com/flutter/engine/issues/1271 - if (event is sky.PointerEvent) { - switch (event.type) { - case 'pointerdown': - _startSplash(event.pointer, entry.localPosition); - break; - case 'pointerup': - _confirmSplash(event.pointer); - break; - } + if (event.type == 'pointerdown' && (_tap != null || _longPress != null)) { + _tap?.addPointer(event); + _longPress?.addPointer(event); + _splashes.add(new InkSplash(entry.localPosition, this)); } } - void _startSplash(int pointer, Point position) { - _splashes.add(new InkSplash(pointer, position, this)); - markNeedsPaint(); + void attach() { + super.attach(); + _syncTapRecognizer(); + _syncLongPressRecognizer(); } - void _forEachSplash(int pointer, Function callback) { - _splashes.where((splash) => splash.pointer == pointer) - .forEach(callback); + void detach() { + _disposeTapRecognizer(); + _disposeLongPressRecognizer(); + super.detach(); } - void _confirmSplash(int pointer) { - _forEachSplash(pointer, (splash) { splash.confirm(); }); - markNeedsPaint(); + void _syncTapRecognizer() { + if (onTap == null) { + _disposeTapRecognizer(); + } else { + _tap ??= new TapGestureRecognizer(router: FlutterBinding.instance.pointerRouter) + ..onTap = _handleTap + ..onTapCancel = _handleTapCancel; + } + } + + void _disposeTapRecognizer() { + _tap?.dispose(); + _tap = null; + } + + void _syncLongPressRecognizer() { + if (onLongPress == null) { + _disposeLongPressRecognizer(); + } else { + _longPress ??= new LongPressGestureRecognizer(router: FlutterBinding.instance.pointerRouter) + ..onLongPress = _handleLongPress; + } + } + + void _disposeLongPressRecognizer() { + _longPress?.dispose(); + _longPress = null; + } + + void _handleTap() { + _splashes.last?.confirm(); + onTap(); + } + + void _handleTapCancel() { + _splashes.last?.cancel(); + } + + void _handleLongPress() { + _splashes.last?.confirm(); + onLongPress(); } void paint(PaintingContext context, Offset offset) { @@ -126,7 +205,20 @@ class RenderInkWell extends RenderProxyBox { } class InkWell extends OneChildRenderObjectWidget { - InkWell({ Key key, Widget child }) : super(key: key, child: child); + InkWell({ + Key key, + Widget child, + this.onTap, + this.onLongPress + }) : super(key: key, child: child); - RenderInkWell createRenderObject() => new RenderInkWell(); + final GestureTapCallback onTap; + final GestureLongPressCallback onLongPress; + + RenderInkWell createRenderObject() => new RenderInkWell(onTap: onTap, onLongPress: onLongPress); + + void updateRenderObject(RenderInkWell renderObject, InkWell oldWidget) { + renderObject.onTap = onTap; + renderObject.onLongPress = onLongPress; + } } diff --git a/packages/flutter/lib/src/widgets/material_button.dart b/packages/flutter/lib/src/widgets/material_button.dart index c5dfb0a7be..b18cc313c1 100644 --- a/packages/flutter/lib/src/widgets/material_button.dart +++ b/packages/flutter/lib/src/widgets/material_button.dart @@ -2,10 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:sky/gestures.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/button_state.dart'; import 'package:sky/src/widgets/framework.dart'; -import 'package:sky/src/widgets/gesture_detector.dart'; import 'package:sky/src/widgets/ink_well.dart'; import 'package:sky/src/widgets/material.dart'; @@ -22,7 +22,7 @@ abstract class MaterialButton extends StatefulComponent { final Widget child; final bool enabled; - final Function onPressed; + final GestureTapCallback onPressed; } abstract class MaterialButtonState extends ButtonState { @@ -37,17 +37,17 @@ abstract class MaterialButtonState extends ButtonState child: config.child // TODO(ianh): figure out a way to compell the child to have gray text when disabled... ) ); - return new GestureDetector( - onTap: config.enabled ? config.onPressed : null, - child: new Container( - height: 36.0, - constraints: new BoxConstraints(minWidth: 88.0), - margin: new EdgeDims.all(8.0), - child: new Material( - type: MaterialType.button, - child: config.enabled ? new InkWell(child: contents) : contents, - level: level, - color: getColor(context) + return new Container( + height: 36.0, + constraints: new BoxConstraints(minWidth: 88.0), + margin: new EdgeDims.all(8.0), + child: new Material( + type: MaterialType.button, + level: level, + color: getColor(context), + child: new InkWell( + onTap: config.enabled ? config.onPressed : null, + child: contents ) ) ); diff --git a/packages/flutter/lib/src/widgets/popup_menu.dart b/packages/flutter/lib/src/widgets/popup_menu.dart index f66f1007c7..c06e9539c8 100644 --- a/packages/flutter/lib/src/widgets/popup_menu.dart +++ b/packages/flutter/lib/src/widgets/popup_menu.dart @@ -11,7 +11,7 @@ import 'package:sky/painting.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/focus.dart'; import 'package:sky/src/widgets/framework.dart'; -import 'package:sky/src/widgets/gesture_detector.dart'; +import 'package:sky/src/widgets/ink_well.dart'; import 'package:sky/src/widgets/navigator.dart'; import 'package:sky/src/widgets/popup_menu_item.dart'; import 'package:sky/src/widgets/scrollable.dart'; @@ -94,7 +94,7 @@ class PopupMenuState extends State { children.add(new FadeTransition( performance: config.performance, opacity: new AnimatedValue(0.0, end: 1.0, interval: new Interval(start, end)), - child: new GestureDetector( + child: new InkWell( onTap: () { config.navigator.pop(config.items[i].value); }, child: config.items[i] )) diff --git a/packages/flutter/lib/src/widgets/popup_menu_item.dart b/packages/flutter/lib/src/widgets/popup_menu_item.dart index 1e44c23c2d..d52d8e26ab 100644 --- a/packages/flutter/lib/src/widgets/popup_menu_item.dart +++ b/packages/flutter/lib/src/widgets/popup_menu_item.dart @@ -4,7 +4,6 @@ import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/framework.dart'; -import 'package:sky/src/widgets/ink_well.dart'; import 'package:sky/src/widgets/theme.dart'; const double _kMenuItemHeight = 48.0; @@ -21,15 +20,13 @@ class PopupMenuItem extends StatelessComponent { final dynamic value; Widget build(BuildContext context) { - return new InkWell( - child: new Container( - height: _kMenuItemHeight, - child: new DefaultTextStyle( - style: Theme.of(context).text.subhead, - child: new Baseline( - baseline: _kMenuItemHeight - _kBaselineOffsetFromBottom, - child: child - ) + return new Container( + height: _kMenuItemHeight, + child: new DefaultTextStyle( + style: Theme.of(context).text.subhead, + child: new Baseline( + baseline: _kMenuItemHeight - _kBaselineOffsetFromBottom, + child: child ) ) ); diff --git a/packages/flutter/lib/src/widgets/raised_button.dart b/packages/flutter/lib/src/widgets/raised_button.dart index bf666f2783..22c596511c 100644 --- a/packages/flutter/lib/src/widgets/raised_button.dart +++ b/packages/flutter/lib/src/widgets/raised_button.dart @@ -2,6 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. +import 'package:sky/gestures.dart'; import 'package:sky/material.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/framework.dart'; @@ -13,7 +14,7 @@ class RaisedButton extends MaterialButton { Key key, Widget child, bool enabled: true, - Function onPressed + GestureTapCallback onPressed }) : super(key: key, child: child, enabled: enabled, diff --git a/packages/flutter/lib/src/widgets/snack_bar.dart b/packages/flutter/lib/src/widgets/snack_bar.dart index 82ff38feb3..179e8320ce 100644 --- a/packages/flutter/lib/src/widgets/snack_bar.dart +++ b/packages/flutter/lib/src/widgets/snack_bar.dart @@ -2,10 +2,10 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. - import 'package:sky/animation.dart'; -import 'package:sky/painting.dart'; +import 'package:sky/gestures.dart'; import 'package:sky/material.dart'; +import 'package:sky/painting.dart'; import 'package:sky/src/widgets/animated_component.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/framework.dart'; @@ -28,7 +28,7 @@ class SnackBarAction extends StatelessComponent { } final String label; - final Function onPressed; + final GestureTapCallback onPressed; Widget build(BuildContext) { return new GestureDetector( diff --git a/packages/flutter/lib/src/widgets/tabs.dart b/packages/flutter/lib/src/widgets/tabs.dart index d65957c433..c00bc8ba6f 100644 --- a/packages/flutter/lib/src/widgets/tabs.dart +++ b/packages/flutter/lib/src/widgets/tabs.dart @@ -7,9 +7,10 @@ import 'dart:sky' as sky; import 'package:newton/newton.dart'; import 'package:sky/animation.dart'; +import 'package:sky/gestures.dart'; +import 'package:sky/material.dart'; import 'package:sky/painting.dart'; import 'package:sky/rendering.dart'; -import 'package:sky/material.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/gesture_detector.dart'; @@ -307,6 +308,7 @@ class TabLabel { class Tab extends StatelessComponent { Tab({ Key key, + this.onSelected, this.label, this.color, this.selected: false, @@ -315,6 +317,7 @@ class Tab extends StatelessComponent { assert(label.text != null || label.icon != null); } + final GestureTapCallback onSelected; final TabLabel label; final Color color; final bool selected; @@ -359,7 +362,10 @@ class Tab extends StatelessComponent { padding: _kTabLabelPadding ); - return new InkWell(child: centeredLabel); + return new InkWell( + onTap: onSelected, + child: centeredLabel + ); } } @@ -458,7 +464,7 @@ class TabBarState extends ScrollableState { .clamp(scrollBehavior.minScrollOffset, scrollBehavior.maxScrollOffset); } - void _handleTap(int tabIndex) { + void _handleTabSelected(int tabIndex) { if (tabIndex != config.selectedIndex) { if (_tabWidths != null) { if (config.isScrollable) @@ -471,14 +477,12 @@ class TabBarState extends ScrollableState { } Widget _toTab(TabLabel label, int tabIndex, Color color, Color selectedColor) { - return new GestureDetector( - onTap: () => _handleTap(tabIndex), - child: new Tab( - label: label, - color: color, - selected: tabIndex == config.selectedIndex, - selectedColor: selectedColor - ) + return new Tab( + onSelected: () => _handleTabSelected(tabIndex), + label: label, + color: color, + selected: tabIndex == config.selectedIndex, + selectedColor: selectedColor ); } From 5287d13b35cd32ff911acebd8c6d50f3c372fa3c Mon Sep 17 00:00:00 2001 From: Mehmet Akin Date: Sat, 3 Oct 2015 22:22:30 +0200 Subject: [PATCH 12/27] Simplfy resetting board and mine generation. --- examples/mine_digger/lib/main.dart | 40 ++++++++++++------------------ 1 file changed, 16 insertions(+), 24 deletions(-) diff --git a/examples/mine_digger/lib/main.dart b/examples/mine_digger/lib/main.dart index 8c823d8c50..6d2fb8ad56 100644 --- a/examples/mine_digger/lib/main.dart +++ b/examples/mine_digger/lib/main.dart @@ -64,33 +64,25 @@ class MineDiggerState extends State { alive = true; hasWon = false; detectedCount = 0; - // Build the arrays. - cells = new List>(); - uiState = new List>(); - for (int iy = 0; iy != rows; iy++) { - cells.add(new List()); - uiState.add(new List()); - for (int ix = 0; ix != cols; ix++) { - cells[iy].add(false); - uiState[iy].add(CellState.covered); - } - } + // Initialize matrices. + cells = new List.generate(rows, (int row) { + return new List.filled(cols, false); + }); + uiState = new List.generate(rows, (int row) { + return new List.filled(cols, CellState.covered); + }); // Place the mines. Random random = new Random(); - int cellsRemaining = rows * cols; int minesRemaining = totalMineCount; - for (int x = 0; x < cols; x += 1) { - for (int y = 0; y < rows; y += 1) { - if (random.nextInt(cellsRemaining) < minesRemaining) { - cells[y][x] = true; - minesRemaining -= 1; - if (minesRemaining <= 0) - return; - } - cellsRemaining -= 1; + while (minesRemaining > 0) { + int pos = random.nextInt(rows * cols); + int row = pos ~/ rows; + int col = pos % cols; + if (!cells[row][col]) { + cells[row][col] = true; + minesRemaining--; } } - assert(false); } PointerEventListener _pointerDownHandlerFor(int posX, int posY) { @@ -106,9 +98,9 @@ class MineDiggerState extends State { Widget buildBoard() { bool hasCoveredCell = false; List flexRows = []; - for (int iy = 0; iy != 9; iy++) { + for (int iy = 0; iy < rows; iy++) { List row = []; - for (int ix = 0; ix != 9; ix++) { + for (int ix = 0; ix < cols; ix++) { CellState state = uiState[iy][ix]; int count = mineCount(ix, iy); if (!alive) { From 34238dd879793edd5b8f46c7a4fd9a59eb26eed7 Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Fri, 2 Oct 2015 23:21:40 -0700 Subject: [PATCH 13/27] Force AnimatedVariables to hit begin on 0.0 We already forced hitting end on 1.0. Fixes #1358 --- .../lib/src/animation/animated_value.dart | 30 +++++++++++++------ .../flutter/lib/src/widgets/dismissable.dart | 5 ---- 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/packages/flutter/lib/src/animation/animated_value.dart b/packages/flutter/lib/src/animation/animated_value.dart index 68da613971..732c8f2855 100644 --- a/packages/flutter/lib/src/animation/animated_value.dart +++ b/packages/flutter/lib/src/animation/animated_value.dart @@ -2,7 +2,7 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import "dart:sky"; +import 'dart:sky' show Color, Rect; import 'package:sky/src/animation/curves.dart'; @@ -16,8 +16,8 @@ enum Direction { } /// An interface describing a variable that changes as an animation progresses. -/// -/// AnimatedVariables, by convention, must be cheap to create. This allows them to be used in +/// +/// AnimatedVariables, by convention, must be cheap to create. This allows them to be used in /// build functions in Widgets. abstract class AnimatedVariable { /// Update the variable to a given time in an animation that is running in the given direction @@ -57,12 +57,12 @@ class AnimationTiming { Interval interval = _getInterval(direction); if (interval != null) t = interval.transform(t); - if (t == 1.0) // Or should we support inverse curves? + assert(t >= 0.0 && t <= 1.0); + if (t == 0.0 || t == 1.0) { + assert(t == _applyCurve(t, direction).round().toDouble()); return t; - Curve curve = _getCurve(direction); - if (curve != null) - t = curve.transform(t); - return t; + } + return _applyCurve(t, direction); } Interval _getInterval(Direction direction) { @@ -76,6 +76,13 @@ class AnimationTiming { return curve; return reverseCurve; } + + double _applyCurve(double t, Direction direction) { + Curve curve = _getCurve(direction); + if (curve == null) + return t; + return curve.transform(t); + } } /// An animated variable with a concrete type @@ -101,7 +108,12 @@ class AnimatedValue extends AnimationTiming implements Animat void setProgress(double t, Direction direction) { if (end != null) { t = transform(t, direction); - value = (t == 1.0) ? end : lerp(t); + if (t == 0.0) + value = begin; + else if (t == 1.0) + value = end; + else + value = lerp(t); } } diff --git a/packages/flutter/lib/src/widgets/dismissable.dart b/packages/flutter/lib/src/widgets/dismissable.dart index fe270ab390..6cb2a78e9b 100644 --- a/packages/flutter/lib/src/widgets/dismissable.dart +++ b/packages/flutter/lib/src/widgets/dismissable.dart @@ -102,11 +102,6 @@ class DismissableState extends State { ..addListener(_handleResizeProgressChanged); _resizePerformance.play(); }); - // Our squash curve (ease) does not return v=0.0 for t=0.0, so we - // technically resize on the first frame. To make sure this doesn't confuse - // any other widgets (like MixedViewport, which checks for this kind of - // thing), we report a resize straight away. - _maybeCallOnResized(); } void _handleResizeProgressChanged() { From db191e96bdeb79724ba4fe2b5043e11ee37848fe Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Sat, 3 Oct 2015 10:55:25 -0700 Subject: [PATCH 14/27] Switch scheduler over to Duration This patch prepares us to switch to using integers when handing off the animation time from the engine to the framework. --- examples/rendering/spinning_flex.dart | 6 +++--- examples/widgets/spinning_mixed.dart | 6 +++--- .../lib/src/animation/animated_simulation.dart | 14 ++++++-------- packages/flutter/lib/src/animation/scheduler.dart | 8 +++++--- packages/flutter/lib/src/rendering/binding.dart | 2 +- packages/flutter/lib/src/widgets/binding.dart | 2 +- packages/flutter_sprites/lib/sprite_box.dart | 9 +++++---- packages/unit/test/animation/scheduler_test.dart | 8 ++++---- 8 files changed, 28 insertions(+), 27 deletions(-) diff --git a/examples/rendering/spinning_flex.dart b/examples/rendering/spinning_flex.dart index db2055ca23..1e72aac95a 100644 --- a/examples/rendering/spinning_flex.dart +++ b/examples/rendering/spinning_flex.dart @@ -9,7 +9,7 @@ import 'package:sky/rendering.dart'; import 'solid_color_box.dart'; -double timeBase; +Duration timeBase; RenderTransform transformBox; void main() { @@ -34,10 +34,10 @@ void main() { scheduler.addPersistentFrameCallback(rotate); } -void rotate(double timeStamp) { +void rotate(Duration timeStamp) { if (timeBase == null) timeBase = timeStamp; - double delta = (timeStamp - timeBase) / 1000; // radians + double delta = (timeStamp - timeBase).inMicroseconds.toDouble() / Duration.MICROSECONDS_PER_SECOND; // radians transformBox.setIdentity(); transformBox.translate(transformBox.size.width / 2.0, transformBox.size.height / 2.0); diff --git a/examples/widgets/spinning_mixed.dart b/examples/widgets/spinning_mixed.dart index 9317390a99..f44b0e0acd 100644 --- a/examples/widgets/spinning_mixed.dart +++ b/examples/widgets/spinning_mixed.dart @@ -54,13 +54,13 @@ Widget builder() { ); } -double timeBase; +Duration timeBase; RenderTransform transformBox; -void rotate(double timeStamp) { +void rotate(Duration timeStamp) { if (timeBase == null) timeBase = timeStamp; - double delta = (timeStamp - timeBase) / 1000; // radians + double delta = (timeStamp - timeBase).inMicroseconds.toDouble() / Duration.MICROSECONDS_PER_SECOND; // radians transformBox.setIdentity(); transformBox.translate(transformBox.size.width / 2.0, transformBox.size.height / 2.0); diff --git a/packages/flutter/lib/src/animation/animated_simulation.dart b/packages/flutter/lib/src/animation/animated_simulation.dart index 0710dfc6d2..6c2f8afb22 100644 --- a/packages/flutter/lib/src/animation/animated_simulation.dart +++ b/packages/flutter/lib/src/animation/animated_simulation.dart @@ -7,10 +7,7 @@ import 'dart:async'; import 'package:newton/newton.dart'; import 'package:sky/src/animation/scheduler.dart'; -const double _kSecondsPerMillisecond = 1000.0; - -// TODO(abarth): Change from double to Duration. -typedef _TickerCallback(double timeStamp); +typedef _TickerCallback(Duration timeStamp); /// Calls its callback once per animation frame class Ticker { @@ -56,7 +53,7 @@ class Ticker { /// Whether this ticker has scheduled a call to onTick bool get isTicking => _completer != null; - void _tick(double timeStamp) { + void _tick(Duration timeStamp) { assert(isTicking); assert(_animationId != null); _animationId = null; @@ -86,7 +83,7 @@ class AnimatedSimulation { Ticker _ticker; Simulation _simulation; - double _startTime; + Duration _startTime; double _value = 0.0; /// The current value of the simulation @@ -119,11 +116,12 @@ class AnimatedSimulation { /// Whether this object is currently ticking a simulation bool get isAnimating => _ticker.isTicking; - void _tick(double timeStamp) { + void _tick(Duration timeStamp) { if (_startTime == null) _startTime = timeStamp; - double timeInSeconds = (timeStamp - _startTime) / _kSecondsPerMillisecond; + double timeInMicroseconds = (timeStamp - _startTime).inMicroseconds.toDouble(); + double timeInSeconds = timeInMicroseconds / Duration.MICROSECONDS_PER_SECOND; _value = _simulation.x(timeInSeconds); final bool isLastTick = _simulation.isDone(timeInSeconds); diff --git a/packages/flutter/lib/src/animation/scheduler.dart b/packages/flutter/lib/src/animation/scheduler.dart index 999ba89008..bfa585e0f3 100644 --- a/packages/flutter/lib/src/animation/scheduler.dart +++ b/packages/flutter/lib/src/animation/scheduler.dart @@ -14,7 +14,7 @@ double timeDilation = 1.0; /// scheduler's epoch. Use timeStamp to determine how far to advance animation /// timelines so that all the animations in the system are synchronized to a /// common time base. -typedef void SchedulerCallback(double timeStamp); +typedef void SchedulerCallback(Duration timeStamp); /// Schedules callbacks to run in concert with the engine's animation system class Scheduler { @@ -35,8 +35,10 @@ class Scheduler { /// This function first calls all the callbacks registered by /// [requestAnimationFrame] and then calls all the callbacks registered by /// [addPersistentFrameCallback], which typically drive the rendering pipeline. - void beginFrame(double timeStamp) { - timeStamp /= timeDilation; + void beginFrame(double timeStampMS) { + timeStampMS /= timeDilation; + + Duration timeStamp = new Duration(microseconds: (timeStampMS * Duration.MICROSECONDS_PER_MILLISECOND).round()); _haveScheduledVisualUpdate = false; diff --git a/packages/flutter/lib/src/rendering/binding.dart b/packages/flutter/lib/src/rendering/binding.dart index 59bf21b5c8..bf9e31ad12 100644 --- a/packages/flutter/lib/src/rendering/binding.dart +++ b/packages/flutter/lib/src/rendering/binding.dart @@ -78,7 +78,7 @@ class FlutterBinding extends HitTestTarget { } /// Pump the rendering pipeline to generate a frame for the given time stamp - void beginFrame(double timeStamp) { + void beginFrame(Duration timeStamp) { RenderObject.flushLayout(); _renderView.updateCompositingBits(); RenderObject.flushPaint(); diff --git a/packages/flutter/lib/src/widgets/binding.dart b/packages/flutter/lib/src/widgets/binding.dart index 792c6a161a..59056dfd4f 100644 --- a/packages/flutter/lib/src/widgets/binding.dart +++ b/packages/flutter/lib/src/widgets/binding.dart @@ -35,7 +35,7 @@ class WidgetFlutterBinding extends FlutterBinding { ); } - void beginFrame(double timeStamp) { + void beginFrame(Duration timeStamp) { buildDirtyElements(); super.beginFrame(timeStamp); Element.finalizeTree(); diff --git a/packages/flutter_sprites/lib/sprite_box.dart b/packages/flutter_sprites/lib/sprite_box.dart index 07e6d317d0..3abf3be04c 100644 --- a/packages/flutter_sprites/lib/sprite_box.dart +++ b/packages/flutter_sprites/lib/sprite_box.dart @@ -47,7 +47,7 @@ class SpriteBox extends RenderBox { } // Tracking of frame rate and updates - double _lastTimeStamp; + Duration _lastTimeStamp; double _frameRate = 0.0; double get frameRate => _frameRate; @@ -349,13 +349,14 @@ class SpriteBox extends RenderBox { scheduler.requestAnimationFrame(_tick); } - void _tick(double timeStamp) { + void _tick(Duration timeStamp) { if (!attached) return; // Calculate delta and frame rate - if (_lastTimeStamp == null) _lastTimeStamp = timeStamp; - double delta = (timeStamp - _lastTimeStamp) / 1000; + if (_lastTimeStamp == null) + _lastTimeStamp = timeStamp; + double delta = (timeStamp - _lastTimeStamp).inMicroseconds.toDouble() / Duration.MICROSECONDS_PER_SECOND; _lastTimeStamp = timeStamp; _frameRate = 1.0/delta; diff --git a/packages/unit/test/animation/scheduler_test.dart b/packages/unit/test/animation/scheduler_test.dart index 380de02bb3..62f9379c3d 100644 --- a/packages/unit/test/animation/scheduler_test.dart +++ b/packages/unit/test/animation/scheduler_test.dart @@ -8,18 +8,18 @@ void main() { bool firstCallbackRan = false; bool secondCallbackRan = false; - void firstCallback(double timeStamp) { + void firstCallback(Duration timeStamp) { expect(firstCallbackRan, isFalse); expect(secondCallbackRan, isFalse); - expect(timeStamp, equals(16.0)); + expect(timeStamp.inMilliseconds, equals(16)); firstCallbackRan = true; scheduler.cancelAnimationFrame(secondId); } - void secondCallback(double timeStamp) { + void secondCallback(Duration timeStamp) { expect(firstCallbackRan, isTrue); expect(secondCallbackRan, isFalse); - expect(timeStamp, equals(16.0)); + expect(timeStamp.inMilliseconds, equals(16)); secondCallbackRan = true; } From 203e6fd7e808a2c58b72a517862b255cf59fc5d7 Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Sat, 3 Oct 2015 11:19:11 -0700 Subject: [PATCH 15/27] Make Ticker start ticking at zero Duration The only client wants a zero-based duration. --- .../src/animation/animated_simulation.dart | 26 +++++++++---------- 1 file changed, 13 insertions(+), 13 deletions(-) diff --git a/packages/flutter/lib/src/animation/animated_simulation.dart b/packages/flutter/lib/src/animation/animated_simulation.dart index 6c2f8afb22..32f0a91aa7 100644 --- a/packages/flutter/lib/src/animation/animated_simulation.dart +++ b/packages/flutter/lib/src/animation/animated_simulation.dart @@ -7,7 +7,7 @@ import 'dart:async'; import 'package:newton/newton.dart'; import 'package:sky/src/animation/scheduler.dart'; -typedef _TickerCallback(Duration timeStamp); +typedef _TickerCallback(Duration elapsed); /// Calls its callback once per animation frame class Ticker { @@ -18,12 +18,14 @@ class Ticker { Completer _completer; int _animationId; + Duration _startTime; /// Start calling onTick once per animation frame /// /// The returned future resolves once the ticker stops ticking. Future start() { assert(!isTicking); + assert(_startTime == null); _completer = new Completer(); _scheduleTick(); return _completer.future; @@ -36,6 +38,8 @@ class Ticker { if (!isTicking) return; + _startTime = null; + if (_animationId != null) { scheduler.cancelAnimationFrame(_animationId); _animationId = null; @@ -58,7 +62,10 @@ class Ticker { assert(_animationId != null); _animationId = null; - _onTick(timeStamp); + if (_startTime == null) + _startTime = timeStamp; + + _onTick(timeStamp - _startTime); // The onTick callback may have scheduled another tick already. if (isTicking && _animationId == null) @@ -83,7 +90,6 @@ class AnimatedSimulation { Ticker _ticker; Simulation _simulation; - Duration _startTime; double _value = 0.0; /// The current value of the simulation @@ -101,7 +107,6 @@ class AnimatedSimulation { assert(simulation != null); assert(!_ticker.isTicking); _simulation = simulation; - _startTime = null; _value = simulation.x(0.0); return _ticker.start(); } @@ -109,23 +114,18 @@ class AnimatedSimulation { /// Stop ticking the current simulation void stop() { _simulation = null; - _startTime = null; _ticker.stop(); } /// Whether this object is currently ticking a simulation bool get isAnimating => _ticker.isTicking; - void _tick(Duration timeStamp) { - if (_startTime == null) - _startTime = timeStamp; + void _tick(Duration elapsed) { - double timeInMicroseconds = (timeStamp - _startTime).inMicroseconds.toDouble(); - double timeInSeconds = timeInMicroseconds / Duration.MICROSECONDS_PER_SECOND; - _value = _simulation.x(timeInSeconds); - final bool isLastTick = _simulation.isDone(timeInSeconds); + double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.MICROSECONDS_PER_SECOND; + _value = _simulation.x(elapsedInSeconds); - if (isLastTick) + if (_simulation.isDone(elapsedInSeconds)) stop(); _onTick(_value); From 8fdd8cb580e6e0905b0fc32f03679b569942ece1 Mon Sep 17 00:00:00 2001 From: Mehmet Akin Date: Sat, 3 Oct 2015 22:56:30 +0200 Subject: [PATCH 16/27] Fix indent. --- examples/mine_digger/lib/main.dart | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/examples/mine_digger/lib/main.dart b/examples/mine_digger/lib/main.dart index 6d2fb8ad56..4c78fe30c6 100644 --- a/examples/mine_digger/lib/main.dart +++ b/examples/mine_digger/lib/main.dart @@ -66,10 +66,10 @@ class MineDiggerState extends State { detectedCount = 0; // Initialize matrices. cells = new List.generate(rows, (int row) { - return new List.filled(cols, false); + return new List.filled(cols, false); }); uiState = new List.generate(rows, (int row) { - return new List.filled(cols, CellState.covered); + return new List.filled(cols, CellState.covered); }); // Place the mines. Random random = new Random(); From 49aba0cc0cbfa497fcf2298fdd5b8416d76c0327 Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Sat, 3 Oct 2015 14:08:04 -0700 Subject: [PATCH 17/27] Simplify Scrollable animations Rather than having two objects driving scrolling animations, we now have one object, a Timeline, drive both scrollTo and fling animations. Using Timeline instead of AnimatedSimulation paves the way to removing AnimatedSimulation (which is now used only inside the animation library). Finally, this patch also simplifies (and makes private) _TweenSimulation by using AnimatedValue to do the math. --- .../src/animation/animation_performance.dart | 2 +- .../flutter/lib/src/animation/timeline.dart | 47 +++++++------------ .../flutter/lib/src/widgets/scrollable.dart | 45 +++++------------- 3 files changed, 32 insertions(+), 62 deletions(-) diff --git a/packages/flutter/lib/src/animation/animation_performance.dart b/packages/flutter/lib/src/animation/animation_performance.dart index 9105498dc4..8111754442 100644 --- a/packages/flutter/lib/src/animation/animation_performance.dart +++ b/packages/flutter/lib/src/animation/animation_performance.dart @@ -85,7 +85,7 @@ class AnimationPerformance implements WatchableAnimationPerformance { /// The progress of this performance along the timeline /// /// Note: Setting this value stops the current animation. - double get progress => _timeline.value; + double get progress => _timeline.value.clamp(0.0, 1.0); void set progress(double t) { // TODO(mpcomplete): should this affect |direction|? stop(); diff --git a/packages/flutter/lib/src/animation/timeline.dart b/packages/flutter/lib/src/animation/timeline.dart index 75da47d5f8..29005c75f5 100644 --- a/packages/flutter/lib/src/animation/timeline.dart +++ b/packages/flutter/lib/src/animation/timeline.dart @@ -5,30 +5,28 @@ import 'dart:async'; import 'package:newton/newton.dart'; - +import 'package:sky/src/animation/curves.dart'; +import 'package:sky/src/animation/animated_value.dart'; import 'package:sky/src/animation/animated_simulation.dart'; /// A simulation that linearly varies from [begin] to [end] over [duration] -class TweenSimulation extends Simulation { +class _TweenSimulation extends Simulation { final double _durationInSeconds; + final AnimatedValue _tween; - /// The initial value of the simulation - final double begin; - - /// The terminal value of the simulation - final double end; - - TweenSimulation(Duration duration, this.begin, this.end) : - _durationInSeconds = duration.inMicroseconds / Duration.MICROSECONDS_PER_SECOND { + _TweenSimulation(double begin, double end, Duration duration, Curve curve) + : _durationInSeconds = duration.inMicroseconds / Duration.MICROSECONDS_PER_SECOND, + _tween = new AnimatedValue(begin, end: end, curve: curve) { assert(_durationInSeconds > 0.0); - assert(begin != null && begin >= 0.0 && begin <= 1.0); - assert(end != null && end >= 0.0 && end <= 1.0); + assert(begin != null); + assert(end != null); } double x(double timeInSeconds) { assert(timeInSeconds >= 0.0); - final double t = timeInSeconds / _durationInSeconds; - return t >= 1.0 ? end : begin + (end - begin) * t; + final double t = (timeInSeconds / _durationInSeconds).clamp(0.0, 1.0); + _tween.setProgress(t, Direction.forward); + return _tween.value; } double dx(double timeInSeconds) => 1.0; @@ -45,9 +43,9 @@ class Timeline { AnimatedSimulation _animation; /// The current value of the timeline - double get value => _animation.value.clamp(0.0, 1.0); + double get value => _animation.value; void set value(double newValue) { - assert(newValue != null && newValue >= 0.0 && newValue <= 1.0); + assert(newValue != null); assert(!isAnimating); _animation.value = newValue; } @@ -55,23 +53,14 @@ class Timeline { /// Whether the timeline is currently animating bool get isAnimating => _animation.isAnimating; - Future _start({ - Duration duration, - double begin: 0.0, - double end: 1.0 - }) { - assert(!_animation.isAnimating); - assert(duration > Duration.ZERO); - return _animation.start(new TweenSimulation(duration, begin, end)); - } - /// Animate value of the timeline to the given target over the given duration /// /// Returns a future that resolves when the timeline stops animating, /// typically when the timeline arives at the target value. - Future animateTo(double target, { Duration duration }) { + Future animateTo(double target, { Duration duration, Curve curve: linear }) { assert(duration > Duration.ZERO); - return _start(duration: duration, begin: value, end: target); + assert(!_animation.isAnimating); + return _animation.start(new _TweenSimulation(value, target, duration, curve)); } /// Stop animating the timeline @@ -79,7 +68,7 @@ class Timeline { _animation.stop(); } - // Gives the given simulation control over the timeline + /// Gives the given simulation control over the timeline Future fling(Simulation simulation) { stop(); return _animation.start(simulation); diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart index f5ac199d25..e09fe4f861 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart @@ -51,16 +51,10 @@ abstract class ScrollableState extends State { super.initState(); if (config.initialScrollOffset is double) _scrollOffset = config.initialScrollOffset; - _toEndAnimation = new AnimatedSimulation(_setScrollOffset); - _toOffsetAnimation = new ValueAnimation() - ..addListener(() { - AnimatedValue offset = _toOffsetAnimation.variable; - _setScrollOffset(offset.value); - }); + _animation = new Timeline(_setScrollOffset); } - AnimatedSimulation _toEndAnimation; // See _startToEndAnimation() - ValueAnimation _toOffsetAnimation; // Started by scrollTo() + Timeline _animation; double _scrollOffset = 0.0; double get scrollOffset => _scrollOffset; @@ -106,23 +100,10 @@ abstract class ScrollableState extends State { Widget buildContent(BuildContext context); - Future _startToOffsetAnimation(double newScrollOffset, Duration duration, Curve curve) { - _stopAnimations(); - _toOffsetAnimation - ..variable = new AnimatedValue(scrollOffset, - end: newScrollOffset, - curve: curve - ) - ..progress = 0.0 - ..duration = duration; - return _toOffsetAnimation.play(); - } - - void _stopAnimations() { - if (_toOffsetAnimation.isAnimating) - _toOffsetAnimation.stop(); - if (_toEndAnimation.isAnimating) - _toEndAnimation.stop(); + Future _animateTo(double newScrollOffset, Duration duration, Curve curve) { + _animation.stop(); + _animation.value = scrollOffset; + return _animation.animateTo(newScrollOffset, duration: duration, curve: curve); } bool _scrollOffsetIsInBounds(double offset) { @@ -165,16 +146,16 @@ abstract class ScrollableState extends State { } Future _startToEndAnimation({ double velocity }) { - _stopAnimations(); + _animation.stop(); Simulation simulation = _createSnapSimulation(velocity) ?? _createFlingSimulation(velocity ?? 0.0); if (simulation == null) return new Future.value(); - return _toEndAnimation.start(simulation); + return _animation.fling(simulation); } void dispose() { - _stopAnimations(); + _animation.stop(); super.dispose(); } @@ -193,12 +174,12 @@ abstract class ScrollableState extends State { return new Future.value(); if (duration == null) { - _stopAnimations(); + _animation.stop(); _setScrollOffset(newScrollOffset); return new Future.value(); } - return _startToOffsetAnimation(newScrollOffset, duration, curve); + return _animateTo(newScrollOffset, duration, curve); } Future scrollBy(double scrollDelta, { Duration duration, Curve curve }) { @@ -209,7 +190,7 @@ abstract class ScrollableState extends State { Future fling(Offset velocity) { if (velocity != Offset.zero) return _startToEndAnimation(velocity: _scrollVelocity(velocity)); - if (!_toEndAnimation.isAnimating && (_toOffsetAnimation == null || !_toOffsetAnimation.isAnimating)) + if (!_animation.isAnimating) return settleScrollOffset(); return new Future.value(); } @@ -226,7 +207,7 @@ abstract class ScrollableState extends State { } void _handlePointerDown(_) { - _stopAnimations(); + _animation.stop(); } void _handleDragUpdate(double delta) { From 5cb0010085f8b59122b15df4243268b87e77b8e1 Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Sat, 3 Oct 2015 14:59:48 -0700 Subject: [PATCH 18/27] Remove AnimatedSimulation This patch folds the functionality from AnimatedSimulation into Timeline. --- packages/flutter/lib/animation.dart | 4 +- .../src/animation/animation_performance.dart | 8 +- ...{timeline.dart => simulation_stepper.dart} | 73 +++++++++++++------ .../{animated_simulation.dart => ticker.dart} | 63 +--------------- .../flutter/lib/src/widgets/scrollable.dart | 6 +- packages/flutter/lib/src/widgets/tabs.dart | 1 - 6 files changed, 64 insertions(+), 91 deletions(-) rename packages/flutter/lib/src/animation/{timeline.dart => simulation_stepper.dart} (52%) rename packages/flutter/lib/src/animation/{animated_simulation.dart => ticker.dart} (55%) diff --git a/packages/flutter/lib/animation.dart b/packages/flutter/lib/animation.dart index f35b2538bb..564f8cdae4 100644 --- a/packages/flutter/lib/animation.dart +++ b/packages/flutter/lib/animation.dart @@ -7,7 +7,6 @@ /// This library depends only on core Dart libraries and the `newton` package. library animation; -export 'src/animation/animated_simulation.dart'; export 'src/animation/animated_value.dart'; export 'src/animation/animation_performance.dart'; export 'src/animation/clamped_simulation.dart'; @@ -15,4 +14,5 @@ export 'src/animation/curves.dart'; export 'src/animation/forces.dart'; export 'src/animation/scheduler.dart'; export 'src/animation/scroll_behavior.dart'; -export 'src/animation/timeline.dart'; +export 'src/animation/simulation_stepper.dart'; +export 'src/animation/ticker.dart'; diff --git a/packages/flutter/lib/src/animation/animation_performance.dart b/packages/flutter/lib/src/animation/animation_performance.dart index 8111754442..a3340d4cc9 100644 --- a/packages/flutter/lib/src/animation/animation_performance.dart +++ b/packages/flutter/lib/src/animation/animation_performance.dart @@ -6,7 +6,7 @@ import 'dart:async'; import 'package:sky/src/animation/animated_value.dart'; import 'package:sky/src/animation/forces.dart'; -import 'package:sky/src/animation/timeline.dart'; +import 'package:sky/src/animation/simulation_stepper.dart'; /// The status of an animation enum AnimationStatus { @@ -53,7 +53,7 @@ abstract class WatchableAnimationPerformance { /// progression. class AnimationPerformance implements WatchableAnimationPerformance { AnimationPerformance({ this.duration, double progress }) { - _timeline = new Timeline(_tick); + _timeline = new SimulationStepper(_tick); if (progress != null) _timeline.value = progress.clamp(0.0, 1.0); } @@ -66,7 +66,7 @@ class AnimationPerformance implements WatchableAnimationPerformance { /// The length of time this performance should last Duration duration; - Timeline _timeline; + SimulationStepper _timeline; Direction _direction; /// The direction used to select the current curve @@ -159,7 +159,7 @@ class AnimationPerformance implements WatchableAnimationPerformance { if (force == null) force = kDefaultSpringForce; _direction = velocity < 0.0 ? Direction.reverse : Direction.forward; - return _timeline.fling(force.release(progress, velocity)); + return _timeline.animateWith(force.release(progress, velocity)); } final List _listeners = new List(); diff --git a/packages/flutter/lib/src/animation/timeline.dart b/packages/flutter/lib/src/animation/simulation_stepper.dart similarity index 52% rename from packages/flutter/lib/src/animation/timeline.dart rename to packages/flutter/lib/src/animation/simulation_stepper.dart index 29005c75f5..39ff00c4e8 100644 --- a/packages/flutter/lib/src/animation/timeline.dart +++ b/packages/flutter/lib/src/animation/simulation_stepper.dart @@ -5,23 +5,26 @@ import 'dart:async'; import 'package:newton/newton.dart'; -import 'package:sky/src/animation/curves.dart'; import 'package:sky/src/animation/animated_value.dart'; -import 'package:sky/src/animation/animated_simulation.dart'; +import 'package:sky/src/animation/curves.dart'; +import 'package:sky/src/animation/ticker.dart'; -/// A simulation that linearly varies from [begin] to [end] over [duration] +/// A simulation that varies from [begin] to [end] over [duration] using [curve] +/// +/// This class is an adaptor between the Simulation interface and the +/// AnimatedValue interface. class _TweenSimulation extends Simulation { - final double _durationInSeconds; - final AnimatedValue _tween; - _TweenSimulation(double begin, double end, Duration duration, Curve curve) - : _durationInSeconds = duration.inMicroseconds / Duration.MICROSECONDS_PER_SECOND, + : _durationInSeconds = duration.inMicroseconds.toDouble() / Duration.MICROSECONDS_PER_SECOND, _tween = new AnimatedValue(begin, end: end, curve: curve) { assert(_durationInSeconds > 0.0); assert(begin != null); assert(end != null); } + final double _durationInSeconds; + final AnimatedValue _tween; + double x(double timeInSeconds) { assert(timeInSeconds >= 0.0); final double t = (timeInSeconds / _durationInSeconds).clamp(0.0, 1.0); @@ -34,24 +37,30 @@ class _TweenSimulation extends Simulation { bool isDone(double timeInSeconds) => timeInSeconds > _durationInSeconds; } -/// A timeline for an animation -class Timeline { - Timeline(Function onTick) { - _animation = new AnimatedSimulation(onTick); +typedef TimelineCallback(double value); + +/// Steps a simulation one per frame +class SimulationStepper { + SimulationStepper(TimelineCallback onTick) : _onTick = onTick { + _ticker = new Ticker(_tick); } - AnimatedSimulation _animation; + final TimelineCallback _onTick; + Ticker _ticker; + Simulation _simulation; /// The current value of the timeline - double get value => _animation.value; + double get value => _value; + double _value = 0.0; void set value(double newValue) { assert(newValue != null); assert(!isAnimating); - _animation.value = newValue; + _value = newValue; + _onTick(_value); } /// Whether the timeline is currently animating - bool get isAnimating => _animation.isAnimating; + bool get isAnimating => _ticker.isTicking; /// Animate value of the timeline to the given target over the given duration /// @@ -59,18 +68,38 @@ class Timeline { /// typically when the timeline arives at the target value. Future animateTo(double target, { Duration duration, Curve curve: linear }) { assert(duration > Duration.ZERO); - assert(!_animation.isAnimating); - return _animation.start(new _TweenSimulation(value, target, duration, curve)); + assert(!isAnimating); + return _start(new _TweenSimulation(value, target, duration, curve)); + } + + /// Gives the given simulation control over the timeline + Future animateWith(Simulation simulation) { + stop(); + return _start(simulation); + } + + /// Start ticking the given simulation once per frame + /// + /// Returns a future that resolves when the simulation stops ticking. + Future _start(Simulation simulation) { + assert(simulation != null); + assert(!isAnimating); + _simulation = simulation; + _value = simulation.x(0.0); + return _ticker.start(); } /// Stop animating the timeline void stop() { - _animation.stop(); + _simulation = null; + _ticker.stop(); } - /// Gives the given simulation control over the timeline - Future fling(Simulation simulation) { - stop(); - return _animation.start(simulation); + void _tick(Duration elapsed) { + double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.MICROSECONDS_PER_SECOND; + _value = _simulation.x(elapsedInSeconds); + if (_simulation.isDone(elapsedInSeconds)) + stop(); + _onTick(_value); } } diff --git a/packages/flutter/lib/src/animation/animated_simulation.dart b/packages/flutter/lib/src/animation/ticker.dart similarity index 55% rename from packages/flutter/lib/src/animation/animated_simulation.dart rename to packages/flutter/lib/src/animation/ticker.dart index 32f0a91aa7..6c0dd32eb0 100644 --- a/packages/flutter/lib/src/animation/animated_simulation.dart +++ b/packages/flutter/lib/src/animation/ticker.dart @@ -4,17 +4,16 @@ import 'dart:async'; -import 'package:newton/newton.dart'; import 'package:sky/src/animation/scheduler.dart'; -typedef _TickerCallback(Duration elapsed); +typedef TickerCallback(Duration elapsed); /// Calls its callback once per animation frame class Ticker { /// Constructs a ticker that will call onTick once per frame while running - Ticker(_TickerCallback onTick) : _onTick = onTick; + Ticker(TickerCallback onTick) : _onTick = onTick; - final _TickerCallback _onTick; + final TickerCallback _onTick; Completer _completer; int _animationId; @@ -45,7 +44,7 @@ class Ticker { _animationId = null; } - // We take the _completer into a local variable so that !isTicking + // We take the _completer into a local variable so that isTicking is false // when we actually complete the future (isTicking uses _completer // to determine its state). Completer localCompleter = _completer; @@ -78,57 +77,3 @@ class Ticker { _animationId = scheduler.requestAnimationFrame(_tick); } } - -/// Ticks a simulation once per frame -class AnimatedSimulation { - - AnimatedSimulation(Function onTick) : _onTick = onTick { - _ticker = new Ticker(_tick); - } - - final Function _onTick; - Ticker _ticker; - - Simulation _simulation; - - double _value = 0.0; - /// The current value of the simulation - double get value => _value; - void set value(double newValue) { - assert(!_ticker.isTicking); - _value = newValue; - _onTick(_value); - } - - /// Start ticking the given simulation once per frame - /// - /// Returns a future that resolves when the simulation stops ticking. - Future start(Simulation simulation) { - assert(simulation != null); - assert(!_ticker.isTicking); - _simulation = simulation; - _value = simulation.x(0.0); - return _ticker.start(); - } - - /// Stop ticking the current simulation - void stop() { - _simulation = null; - _ticker.stop(); - } - - /// Whether this object is currently ticking a simulation - bool get isAnimating => _ticker.isTicking; - - void _tick(Duration elapsed) { - - double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.MICROSECONDS_PER_SECOND; - _value = _simulation.x(elapsedInSeconds); - - if (_simulation.isDone(elapsedInSeconds)) - stop(); - - _onTick(_value); - } - -} diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart index e09fe4f861..cf1693a4a1 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart @@ -51,10 +51,10 @@ abstract class ScrollableState extends State { super.initState(); if (config.initialScrollOffset is double) _scrollOffset = config.initialScrollOffset; - _animation = new Timeline(_setScrollOffset); + _animation = new SimulationStepper(_setScrollOffset); } - Timeline _animation; + SimulationStepper _animation; double _scrollOffset = 0.0; double get scrollOffset => _scrollOffset; @@ -151,7 +151,7 @@ abstract class ScrollableState extends State { _createSnapSimulation(velocity) ?? _createFlingSimulation(velocity ?? 0.0); if (simulation == null) return new Future.value(); - return _animation.fling(simulation); + return _animation.animateWith(simulation); } void dispose() { diff --git a/packages/flutter/lib/src/widgets/tabs.dart b/packages/flutter/lib/src/widgets/tabs.dart index c00bc8ba6f..19b9c6748b 100644 --- a/packages/flutter/lib/src/widgets/tabs.dart +++ b/packages/flutter/lib/src/widgets/tabs.dart @@ -13,7 +13,6 @@ import 'package:sky/painting.dart'; import 'package:sky/rendering.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/framework.dart'; -import 'package:sky/src/widgets/gesture_detector.dart'; import 'package:sky/src/widgets/icon.dart'; import 'package:sky/src/widgets/ink_well.dart'; import 'package:sky/src/widgets/scrollable.dart'; From 49c478769839a4f47e6f14455dc7b7ac204fb92c Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Sun, 4 Oct 2015 13:13:39 -0700 Subject: [PATCH 19/27] Convert Drawer to using navigator This patch converts drawer to using the "openDialog" pattern for managing its state. Currently, the drawer entrance and exit animation aren't integrated with the navigator's animation system because the drawer's animations can be stopped and reversed, which the navigator can't yet understand. That means dismissing the drawer via the system back button causes the drawer to be removed instanteously. Fixes #715 Fixes #1187 --- examples/fitness/lib/feed.dart | 36 +---- examples/stocks/lib/stock_home.dart | 35 +---- examples/widgets/card_collection.dart | 142 ++++++++---------- examples/widgets/pageable_list.dart | 68 +++------ .../src/animation/animation_performance.dart | 9 -- packages/flutter/lib/src/widgets/drawer.dart | 114 +++++++------- 6 files changed, 159 insertions(+), 245 deletions(-) diff --git a/examples/fitness/lib/feed.dart b/examples/fitness/lib/feed.dart index a3c0275f7e..221b7056f8 100644 --- a/examples/fitness/lib/feed.dart +++ b/examples/fitness/lib/feed.dart @@ -66,19 +66,14 @@ class FeedFragmentState extends State { void _handleFitnessModeChange(FitnessMode value) { setState(() { _fitnessMode = value; - _drawerShowing = false; }); + config.navigator.pop(); } - Drawer buildDrawer() { - if (_drawerStatus == AnimationStatus.dismissed) - return null; - return new Drawer( - showing: _drawerShowing, - level: 3, - onDismissed: _handleDrawerDismissed, + void _showDrawer() { + showDrawer( navigator: config.navigator, - children: [ + child: new Block([ new DrawerHeader(child: new Text('Fitness')), new DrawerItem( icon: 'action/view_list', @@ -98,26 +93,10 @@ class FeedFragmentState extends State { new DrawerItem( icon: 'action/help', child: new Text('Help & Feedback')) - ] + ]) ); } - bool _drawerShowing = false; - AnimationStatus _drawerStatus = AnimationStatus.dismissed; - - void _handleOpenDrawer() { - setState(() { - _drawerShowing = true; - _drawerStatus = AnimationStatus.forward; - }); - } - - void _handleDrawerDismissed() { - setState(() { - _drawerStatus = AnimationStatus.dismissed; - }); - } - void _handleShowSettings() { config.navigator.pop(); config.navigator.pushNamed('/settings'); @@ -135,7 +114,7 @@ class FeedFragmentState extends State { return new ToolBar( left: new IconButton( icon: "navigation/menu", - onPressed: _handleOpenDrawer), + onPressed: _showDrawer), center: new Text(fitnessModeTitle) ); } @@ -262,8 +241,7 @@ class FeedFragmentState extends State { toolbar: buildToolBar(), body: buildBody(), snackBar: buildSnackBar(), - floatingActionButton: buildFloatingActionButton(), - drawer: buildDrawer() + floatingActionButton: buildFloatingActionButton() ); } } diff --git a/examples/stocks/lib/stock_home.dart b/examples/stocks/lib/stock_home.dart index 523b668468..80e399ce8b 100644 --- a/examples/stocks/lib/stock_home.dart +++ b/examples/stocks/lib/stock_home.dart @@ -56,22 +56,6 @@ class StockHomeState extends State { }); } - bool _drawerShowing = false; - AnimationStatus _drawerStatus = AnimationStatus.dismissed; - - void _handleOpenDrawer() { - setState(() { - _drawerShowing = true; - _drawerStatus = AnimationStatus.forward; - }); - } - - void _handleDrawerDismissed() { - setState(() { - _drawerStatus = AnimationStatus.dismissed; - }); - } - bool _autorefresh = false; void _handleAutorefreshChanged(bool value) { setState(() { @@ -91,16 +75,10 @@ class StockHomeState extends State { ); } - Drawer buildDrawer() { - if (_drawerStatus == AnimationStatus.dismissed) - return null; - assert(_drawerShowing); // TODO(mpcomplete): this is always true - return new Drawer( - level: 3, - showing: _drawerShowing, - onDismissed: _handleDrawerDismissed, + void _showDrawer() { + showDrawer( navigator: config.navigator, - children: [ + child: new Block([ new DrawerHeader(child: new Text('Stocks')), new DrawerItem( icon: 'action/assessment', @@ -141,7 +119,7 @@ class StockHomeState extends State { new DrawerItem( icon: 'action/help', child: new Text('Help & Feedback')) - ] + ]) ); } @@ -154,7 +132,7 @@ class StockHomeState extends State { return new ToolBar( left: new IconButton( icon: "navigation/menu", - onPressed: _handleOpenDrawer + onPressed: _showDrawer ), center: new Text('Stocks'), right: [ @@ -276,8 +254,7 @@ class StockHomeState extends State { toolbar: _isSearching ? buildSearchBar() : buildToolBar(), body: buildTabNavigator(), snackBar: buildSnackBar(), - floatingActionButton: buildFloatingActionButton(), - drawer: buildDrawer() + floatingActionButton: buildFloatingActionButton() ); } } diff --git a/examples/widgets/card_collection.dart b/examples/widgets/card_collection.dart index ba5a53c975..0e7a06c4f5 100644 --- a/examples/widgets/card_collection.dart +++ b/examples/widgets/card_collection.dart @@ -4,7 +4,6 @@ import 'dart:sky' as sky; -import 'package:sky/animation.dart'; import 'package:sky/material.dart'; import 'package:sky/painting.dart'; import 'package:sky/widgets.dart'; @@ -18,11 +17,15 @@ class CardModel { Key get key => new ObjectKey(this); } -class CardCollectionApp extends StatefulComponent { - CardCollectionAppState createState() => new CardCollectionAppState(); +class CardCollection extends StatefulComponent { + CardCollection({ this.navigator }); + + final NavigatorState navigator; + + CardCollectionState createState() => new CardCollectionState(); } -class CardCollectionAppState extends State { +class CardCollectionState extends State { static const TextStyle cardLabelStyle = const TextStyle(color: Colors.white, fontSize: 18.0, fontWeight: bold); @@ -37,9 +40,7 @@ class CardCollectionAppState extends State { DismissDirection _dismissDirection = DismissDirection.horizontal; bool _snapToCenter = false; bool _fixedSizeCards = false; - bool _drawerShowing = false; bool _sunshine = false; - AnimationStatus _drawerStatus = AnimationStatus.dismissed; InvalidatorCallback _invalidator; Size _cardCollectionSize = new Size(200.0, 200.0); @@ -114,17 +115,23 @@ class CardCollectionAppState extends State { } } - void _handleOpenDrawer() { - setState(() { - _drawerShowing = true; - _drawerStatus = AnimationStatus.forward; - }); - } - - void _handleDrawerDismissed() { - setState(() { - _drawerStatus = AnimationStatus.dismissed; - }); + void _showDrawer() { + showDrawer( + navigator: config.navigator, + child: new IconTheme( + data: const IconThemeData(color: IconThemeColor.black), + child: new Block([ + new DrawerHeader(child: new Text('Options')), + buildDrawerCheckbox("Snap fling scrolls to center", _snapToCenter, _toggleSnapToCenter), + buildDrawerCheckbox("Fixed size cards", _fixedSizeCards, _toggleFixedSizeCards), + buildDrawerCheckbox("Let the sun shine", _sunshine, _toggleSunshine), + new DrawerDivider(), + buildDrawerRadioItem(DismissDirection.horizontal, 'action/code'), + buildDrawerRadioItem(DismissDirection.left, 'navigation/arrow_back'), + buildDrawerRadioItem(DismissDirection.right, 'navigation/arrow_forward'), + ]) + ) + ); } String _dismissDirectionText(DismissDirection direction) { @@ -151,65 +158,41 @@ class CardCollectionAppState extends State { }); } - _changeDismissDirection(DismissDirection newDismissDirection) { + void _changeDismissDirection(DismissDirection newDismissDirection) { setState(() { _dismissDirection = newDismissDirection; - _drawerStatus = AnimationStatus.dismissed; }); + config.navigator.pop(); } - Widget buildDrawer() { - if (_drawerStatus == AnimationStatus.dismissed) - return null; + Widget buildDrawerCheckbox(String label, bool value, Function callback) { + return new DrawerItem( + onPressed: callback, + child: new Row([ + new Flexible(child: new Text(label)), + new Checkbox(value: value, onChanged: (_) { callback(); }) + ]) + ); + } - Widget buildDrawerCheckbox(String label, bool value, Function callback) { - return new DrawerItem( - onPressed: callback, - child: new Row([ - new Flexible(child: new Text(label)), - new Checkbox(value: value, onChanged: (_) { callback(); }) - ]) - ); - } - - Widget buildDrawerRadioItem(DismissDirection direction, String icon) { - return new DrawerItem( - icon: icon, - onPressed: () { _changeDismissDirection(direction); }, - child: new Row([ - new Flexible(child: new Text(_dismissDirectionText(direction))), - new Radio( - value: direction, - onChanged: _changeDismissDirection, - groupValue: _dismissDirection - ) - ]) - ); - } - - return new IconTheme( - data: const IconThemeData(color: IconThemeColor.black), - child: new Drawer( - level: 3, - showing: _drawerShowing, - onDismissed: _handleDrawerDismissed, - children: [ - new DrawerHeader(child: new Text('Options')), - buildDrawerCheckbox("Snap fling scrolls to center", _snapToCenter, _toggleSnapToCenter), - buildDrawerCheckbox("Fixed size cards", _fixedSizeCards, _toggleFixedSizeCards), - buildDrawerCheckbox("Let the sun shine", _sunshine, _toggleSunshine), - new DrawerDivider(), - buildDrawerRadioItem(DismissDirection.horizontal, 'action/code'), - buildDrawerRadioItem(DismissDirection.left, 'navigation/arrow_back'), - buildDrawerRadioItem(DismissDirection.right, 'navigation/arrow_forward'), - ] - ) + Widget buildDrawerRadioItem(DismissDirection direction, String icon) { + return new DrawerItem( + icon: icon, + onPressed: () { _changeDismissDirection(direction); }, + child: new Row([ + new Flexible(child: new Text(_dismissDirectionText(direction))), + new Radio( + value: direction, + onChanged: _changeDismissDirection, + groupValue: _dismissDirection + ) + ]) ); } Widget buildToolBar() { return new ToolBar( - left: new IconButton(icon: "navigation/menu", onPressed: _handleOpenDrawer), + left: new IconButton(icon: "navigation/menu", onPressed: _showDrawer), center: new Text('Swipe Away'), right: [ new Text(_dismissDirectionText(_dismissDirection)) @@ -358,24 +341,23 @@ class CardCollectionAppState extends State { body = new Stack([body, indicator]); } - return new Theme( - data: new ThemeData( - brightness: ThemeBrightness.light, - primarySwatch: Colors.blue, - accentColor: Colors.redAccent[200] - ), - child: new Title( - title: 'Cards', - child: new Scaffold( - toolbar: buildToolBar(), - drawer: buildDrawer(), - body: body - ) - ) + return new Scaffold( + toolbar: buildToolBar(), + body: body ); } } void main() { - runApp(new CardCollectionApp()); + runApp(new App( + title: 'Cards', + theme: new ThemeData( + brightness: ThemeBrightness.light, + primarySwatch: Colors.blue, + accentColor: Colors.redAccent[200] + ), + routes: { + '/': (NavigatorState navigator, Route route) => new CardCollection(navigator: navigator), + } + )); } diff --git a/examples/widgets/pageable_list.dart b/examples/widgets/pageable_list.dart index e9c079cffe..c00da4b488 100644 --- a/examples/widgets/pageable_list.dart +++ b/examples/widgets/pageable_list.dart @@ -2,7 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'package:sky/animation.dart'; import 'package:sky/material.dart'; import 'package:sky/painting.dart'; import 'package:sky/widgets.dart'; @@ -17,6 +16,10 @@ class CardModel { } class PageableListApp extends StatefulComponent { + PageableListApp({ this.navigator }); + + final NavigatorState navigator; + PageableListAppState createState() => new PageableListAppState(); } @@ -85,31 +88,10 @@ class PageableListAppState extends State { }); } - bool _drawerShowing = false; - AnimationStatus _drawerStatus = AnimationStatus.dismissed; - - void _handleOpenDrawer() { - setState(() { - _drawerShowing = true; - _drawerStatus = AnimationStatus.forward; - }); - } - - void _handleDrawerDismissed() { - setState(() { - _drawerStatus = AnimationStatus.dismissed; - }); - } - - Drawer buildDrawer() { - if (_drawerStatus == AnimationStatus.dismissed) - return null; - - return new Drawer( - level: 3, - showing: _drawerShowing, - onDismissed: _handleDrawerDismissed, - children: [ + void _showDrawer() { + showDrawer( + navigator: config.navigator, + child: new Block([ new DrawerHeader(child: new Text('Options')), new DrawerItem( icon: 'navigation/more_horiz', @@ -130,14 +112,13 @@ class PageableListAppState extends State { new Checkbox(value: itemsWrap) ]) ) - ] + ]) ); - } Widget buildToolBar() { return new ToolBar( - left: new IconButton(icon: "navigation/menu", onPressed: _handleOpenDrawer), + left: new IconButton(icon: "navigation/menu", onPressed: _showDrawer), center: new Text('PageableList'), right: [ new Text(scrollDirection == ScrollDirection.horizontal ? "horizontal" : "vertical") @@ -167,25 +148,24 @@ class PageableListAppState extends State { Widget build(BuildContext context) { return new IconTheme( data: const IconThemeData(color: IconThemeColor.white), - child: new Theme( - data: new ThemeData( - brightness: ThemeBrightness.light, - primarySwatch: Colors.blue, - accentColor: Colors.redAccent[200] - ), - child: new Title( - title: 'PageableList', - child: new Scaffold( - drawer: buildDrawer(), - toolbar: buildToolBar(), - body: buildBody(context) - ) - ) + child: new Scaffold( + toolbar: buildToolBar(), + body: buildBody(context) ) ); } } void main() { - runApp(new PageableListApp()); + runApp(new App( + title: 'PageableList', + theme: new ThemeData( + brightness: ThemeBrightness.light, + primarySwatch: Colors.blue, + accentColor: Colors.redAccent[200] + ), + routes: { + '/': (NavigatorState navigator, Route route) => new PageableListApp(navigator: navigator), + } + )); } diff --git a/packages/flutter/lib/src/animation/animation_performance.dart b/packages/flutter/lib/src/animation/animation_performance.dart index 8111754442..6eb2ee8254 100644 --- a/packages/flutter/lib/src/animation/animation_performance.dart +++ b/packages/flutter/lib/src/animation/animation_performance.dart @@ -79,9 +79,6 @@ class AnimationPerformance implements WatchableAnimationPerformance { /// If non-null, animate with this timing instead of a linear timing AnimationTiming timing; - /// If non-null, animate with this force instead of a zero-to-one timeline. - Force attachedForce; - /// The progress of this performance along the timeline /// /// Note: Setting this value stops the current animation. @@ -136,12 +133,6 @@ class AnimationPerformance implements WatchableAnimationPerformance { /// Start running this animation in the most recently direction Future resume() { - if (attachedForce != null) { - return fling( - velocity: _direction == Direction.forward ? 1.0 : -1.0, - force: attachedForce - ); - } return _animateTo(_direction == Direction.forward ? 1.0 : 0.0); } diff --git a/packages/flutter/lib/src/widgets/drawer.dart b/packages/flutter/lib/src/widgets/drawer.dart index edee253af6..5d980b391e 100644 --- a/packages/flutter/lib/src/widgets/drawer.dart +++ b/packages/flutter/lib/src/widgets/drawer.dart @@ -14,6 +14,7 @@ import 'package:sky/src/widgets/navigator.dart'; import 'package:sky/src/widgets/scrollable.dart'; import 'package:sky/src/widgets/theme.dart'; import 'package:sky/src/widgets/transitions.dart'; +import 'package:sky/src/widgets/focus.dart'; // TODO(eseidel): Draw width should vary based on device size: // http://www.google.com/design/spec/layout/structure.html#structure-side-nav @@ -36,22 +37,16 @@ const Duration _kThemeChangeDuration = const Duration(milliseconds: 200); const Point _kOpenPosition = Point.origin; const Point _kClosedPosition = const Point(-_kWidth, 0.0); -typedef void DrawerDismissedCallback(); - class Drawer extends StatefulComponent { Drawer({ Key key, - this.children, - this.showing: false, - this.level: 0, - this.onDismissed, + this.child, + this.level: 3, this.navigator }) : super(key: key); - final List children; - final bool showing; + final Widget child; final int level; - final DrawerDismissedCallback onDismissed; final NavigatorState navigator; DrawerState createState() => new DrawerState(); @@ -60,44 +55,24 @@ class Drawer extends StatefulComponent { class DrawerState extends State { void initState() { super.initState(); - _performance = new AnimationPerformance(duration: _kBaseSettleDuration); - _performance.addStatusListener((AnimationStatus status) { - if (status == AnimationStatus.dismissed) - _handleDismissed(); - }); - // Use a spring force for animating the drawer. We can't use curves for - // this because we need a linear curve in order to track the user's finger - // while dragging. - _performance.attachedForce = kDefaultSpringForce; - if (config.navigator != null) { - // TODO(ianh): This is crazy. We should convert drawer to use a pattern like openDialog(). - // https://github.com/domokit/sky_engine/pull/1186 - scheduleMicrotask(() { - config.navigator.pushState(this, (_) => _performance.reverse()); + _performance = new AnimationPerformance(duration: _kBaseSettleDuration) + ..addStatusListener((AnimationStatus status) { + if (status == AnimationStatus.dismissed) + config.navigator.pop(); }); - } - _performance.play(_direction); + _open(); } AnimationPerformance _performance; - Direction get _direction => config.showing ? Direction.forward : Direction.reverse; - - void didUpdateConfig(Drawer oldConfig) { - if (config.showing != oldConfig.showing) - _performance.play(_direction); - } - Widget build(BuildContext context) { - var mask = new GestureDetector( + Widget mask = new GestureDetector( + onTap: _close, child: new ColorTransition( performance: _performance.view, - color: new AnimatedColorValue(Colors.transparent, end: const Color(0x7F000000)), + color: new AnimatedColorValue(Colors.transparent, end: Colors.black54), child: new Container() - ), - onTap: () { - _performance.reverse(); - } + ) ); Widget content = new SlideTransition( @@ -105,12 +80,12 @@ class DrawerState extends State { position: new AnimatedValue(_kClosedPosition, end: _kOpenPosition), child: new AnimatedContainer( curve: ease, - duration: const Duration(milliseconds: 200), + duration: _kThemeChangeDuration, decoration: new BoxDecoration( backgroundColor: Theme.of(context).canvasColor, boxShadow: shadows[config.level]), width: _kWidth, - child: new Block(config.children) + child: config.child ) ); @@ -118,33 +93,64 @@ class DrawerState extends State { onHorizontalDragStart: _performance.stop, onHorizontalDragUpdate: _handleDragUpdate, onHorizontalDragEnd: _handleDragEnd, - child: new Stack([ mask, content ]) + child: new Stack([ + mask, + new Positioned( + top: 0.0, + left: 0.0, + bottom: 0.0, + child: content + ) + ]) ); } - void _handleDismissed() { - if (config.navigator != null && - config.navigator.currentRoute is StateRoute && - (config.navigator.currentRoute as StateRoute).owner == this) // TODO(ianh): remove cast once analyzer is cleverer - config.navigator.pop(); - if (config.onDismissed != null) - config.onDismissed(); - } - bool get _isMostlyClosed => _performance.progress < 0.5; - void _settle() { _isMostlyClosed ? _performance.reverse() : _performance.play(); } - void _handleDragUpdate(double delta) { _performance.progress += delta / _kWidth; } + void _open() { + _performance.fling(velocity: 1.0); + } + + void _close() { + _performance.fling(velocity: -1.0); + } + void _handleDragEnd(Offset velocity) { if (velocity.dx.abs() >= _kMinFlingVelocity) { _performance.fling(velocity: velocity.dx * _kFlingVelocityScale); + } else if (_isMostlyClosed) { + _close(); } else { - _settle(); + _open(); } } - +} + +class DrawerRoute extends Route { + DrawerRoute({ this.child, this.level }); + + final Widget child; + final int level; + + bool get opaque => false; + + Widget build(NavigatorState navigator, WatchableAnimationPerformance nextRoutePerformance) { + return new Focus( + key: new GlobalObjectKey(this), + autofocus: true, + child: new Drawer( + child: child, + level: level, + navigator: navigator + ) + ); + } +} + +void showDrawer({ NavigatorState navigator, Widget child, int level: 3 }) { + navigator.push(new DrawerRoute(child: child, level: level)); } From 3e916982c41d616e0a6dec4a3864e73303a02aa2 Mon Sep 17 00:00:00 2001 From: Chinmay Garde Date: Fri, 2 Oct 2015 14:21:00 -0700 Subject: [PATCH 20/27] Allow displaying compositor statistics via a widget Example: import 'package:sky/widgets.dart'; void main() => runApp(new Center(child: new StatisticsOverlay.allEnabled())); --- packages/flutter/lib/src/rendering/layer.dart | 23 +++++++ .../flutter/lib/src/rendering/object.dart | 9 +++ .../lib/src/rendering/statistics_box.dart | 47 +++++++++++++++ .../lib/src/widgets/statistics_overlay.dart | 60 +++++++++++++++++++ packages/flutter/lib/widgets.dart | 1 + 5 files changed, 140 insertions(+) create mode 100644 packages/flutter/lib/src/rendering/statistics_box.dart create mode 100644 packages/flutter/lib/src/widgets/statistics_overlay.dart diff --git a/packages/flutter/lib/src/rendering/layer.dart b/packages/flutter/lib/src/rendering/layer.dart index 49d1e3df58..1e22221d39 100644 --- a/packages/flutter/lib/src/rendering/layer.dart +++ b/packages/flutter/lib/src/rendering/layer.dart @@ -87,6 +87,29 @@ class PictureLayer extends Layer { } +/// A layer that indicates to the compositor that it should display +/// certain statistics within it +class StatisticsLayer extends Layer { + StatisticsLayer({ + Offset offset: Offset.zero, + this.paintBounds, + this.optionsMask + }) : super(offset: offset); + + /// The rectangle in this layer's coodinate system that bounds the recording + Rect paintBounds; + + /// A mask specifying the statistics to display + int optionsMask; + + void addToScene(sky.SceneBuilder builder, Offset layerOffset) { + assert(optionsMask != null); + builder.addStatistics(optionsMask, paintBounds.shift(layerOffset)); + } + +} + + /// A composited layer that has a list of children class ContainerLayer extends Layer { ContainerLayer({ Offset offset: Offset.zero }) : super(offset: offset); diff --git a/packages/flutter/lib/src/rendering/object.dart b/packages/flutter/lib/src/rendering/object.dart index 07cee15b5f..0daaab2bef 100644 --- a/packages/flutter/lib/src/rendering/object.dart +++ b/packages/flutter/lib/src/rendering/object.dart @@ -130,6 +130,15 @@ class PaintingContext { } } + void paintStatistics(int optionsMask, Offset offset, Size size) { + StatisticsLayer statsLayer = new StatisticsLayer( + offset: offset, + paintBounds: new Rect.fromLTWH(0.0, 0.0, size.width, size.height), + optionsMask : optionsMask + ); + _containerLayer.append(statsLayer); + } + // Below we have various variants of the paintChild() method, which // do additional work, such as clipping or transforming, at the same // time as painting the children. diff --git a/packages/flutter/lib/src/rendering/statistics_box.dart b/packages/flutter/lib/src/rendering/statistics_box.dart new file mode 100644 index 0000000000..98780ea889 --- /dev/null +++ b/packages/flutter/lib/src/rendering/statistics_box.dart @@ -0,0 +1,47 @@ +// 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:sky/src/rendering/box.dart'; +import 'package:sky/src/rendering/object.dart'; + +class StatisticsBox extends RenderBox { + + StatisticsBox({int optionsMask: 0}) : _optionsMask = optionsMask; + + int _optionsMask; + int get optionsMask => _optionsMask; + void set optionsMask (int mask) { + if (mask == _optionsMask) { + return; + } + _optionsMask = mask; + markNeedsPaint(); + } + + bool get sizedByParent => true; + + double getMinIntrinsicWidth(BoxConstraints constraints) { + return constraints.minWidth; + } + + double getMaxIntrinsicWidth(BoxConstraints constraints) { + return constraints.maxWidth; + } + + double getMinIntrinsicHeight(BoxConstraints constraints) { + return constraints.minHeight; + } + + double getMaxIntrinsicHeight(BoxConstraints constraints) { + return constraints.maxHeight; + } + + void performResize() { + size = constraints.constrain(Size.infinite); + } + + void paint(PaintingContext context, Offset offset) { + context.paintStatistics(optionsMask, offset, size); + } +} diff --git a/packages/flutter/lib/src/widgets/statistics_overlay.dart b/packages/flutter/lib/src/widgets/statistics_overlay.dart new file mode 100644 index 0000000000..e3d62fc556 --- /dev/null +++ b/packages/flutter/lib/src/widgets/statistics_overlay.dart @@ -0,0 +1,60 @@ +// 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:sky/src/widgets/framework.dart'; +import 'package:sky/src/rendering/statistics_box.dart'; + +/// The options that control whether the statistics overlay displays certain +/// aspects of the compositor +enum StatisticsOption { + /// Display the frame time and FPS of the last frame rendered. This field is + /// updated every frame. + /// + /// This is the time spent by the rasterizer as it tries + /// to convert the layer tree obtained from the widgets into OpenGL commands + /// and tries to flush them onto the screen. When the total time taken by this + /// step exceeds the frame slice, a frame is lost. + displayRasterizerStatistics, + /// Display the rasterizer frame times as they change over a set period of + /// time in the form of a graph. The y axis of the graph denotes the total + /// time spent by the rasterizer as a fraction of the total frame slice. When + /// the bar turns red, a frame is lost. + visualizeRasterizerStatistics, + /// Display the frame time and FPS at which the interface can construct a + /// layer tree for the rasterizer (whose behavior is described above) to + /// consume. + /// + /// This involves all layout, animations, etc. When the total time taken by + /// this step exceeds the frame slice, a frame is lost. + displayEngineStatistics, + /// Display the engine frame times as they change over a set period of time + /// in the form of a graph. The y axis of the graph denotes the total time + /// spent by the eninge as a fraction of the total frame slice. When the bar + /// turns red, a frame is lost. + visualizeEngineStatistics, +} + +class StatisticsOverlay extends LeafRenderObjectWidget { + + /// Create a statistics overlay that only displays specific statistics. The + /// mask is created by shifting 1 by the index of the specific StatisticOption + /// to enable. + StatisticsOverlay({ this.optionsMask, Key key }) : super(key: key); + + /// Create a statistics overaly that displays all available statistics + StatisticsOverlay.allEnabled({ Key key }) : super(key: key), optionsMask = ( + 1 << StatisticsOption.displayRasterizerStatistics.index | + 1 << StatisticsOption.visualizeRasterizerStatistics.index | + 1 << StatisticsOption.displayEngineStatistics.index | + 1 << StatisticsOption.visualizeEngineStatistics.index + ); + + final int optionsMask; + + StatisticsBox createRenderObject() => new StatisticsBox(optionsMask: optionsMask); + + void updateRenderObject(StatisticsBox renderObject, RenderObjectWidget oldWidget) { + renderObject.optionsMask = optionsMask; + } +} diff --git a/packages/flutter/lib/widgets.dart b/packages/flutter/lib/widgets.dart index 3e7f257d57..28e2553441 100644 --- a/packages/flutter/lib/widgets.dart +++ b/packages/flutter/lib/widgets.dart @@ -43,6 +43,7 @@ export 'src/widgets/radio.dart'; export 'src/widgets/raised_button.dart'; export 'src/widgets/scaffold.dart'; export 'src/widgets/scrollable.dart'; +export 'src/widgets/statistics_overlay.dart'; export 'src/widgets/snack_bar.dart'; export 'src/widgets/switch.dart'; export 'src/widgets/tabs.dart'; From 3f82552d22d20c72ed090c87c08864a7993d6108 Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Mon, 5 Oct 2015 10:45:51 -0700 Subject: [PATCH 21/27] Improves names of animation classes Fixes #1170 --- examples/fitness/lib/feed.dart | 8 +- examples/stocks/lib/stock_home.dart | 8 +- examples/widgets/progress_indicator.dart | 16 ++-- packages/flutter/lib/animation.dart | 2 +- .../lib/src/animation/animated_value.dart | 48 +++------- ...tion_performance.dart => performance.dart} | 94 +++++++++---------- .../lib/src/animation/simulation_stepper.dart | 2 +- .../lib/src/painting/radial_reaction.dart | 8 +- .../flutter/lib/src/rendering/toggleable.dart | 8 +- .../lib/src/widgets/animated_component.dart | 14 +-- .../lib/src/widgets/animated_container.dart | 6 +- packages/flutter/lib/src/widgets/dialog.dart | 2 +- .../flutter/lib/src/widgets/dismissable.dart | 14 +-- .../flutter/lib/src/widgets/drag_target.dart | 2 +- packages/flutter/lib/src/widgets/drawer.dart | 10 +- .../flutter/lib/src/widgets/ink_well.dart | 4 +- .../flutter/lib/src/widgets/navigator.dart | 28 +++--- .../flutter/lib/src/widgets/popup_menu.dart | 8 +- .../lib/src/widgets/progress_indicator.dart | 8 +- .../flutter/lib/src/widgets/snack_bar.dart | 2 +- packages/flutter/lib/src/widgets/tabs.dart | 8 +- .../flutter/lib/src/widgets/transitions.dart | 16 ++-- 22 files changed, 149 insertions(+), 167 deletions(-) rename packages/flutter/lib/src/animation/{animation_performance.dart => performance.dart} (66%) diff --git a/examples/fitness/lib/feed.dart b/examples/fitness/lib/feed.dart index 221b7056f8..b7555f92e6 100644 --- a/examples/fitness/lib/feed.dart +++ b/examples/fitness/lib/feed.dart @@ -60,7 +60,7 @@ class FeedFragment extends StatefulComponent { class FeedFragmentState extends State { FitnessMode _fitnessMode = FitnessMode.feed; - AnimationStatus _snackBarStatus = AnimationStatus.dismissed; + PerformanceStatus _snackBarStatus = PerformanceStatus.dismissed; bool _isShowingSnackBar = false; void _handleFitnessModeChange(FitnessMode value) { @@ -126,7 +126,7 @@ class FeedFragmentState extends State { setState(() { _undoItem = item; _isShowingSnackBar = true; - _snackBarStatus = AnimationStatus.forward; + _snackBarStatus = PerformanceStatus.forward; }); } @@ -207,13 +207,13 @@ class FeedFragmentState extends State { } Widget buildSnackBar() { - if (_snackBarStatus == AnimationStatus.dismissed) + if (_snackBarStatus == PerformanceStatus.dismissed) return null; return new SnackBar( showing: _isShowingSnackBar, content: new Text("Item deleted."), actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)], - onDismissed: () { setState(() { _snackBarStatus = AnimationStatus.dismissed; }); } + onDismissed: () { setState(() { _snackBarStatus = PerformanceStatus.dismissed; }); } ); } diff --git a/examples/stocks/lib/stock_home.dart b/examples/stocks/lib/stock_home.dart index 80e399ce8b..091e379f67 100644 --- a/examples/stocks/lib/stock_home.dart +++ b/examples/stocks/lib/stock_home.dart @@ -25,7 +25,7 @@ class StockHomeState extends State { bool _isSearching = false; String _searchQuery; - AnimationStatus _snackBarStatus = AnimationStatus.dismissed; + PerformanceStatus _snackBarStatus = PerformanceStatus.dismissed; bool _isSnackBarShowing = false; void _handleSearchBegin() { @@ -224,20 +224,20 @@ class StockHomeState extends State { GlobalKey snackBarKey = new GlobalKey(label: 'snackbar'); Widget buildSnackBar() { - if (_snackBarStatus == AnimationStatus.dismissed) + if (_snackBarStatus == PerformanceStatus.dismissed) return null; return new SnackBar( showing: _isSnackBarShowing, content: new Text("Stock purchased!"), actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)], - onDismissed: () { setState(() { _snackBarStatus = AnimationStatus.dismissed; }); } + onDismissed: () { setState(() { _snackBarStatus = PerformanceStatus.dismissed; }); } ); } void _handleStockPurchased() { setState(() { _isSnackBarShowing = true; - _snackBarStatus = AnimationStatus.forward; + _snackBarStatus = PerformanceStatus.forward; }); } diff --git a/examples/widgets/progress_indicator.dart b/examples/widgets/progress_indicator.dart index 1024d37138..e769784b46 100644 --- a/examples/widgets/progress_indicator.dart +++ b/examples/widgets/progress_indicator.dart @@ -13,7 +13,7 @@ class ProgressIndicatorApp extends StatefulComponent { class ProgressIndicatorAppState extends State { void initState() { super.initState(); - valueAnimation = new ValueAnimation() + valueAnimation = new ValuePerformance() ..duration = const Duration(milliseconds: 1500) ..variable = new AnimatedValue( 0.0, @@ -22,15 +22,15 @@ class ProgressIndicatorAppState extends State { reverseCurve: ease, interval: new Interval(0.0, 0.9) ); - valueAnimation.addStatusListener((AnimationStatus status) { - if (status == AnimationStatus.dismissed || status == AnimationStatus.completed) + valueAnimation.addStatusListener((PerformanceStatus status) { + if (status == PerformanceStatus.dismissed || status == PerformanceStatus.completed) reverseValueAnimationDirection(); }); valueAnimation.play(valueAnimationDirection); } - ValueAnimation valueAnimation; - Direction valueAnimationDirection = Direction.forward; + ValuePerformance valueAnimation; + AnimationDirection valueAnimationDirection = AnimationDirection.forward; void handleTap() { setState(() { @@ -43,9 +43,9 @@ class ProgressIndicatorAppState extends State { } void reverseValueAnimationDirection() { - valueAnimationDirection = (valueAnimationDirection == Direction.forward) - ? Direction.reverse - : Direction.forward; + valueAnimationDirection = (valueAnimationDirection == AnimationDirection.forward) + ? AnimationDirection.reverse + : AnimationDirection.forward; valueAnimation.play(valueAnimationDirection); } diff --git a/packages/flutter/lib/animation.dart b/packages/flutter/lib/animation.dart index 564f8cdae4..e9c75373b7 100644 --- a/packages/flutter/lib/animation.dart +++ b/packages/flutter/lib/animation.dart @@ -8,7 +8,7 @@ library animation; export 'src/animation/animated_value.dart'; -export 'src/animation/animation_performance.dart'; +export 'src/animation/performance.dart'; export 'src/animation/clamped_simulation.dart'; export 'src/animation/curves.dart'; export 'src/animation/forces.dart'; diff --git a/packages/flutter/lib/src/animation/animated_value.dart b/packages/flutter/lib/src/animation/animated_value.dart index 732c8f2855..f42c4b8910 100644 --- a/packages/flutter/lib/src/animation/animated_value.dart +++ b/packages/flutter/lib/src/animation/animated_value.dart @@ -7,7 +7,7 @@ import 'dart:sky' show Color, Rect; import 'package:sky/src/animation/curves.dart'; /// The direction in which an animation is running -enum Direction { +enum AnimationDirection { /// The animation is running from beginning to end forward, @@ -17,11 +17,11 @@ enum Direction { /// An interface describing a variable that changes as an animation progresses. /// -/// AnimatedVariables, by convention, must be cheap to create. This allows them to be used in -/// build functions in Widgets. -abstract class AnimatedVariable { +/// Animatable objects, by convention, must be cheap to create. This allows them +/// to be used in build functions in Widgets. +abstract class Animatable { /// Update the variable to a given time in an animation that is running in the given direction - void setProgress(double t, Direction direction); + void setProgress(double t, AnimationDirection direction); String toString(); } @@ -53,7 +53,7 @@ class AnimationTiming { Curve reverseCurve; /// Applies this timing to the given animation clock value in the given direction - double transform(double t, Direction direction) { + double transform(double t, AnimationDirection direction) { Interval interval = _getInterval(direction); if (interval != null) t = interval.transform(t); @@ -65,19 +65,19 @@ class AnimationTiming { return _applyCurve(t, direction); } - Interval _getInterval(Direction direction) { - if (direction == Direction.forward || reverseInterval == null) + Interval _getInterval(AnimationDirection direction) { + if (direction == AnimationDirection.forward || reverseInterval == null) return interval; return reverseInterval; } - Curve _getCurve(Direction direction) { - if (direction == Direction.forward || reverseCurve == null) + Curve _getCurve(AnimationDirection direction) { + if (direction == AnimationDirection.forward || reverseCurve == null) return curve; return reverseCurve; } - double _applyCurve(double t, Direction direction) { + double _applyCurve(double t, AnimationDirection direction) { Curve curve = _getCurve(direction); if (curve == null) return t; @@ -86,7 +86,7 @@ class AnimationTiming { } /// An animated variable with a concrete type -class AnimatedValue extends AnimationTiming implements AnimatedVariable { +class AnimatedValue extends AnimationTiming implements Animatable { AnimatedValue(this.begin, { this.end, Interval interval, Interval reverseInterval, Curve curve, Curve reverseCurve }) : super(interval: interval, reverseInterval: reverseInterval, curve: curve, reverseCurve: reverseCurve) { value = begin; @@ -105,7 +105,7 @@ class AnimatedValue extends AnimationTiming implements Animat T lerp(double t) => begin + (end - begin) * t; /// Updates the value of this variable according to the given animation clock value and direction - void setProgress(double t, Direction direction) { + void setProgress(double t, AnimationDirection direction) { if (end != null) { t = transform(t, direction); if (t == 0.0) @@ -120,24 +120,6 @@ class AnimatedValue extends AnimationTiming implements Animat String toString() => 'AnimatedValue(begin=$begin, end=$end, value=$value)'; } -/// A list of animated variables -class AnimatedList extends AnimationTiming implements AnimatedVariable { - /// The list of variables contained in the list - List variables; - - AnimatedList(this.variables, { Interval interval, Interval reverseInterval, Curve curve, Curve reverseCurve }) - : super(interval: interval, reverseInterval: reverseInterval, curve: curve, reverseCurve: reverseCurve); - - // Updates the value of all the variables in the list according to the given animation clock value and direction - void setProgress(double t, Direction direction) { - double adjustedTime = transform(t, direction); - for (AnimatedVariable variable in variables) - variable.setProgress(adjustedTime, direction); - } - - String toString() => 'AnimatedList([$variables])'; -} - /// An animated variable containing a color /// /// This class specializes the interpolation of AnimatedValue to be @@ -153,8 +135,8 @@ class AnimatedColorValue extends AnimatedValue { /// /// This class specializes the interpolation of AnimatedValue to be /// appropriate for rectangles. -class AnimatedRect extends AnimatedValue { - AnimatedRect(Rect begin, { Rect end, Interval interval, Interval reverseInterval, Curve curve, Curve reverseCurve }) +class AnimatedRectValue extends AnimatedValue { + AnimatedRectValue(Rect begin, { Rect end, Interval interval, Interval reverseInterval, Curve curve, Curve reverseCurve }) : super(begin, end: end, interval: interval, reverseInterval: reverseInterval, curve: curve, reverseCurve: reverseCurve); Rect lerp(double t) => Rect.lerp(begin, end, t); diff --git a/packages/flutter/lib/src/animation/animation_performance.dart b/packages/flutter/lib/src/animation/performance.dart similarity index 66% rename from packages/flutter/lib/src/animation/animation_performance.dart rename to packages/flutter/lib/src/animation/performance.dart index e9690daf2b..92c77f8b59 100644 --- a/packages/flutter/lib/src/animation/animation_performance.dart +++ b/packages/flutter/lib/src/animation/performance.dart @@ -9,7 +9,7 @@ import 'package:sky/src/animation/forces.dart'; import 'package:sky/src/animation/simulation_stepper.dart'; /// The status of an animation -enum AnimationStatus { +enum PerformanceStatus { /// The animation is stopped at the beginning dismissed, @@ -23,27 +23,27 @@ enum AnimationStatus { completed, } -typedef void AnimationPerformanceListener(); -typedef void AnimationPerformanceStatusListener(AnimationStatus status); +typedef void PerformanceListener(); +typedef void PerformanceStatusListener(PerformanceStatus status); -/// An interface that is implemented by [AnimationPerformance] that exposes a +/// An interface that is implemented by [Performance] that exposes a /// read-only view of the underlying performance. This is used by classes that /// want to watch a performance but should not be able to change the /// performance's state. -abstract class WatchableAnimationPerformance { +abstract class PerformanceView { /// Update the given variable according to the current progress of the performance - void updateVariable(AnimatedVariable variable); + void updateVariable(Animatable variable); /// Calls the listener every time the progress of the performance changes - void addListener(AnimationPerformanceListener listener); + void addListener(PerformanceListener listener); /// Stop calling the listener every time the progress of the performance changes - void removeListener(AnimationPerformanceListener listener); + void removeListener(PerformanceListener listener); /// Calls listener every time the status of the performance changes - void addStatusListener(AnimationPerformanceStatusListener listener); + void addStatusListener(PerformanceStatusListener listener); /// Stops calling the listener every time the status of the performance changes - void removeStatusListener(AnimationPerformanceStatusListener listener); + void removeStatusListener(PerformanceStatusListener listener); } -/// A timeline that can be reversed and used to update [AnimatedVariable]s. +/// A timeline that can be reversed and used to update [Animatable]s. /// /// For example, a performance may handle an animation of a menu opening by /// sliding and fading in (changing Y value and opacity) over .5 seconds. The @@ -51,30 +51,30 @@ abstract class WatchableAnimationPerformance { /// may also take direct control of the timeline by manipulating [progress], or /// [fling] the timeline causing a physics-based simulation to take over the /// progression. -class AnimationPerformance implements WatchableAnimationPerformance { - AnimationPerformance({ this.duration, double progress }) { +class Performance implements PerformanceView { + Performance({ this.duration, double progress }) { _timeline = new SimulationStepper(_tick); if (progress != null) _timeline.value = progress.clamp(0.0, 1.0); } - /// Returns a [WatchableAnimationPerformance] for this performance, + /// Returns a [PerformanceView] for this performance, /// so that a pointer to this object can be passed around without /// allowing users of that pointer to mutate the AnimationPerformance state. - WatchableAnimationPerformance get view => this; + PerformanceView get view => this; /// The length of time this performance should last Duration duration; SimulationStepper _timeline; - Direction _direction; + AnimationDirection _direction; /// The direction used to select the current curve /// /// Curve direction is only reset when we hit the beginning or the end of the /// timeline to avoid discontinuities in the value of any variables this /// performance is used to animate. - Direction _curveDirection; + AnimationDirection _curveDirection; /// If non-null, animate with this timing instead of a linear timing AnimationTiming timing; @@ -95,45 +95,45 @@ class AnimationPerformance implements WatchableAnimationPerformance { } /// Whether this animation is stopped at the beginning - bool get isDismissed => status == AnimationStatus.dismissed; + bool get isDismissed => status == PerformanceStatus.dismissed; /// Whether this animation is stopped at the end - bool get isCompleted => status == AnimationStatus.completed; + bool get isCompleted => status == PerformanceStatus.completed; /// Whether this animation is currently animating in either the forward or reverse direction bool get isAnimating => _timeline.isAnimating; /// The current status of this animation - AnimationStatus get status { + PerformanceStatus get status { if (!isAnimating && progress == 1.0) - return AnimationStatus.completed; + return PerformanceStatus.completed; if (!isAnimating && progress == 0.0) - return AnimationStatus.dismissed; - return _direction == Direction.forward ? - AnimationStatus.forward : - AnimationStatus.reverse; + return PerformanceStatus.dismissed; + return _direction == AnimationDirection.forward ? + PerformanceStatus.forward : + PerformanceStatus.reverse; } /// Update the given varaible according to the current progress of this performance - void updateVariable(AnimatedVariable variable) { + void updateVariable(Animatable variable) { variable.setProgress(_curvedProgress, _curveDirection); } /// Start running this animation forwards (towards the end) - Future forward() => play(Direction.forward); + Future forward() => play(AnimationDirection.forward); /// Start running this animation in reverse (towards the beginning) - Future reverse() => play(Direction.reverse); + Future reverse() => play(AnimationDirection.reverse); /// Start running this animation in the given direction - Future play([Direction direction = Direction.forward]) { + Future play([AnimationDirection direction = AnimationDirection.forward]) { _direction = direction; return resume(); } - /// Start running this animation in the most recently direction + /// Start running this animation in the most recent direction Future resume() { - return _animateTo(_direction == Direction.forward ? 1.0 : 0.0); + return _animateTo(_direction == AnimationDirection.forward ? 1.0 : 0.0); } /// Stop running this animation @@ -149,46 +149,46 @@ class AnimationPerformance implements WatchableAnimationPerformance { Future fling({double velocity: 1.0, Force force}) { if (force == null) force = kDefaultSpringForce; - _direction = velocity < 0.0 ? Direction.reverse : Direction.forward; + _direction = velocity < 0.0 ? AnimationDirection.reverse : AnimationDirection.forward; return _timeline.animateWith(force.release(progress, velocity)); } - final List _listeners = new List(); + final List _listeners = new List(); /// Calls the listener every time the progress of this performance changes - void addListener(AnimationPerformanceListener listener) { + void addListener(PerformanceListener listener) { _listeners.add(listener); } /// Stop calling the listener every time the progress of this performance changes - void removeListener(AnimationPerformanceListener listener) { + void removeListener(PerformanceListener listener) { _listeners.remove(listener); } void _notifyListeners() { - List localListeners = new List.from(_listeners); - for (AnimationPerformanceListener listener in localListeners) + List localListeners = new List.from(_listeners); + for (PerformanceListener listener in localListeners) listener(); } - final List _statusListeners = new List(); + final List _statusListeners = new List(); /// Calls listener every time the status of this performance changes - void addStatusListener(AnimationPerformanceStatusListener listener) { + void addStatusListener(PerformanceStatusListener listener) { _statusListeners.add(listener); } /// Stops calling the listener every time the status of this performance changes - void removeStatusListener(AnimationPerformanceStatusListener listener) { + void removeStatusListener(PerformanceStatusListener listener) { _statusListeners.remove(listener); } - AnimationStatus _lastStatus = AnimationStatus.dismissed; + PerformanceStatus _lastStatus = PerformanceStatus.dismissed; void _checkStatusChanged() { - AnimationStatus currentStatus = status; + PerformanceStatus currentStatus = status; if (currentStatus != _lastStatus) { - List localListeners = new List.from(_statusListeners); - for (AnimationPerformanceStatusListener listener in localListeners) + List localListeners = new List.from(_statusListeners); + for (PerformanceStatusListener listener in localListeners) listener(currentStatus); } _lastStatus = currentStatus; @@ -196,7 +196,7 @@ class AnimationPerformance implements WatchableAnimationPerformance { void _updateCurveDirection() { if (status != _lastStatus) { - if (_lastStatus == AnimationStatus.dismissed || _lastStatus == AnimationStatus.completed) + if (_lastStatus == PerformanceStatus.dismissed || _lastStatus == PerformanceStatus.completed) _curveDirection = _direction; } } @@ -221,8 +221,8 @@ class AnimationPerformance implements WatchableAnimationPerformance { } /// An animation performance with an animated variable with a concrete type -class ValueAnimation extends AnimationPerformance { - ValueAnimation({ this.variable, Duration duration, double progress }) : +class ValuePerformance extends Performance { + ValuePerformance({ this.variable, Duration duration, double progress }) : super(duration: duration, progress: progress); AnimatedValue variable; diff --git a/packages/flutter/lib/src/animation/simulation_stepper.dart b/packages/flutter/lib/src/animation/simulation_stepper.dart index 39ff00c4e8..dee9cb0c73 100644 --- a/packages/flutter/lib/src/animation/simulation_stepper.dart +++ b/packages/flutter/lib/src/animation/simulation_stepper.dart @@ -28,7 +28,7 @@ class _TweenSimulation extends Simulation { double x(double timeInSeconds) { assert(timeInSeconds >= 0.0); final double t = (timeInSeconds / _durationInSeconds).clamp(0.0, 1.0); - _tween.setProgress(t, Direction.forward); + _tween.setProgress(t, AnimationDirection.forward); return _tween.value; } diff --git a/packages/flutter/lib/src/painting/radial_reaction.dart b/packages/flutter/lib/src/painting/radial_reaction.dart index c7997e4e5e..c2822cb9da 100644 --- a/packages/flutter/lib/src/painting/radial_reaction.dart +++ b/packages/flutter/lib/src/painting/radial_reaction.dart @@ -30,13 +30,13 @@ class RadialReaction { _outerOpacity = new AnimatedValue(0.0, end: _kMaxOpacity, curve: easeOut); _innerCenter = new AnimatedValue(startPosition, end: center, curve: easeOut); _innerRadius = new AnimatedValue(0.0, end: radius, curve: easeOut); - _showPerformance = new AnimationPerformance(duration: _kShowDuration) + _showPerformance = new Performance(duration: _kShowDuration) ..addListener(() { _showPerformance.updateVariable(_outerOpacity); _showPerformance.updateVariable(_innerCenter); _showPerformance.updateVariable(_innerRadius); }); - _fade = new ValueAnimation( + _fade = new ValuePerformance( variable: new AnimatedValue(1.0, end: 0.0, curve: easeIn), duration: _kHideDuration ); @@ -48,14 +48,14 @@ class RadialReaction { /// The radius of the circle in which the reaction occurs final double radius; - AnimationPerformance _showPerformance; + Performance _showPerformance; AnimatedValue _outerOpacity; AnimatedValue _innerCenter; AnimatedValue _innerRadius; Future _showComplete; - ValueAnimation _fade; + ValuePerformance _fade; /// Show the reaction /// diff --git a/packages/flutter/lib/src/rendering/toggleable.dart b/packages/flutter/lib/src/rendering/toggleable.dart index 1a2bbae9ed..bd98d27cdb 100644 --- a/packages/flutter/lib/src/rendering/toggleable.dart +++ b/packages/flutter/lib/src/rendering/toggleable.dart @@ -24,15 +24,15 @@ abstract class RenderToggleable extends RenderConstrainedBox { : _value = value, _onChanged = onChanged, super(additionalConstraints: new BoxConstraints.tight(size)) { - _performance = new ValueAnimation( + _performance = new ValuePerformance( variable: new AnimatedValue(0.0, end: 1.0, curve: easeIn, reverseCurve: easeOut), duration: _kToggleDuration, progress: _value ? 1.0 : 0.0 )..addListener(markNeedsPaint); } - ValueAnimation get performance => _performance; - ValueAnimation _performance; + ValuePerformance get performance => _performance; + ValuePerformance _performance; double get position => _performance.value; @@ -68,7 +68,7 @@ abstract class RenderToggleable extends RenderConstrainedBox { if (value == _value) return; _value = value; - performance.play(value ? Direction.forward : Direction.reverse); + performance.play(value ? AnimationDirection.forward : AnimationDirection.reverse); } ValueChanged get onChanged => _onChanged; diff --git a/packages/flutter/lib/src/widgets/animated_component.dart b/packages/flutter/lib/src/widgets/animated_component.dart index d9b27490c9..cf6acbc34f 100644 --- a/packages/flutter/lib/src/widgets/animated_component.dart +++ b/packages/flutter/lib/src/widgets/animated_component.dart @@ -9,13 +9,13 @@ abstract class AnimatedComponent extends StatefulComponent { const AnimatedComponent({ Key key, this.direction, this.duration }) : super(key: key); final Duration duration; - final Direction direction; + final AnimationDirection direction; } abstract class AnimatedState extends State { void initState() { super.initState(); - _performance = new AnimationPerformance(duration: config.duration); + _performance = new Performance(duration: config.duration); performance.addStatusListener(_handleAnimationStatusChanged); if (buildDependsOnPerformance) { performance.addListener(() { @@ -34,13 +34,13 @@ abstract class AnimatedState extends State { performance.play(config.direction); } - AnimationPerformance get performance => _performance; - AnimationPerformance _performance; + Performance get performance => _performance; + Performance _performance; - void _handleAnimationStatusChanged(AnimationStatus status) { - if (status == AnimationStatus.completed) + void _handleAnimationStatusChanged(PerformanceStatus status) { + if (status == PerformanceStatus.completed) handleCompleted(); - else if (status == AnimationStatus.dismissed) + else if (status == PerformanceStatus.dismissed) handleDismissed(); } diff --git a/packages/flutter/lib/src/widgets/animated_container.dart b/packages/flutter/lib/src/widgets/animated_container.dart index 291023e9f2..4d1f1d4de0 100644 --- a/packages/flutter/lib/src/widgets/animated_container.dart +++ b/packages/flutter/lib/src/widgets/animated_container.dart @@ -90,11 +90,11 @@ class AnimatedContainerState extends State { AnimatedValue _width; AnimatedValue _height; - AnimationPerformance _performance; + Performance _performance; void initState() { super.initState(); - _performance = new AnimationPerformance(duration: config.duration) + _performance = new Performance(duration: config.duration) ..timing = new AnimationTiming(curve: config.curve) ..addListener(_updateAllVariables); _configAllVariables(); @@ -115,7 +115,7 @@ class AnimatedContainerState extends State { super.dispose(); } - void _updateVariable(AnimatedVariable variable) { + void _updateVariable(Animatable variable) { if (variable != null) _performance.updateVariable(variable); } diff --git a/packages/flutter/lib/src/widgets/dialog.dart b/packages/flutter/lib/src/widgets/dialog.dart index ef142d0baa..54c2a6f174 100644 --- a/packages/flutter/lib/src/widgets/dialog.dart +++ b/packages/flutter/lib/src/widgets/dialog.dart @@ -141,7 +141,7 @@ class DialogRoute extends Route { Duration get transitionDuration => _kTransitionDuration; bool get opaque => false; - Widget build(NavigatorState navigator, WatchableAnimationPerformance nextRoutePerformance) { + Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance) { return new FadeTransition( performance: performance, opacity: new AnimatedValue(0.0, end: 1.0, curve: easeOut), diff --git a/packages/flutter/lib/src/widgets/dismissable.dart b/packages/flutter/lib/src/widgets/dismissable.dart index 6cb2a78e9b..d019580a68 100644 --- a/packages/flutter/lib/src/widgets/dismissable.dart +++ b/packages/flutter/lib/src/widgets/dismissable.dart @@ -50,15 +50,15 @@ class Dismissable extends StatefulComponent { class DismissableState extends State { void initState() { super.initState(); - _fadePerformance = new AnimationPerformance(duration: _kCardDismissFadeout); - _fadePerformance.addStatusListener((AnimationStatus status) { - if (status == AnimationStatus.completed) + _fadePerformance = new Performance(duration: _kCardDismissFadeout); + _fadePerformance.addStatusListener((PerformanceStatus status) { + if (status == PerformanceStatus.completed) _handleFadeCompleted(); }); } - AnimationPerformance _fadePerformance; - AnimationPerformance _resizePerformance; + Performance _fadePerformance; + Performance _resizePerformance; Size _size; double _dragExtent = 0.0; @@ -97,7 +97,7 @@ class DismissableState extends State { assert(_resizePerformance == null); setState(() { - _resizePerformance = new AnimationPerformance() + _resizePerformance = new Performance() ..duration = _kCardDismissResize ..addListener(_handleResizeProgressChanged); _resizePerformance.play(); @@ -221,7 +221,7 @@ class DismissableState extends State { Widget build(BuildContext context) { if (_resizePerformance != null) { // make sure you remove this widget once it's been dismissed! - assert(_resizePerformance.status == AnimationStatus.forward); + assert(_resizePerformance.status == PerformanceStatus.forward); AnimatedValue squashAxisExtent = new AnimatedValue( _directionIsYAxis ? _size.width : _size.height, diff --git a/packages/flutter/lib/src/widgets/drag_target.dart b/packages/flutter/lib/src/widgets/drag_target.dart index accac1bb13..51800d7891 100644 --- a/packages/flutter/lib/src/widgets/drag_target.dart +++ b/packages/flutter/lib/src/widgets/drag_target.dart @@ -258,7 +258,7 @@ class DragRoute extends Route { bool get opaque => false; Duration get transitionDuration => const Duration(); - Widget build(NavigatorState navigator, WatchableAnimationPerformance nextRoutePerformance) { + Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance) { return new Positioned( left: _lastOffset.dx, top: _lastOffset.dy, diff --git a/packages/flutter/lib/src/widgets/drawer.dart b/packages/flutter/lib/src/widgets/drawer.dart index 5d980b391e..945542c5dd 100644 --- a/packages/flutter/lib/src/widgets/drawer.dart +++ b/packages/flutter/lib/src/widgets/drawer.dart @@ -55,15 +55,15 @@ class Drawer extends StatefulComponent { class DrawerState extends State { void initState() { super.initState(); - _performance = new AnimationPerformance(duration: _kBaseSettleDuration) - ..addStatusListener((AnimationStatus status) { - if (status == AnimationStatus.dismissed) + _performance = new Performance(duration: _kBaseSettleDuration) + ..addStatusListener((PerformanceStatus status) { + if (status == PerformanceStatus.dismissed) config.navigator.pop(); }); _open(); } - AnimationPerformance _performance; + Performance _performance; Widget build(BuildContext context) { Widget mask = new GestureDetector( @@ -138,7 +138,7 @@ class DrawerRoute extends Route { bool get opaque => false; - Widget build(NavigatorState navigator, WatchableAnimationPerformance nextRoutePerformance) { + Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance) { return new Focus( key: new GlobalObjectKey(this), autofocus: true, diff --git a/packages/flutter/lib/src/widgets/ink_well.dart b/packages/flutter/lib/src/widgets/ink_well.dart index a0f36726d1..9abe7745da 100644 --- a/packages/flutter/lib/src/widgets/ink_well.dart +++ b/packages/flutter/lib/src/widgets/ink_well.dart @@ -32,7 +32,7 @@ class InkSplash { _radius = new AnimatedValue( _kSplashInitialSize, end: _targetRadius, curve: easeOut); - _performance = new ValueAnimation( + _performance = new ValuePerformance( variable: _radius, duration: new Duration(milliseconds: (_targetRadius / _kSplashUnconfirmedVelocity).floor()) )..addListener(_handleRadiusChange); @@ -47,7 +47,7 @@ class InkSplash { double _targetRadius; double _pinnedRadius; AnimatedValue _radius; - AnimationPerformance _performance; + Performance _performance; Timer _startTimer; bool _cancelStartTimer() { diff --git a/packages/flutter/lib/src/widgets/navigator.dart b/packages/flutter/lib/src/widgets/navigator.dart index 55abf1be2c..7df0d8fe0a 100644 --- a/packages/flutter/lib/src/widgets/navigator.dart +++ b/packages/flutter/lib/src/widgets/navigator.dart @@ -118,7 +118,7 @@ class NavigatorState extends State { Widget build(BuildContext context) { List visibleRoutes = new List(); bool alreadyInsertModalBarrier = false; - WatchableAnimationPerformance nextPerformance; + PerformanceView nextPerformance; for (int i = _history.length-1; i >= 0; i -= 1) { Route route = _history[i]; if (!route.hasContent) { @@ -126,7 +126,7 @@ class NavigatorState extends State { continue; } route.ensurePerformance( - direction: (i <= _currentPosition) ? Direction.forward : Direction.reverse + direction: (i <= _currentPosition) ? AnimationDirection.forward : AnimationDirection.reverse ); route._onDismissed = () { setState(() { @@ -159,28 +159,28 @@ class NavigatorState extends State { abstract class Route { - WatchableAnimationPerformance get performance => _performance?.view; - AnimationPerformance _performance; + PerformanceView get performance => _performance?.view; + Performance _performance; NotificationCallback _onDismissed; - AnimationPerformance createPerformance() { + Performance createPerformance() { Duration duration = transitionDuration; if (duration > Duration.ZERO) { - return new AnimationPerformance(duration: duration) - ..addStatusListener((AnimationStatus status) { - if (status == AnimationStatus.dismissed && _onDismissed != null) + return new Performance(duration: duration) + ..addStatusListener((PerformanceStatus status) { + if (status == PerformanceStatus.dismissed && _onDismissed != null) _onDismissed(); }); } return null; } - void ensurePerformance({ Direction direction }) { + void ensurePerformance({ AnimationDirection direction }) { assert(direction != null); if (_performance == null) _performance = createPerformance(); if (_performance != null) { - AnimationStatus desiredStatus = direction == Direction.forward ? AnimationStatus.forward : AnimationStatus.reverse; + PerformanceStatus desiredStatus = direction == AnimationDirection.forward ? PerformanceStatus.forward : PerformanceStatus.reverse; if (_performance.status != desiredStatus) _performance.play(direction); } @@ -236,14 +236,14 @@ abstract class Route { /// cover the entire application surface or are in any way semi-transparent. bool get opaque => false; - /// If this is set to a non-zero [Duration], then an [AnimationPerformance] + /// If this is set to a non-zero [Duration], then an [Performance] /// object, available via the performance field, will be created when the /// route is first built, using the duration described here. Duration get transitionDuration => Duration.ZERO; bool get isActuallyOpaque => (performance == null || _performance.isCompleted) && opaque; - Widget build(NavigatorState navigator, WatchableAnimationPerformance nextRoutePerformance); + Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance); void didPop([dynamic result]) { if (performance == null && _onDismissed != null) _onDismissed(); @@ -263,7 +263,7 @@ class PageRoute extends Route { bool get opaque => true; Duration get transitionDuration => _kTransitionDuration; - Widget build(NavigatorState navigator, WatchableAnimationPerformance nextRoutePerformance) { + Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance) { // TODO(jackson): Hit testing should ignore transform // TODO(jackson): Block input unless content is interactive return new SlideTransition( @@ -296,5 +296,5 @@ class StateRoute extends Route { super.didPop(result); } - Widget build(NavigatorState navigator, WatchableAnimationPerformance nextRoutePerformance) => null; + Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance) => null; } diff --git a/packages/flutter/lib/src/widgets/popup_menu.dart b/packages/flutter/lib/src/widgets/popup_menu.dart index c06e9539c8..3812fb779a 100644 --- a/packages/flutter/lib/src/widgets/popup_menu.dart +++ b/packages/flutter/lib/src/widgets/popup_menu.dart @@ -44,7 +44,7 @@ class PopupMenu extends StatefulComponent { final List items; final int level; final NavigatorState navigator; - final WatchableAnimationPerformance performance; + final PerformanceView performance; PopupMenuState createState() => new PopupMenuState(); } @@ -159,8 +159,8 @@ class MenuRoute extends Route { final PopupMenuItemsBuilder builder; final int level; - AnimationPerformance createPerformance() { - AnimationPerformance result = super.createPerformance(); + Performance createPerformance() { + Performance result = super.createPerformance(); AnimationTiming timing = new AnimationTiming(); timing.reverseInterval = new Interval(0.0, _kMenuCloseIntervalEnd); result.timing = timing; @@ -172,7 +172,7 @@ class MenuRoute extends Route { bool get opaque => false; Duration get transitionDuration => _kMenuDuration; - Widget build(NavigatorState navigator, WatchableAnimationPerformance nextRoutePerformance) { + Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance) { return new Positioned( top: position?.top, right: position?.right, diff --git a/packages/flutter/lib/src/widgets/progress_indicator.dart b/packages/flutter/lib/src/widgets/progress_indicator.dart index d6c86ef72f..c661fced9e 100644 --- a/packages/flutter/lib/src/widgets/progress_indicator.dart +++ b/packages/flutter/lib/src/widgets/progress_indicator.dart @@ -36,16 +36,16 @@ abstract class ProgressIndicator extends StatefulComponent { class ProgressIndicatorState extends State { - ValueAnimation _performance; + ValuePerformance _performance; void initState() { super.initState(); - _performance = new ValueAnimation( + _performance = new ValuePerformance( variable: new AnimatedValue(0.0, end: 1.0, curve: ease), duration: const Duration(milliseconds: 1500) ); - _performance.addStatusListener((AnimationStatus status) { - if (status == AnimationStatus.completed) + _performance.addStatusListener((PerformanceStatus status) { + if (status == PerformanceStatus.completed) _restartAnimation(); }); _performance.play(); diff --git a/packages/flutter/lib/src/widgets/snack_bar.dart b/packages/flutter/lib/src/widgets/snack_bar.dart index 179e8320ce..757d9a683a 100644 --- a/packages/flutter/lib/src/widgets/snack_bar.dart +++ b/packages/flutter/lib/src/widgets/snack_bar.dart @@ -49,7 +49,7 @@ class SnackBar extends AnimatedComponent { this.actions, bool showing, this.onDismissed - }) : super(key: key, direction: showing ? Direction.forward : Direction.reverse, duration: _kSlideInDuration) { + }) : super(key: key, direction: showing ? AnimationDirection.forward : AnimationDirection.reverse, duration: _kSlideInDuration) { assert(content != null); } diff --git a/packages/flutter/lib/src/widgets/tabs.dart b/packages/flutter/lib/src/widgets/tabs.dart index 19b9c6748b..bcee5e115e 100644 --- a/packages/flutter/lib/src/widgets/tabs.dart +++ b/packages/flutter/lib/src/widgets/tabs.dart @@ -408,16 +408,16 @@ class TabBar extends Scrollable { class TabBarState extends ScrollableState { void initState() { super.initState(); - _indicatorAnimation = new ValueAnimation() + _indicatorAnimation = new ValuePerformance() ..duration = _kTabBarScroll - ..variable = new AnimatedRect(null, curve: ease); + ..variable = new AnimatedRectValue(null, curve: ease); scrollBehavior.isScrollable = config.isScrollable; } Size _tabBarSize; Size _viewportSize = Size.zero; List _tabWidths; - ValueAnimation _indicatorAnimation; + ValuePerformance _indicatorAnimation; void didUpdateConfig(TabBar oldConfig) { super.didUpdateConfig(oldConfig); @@ -425,7 +425,7 @@ class TabBarState extends ScrollableState { scrollTo(0.0); } - AnimatedRect get _indicatorRect => _indicatorAnimation.variable; + AnimatedRectValue get _indicatorRect => _indicatorAnimation.variable; void _startIndicatorAnimation(int fromTabIndex, int toTabIndex) { _indicatorRect diff --git a/packages/flutter/lib/src/widgets/transitions.dart b/packages/flutter/lib/src/widgets/transitions.dart index be14f5d30d..4f00c3694d 100644 --- a/packages/flutter/lib/src/widgets/transitions.dart +++ b/packages/flutter/lib/src/widgets/transitions.dart @@ -7,7 +7,7 @@ import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/framework.dart'; import 'package:vector_math/vector_math_64.dart'; -export 'package:sky/animation.dart' show Direction; +export 'package:sky/animation.dart' show AnimationDirection; abstract class TransitionComponent extends StatefulComponent { TransitionComponent({ @@ -17,7 +17,7 @@ abstract class TransitionComponent extends StatefulComponent { assert(performance != null); } - final WatchableAnimationPerformance performance; + final PerformanceView performance; Widget build(BuildContext context); @@ -57,7 +57,7 @@ abstract class TransitionWithChild extends TransitionComponent { TransitionWithChild({ Key key, this.child, - WatchableAnimationPerformance performance + PerformanceView performance }) : super(key: key, performance: performance); final Widget child; @@ -71,7 +71,7 @@ class SlideTransition extends TransitionWithChild { SlideTransition({ Key key, this.position, - WatchableAnimationPerformance performance, + PerformanceView performance, Widget child }) : super(key: key, performance: performance, @@ -91,7 +91,7 @@ class FadeTransition extends TransitionWithChild { FadeTransition({ Key key, this.opacity, - WatchableAnimationPerformance performance, + PerformanceView performance, Widget child }) : super(key: key, performance: performance, @@ -109,7 +109,7 @@ class ColorTransition extends TransitionWithChild { ColorTransition({ Key key, this.color, - WatchableAnimationPerformance performance, + PerformanceView performance, Widget child }) : super(key: key, performance: performance, @@ -131,7 +131,7 @@ class SquashTransition extends TransitionWithChild { Key key, this.width, this.height, - WatchableAnimationPerformance performance, + PerformanceView performance, Widget child }) : super(key: key, performance: performance, @@ -156,7 +156,7 @@ class BuilderTransition extends TransitionComponent { Key key, this.variables, this.builder, - WatchableAnimationPerformance performance + PerformanceView performance }) : super(key: key, performance: performance); From 071201a5bba86afad3e2a5b41da3cf790b4784ec Mon Sep 17 00:00:00 2001 From: Viktor Lidholt Date: Mon, 5 Oct 2015 11:24:59 -0700 Subject: [PATCH 22/27] Prevents sprite update methods to be called before the sprite box has been property intialized --- packages/flutter_sprites/lib/sprite_box.dart | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/packages/flutter_sprites/lib/sprite_box.dart b/packages/flutter_sprites/lib/sprite_box.dart index 3abf3be04c..90f88c3b0e 100644 --- a/packages/flutter_sprites/lib/sprite_box.dart +++ b/packages/flutter_sprites/lib/sprite_box.dart @@ -86,6 +86,8 @@ class SpriteBox extends RenderBox { return _visibleArea; } + bool _initialized = false; + // Setup /// Creates a new SpriteBox with a node as its content, by default uses letterboxing. @@ -136,6 +138,7 @@ class SpriteBox extends RenderBox { size = constraints.biggest; _invalidateTransformMatrix(); _callSpriteBoxPerformedLayout(_rootNode); + _initialized = true; } // Adding and removing nodes @@ -361,11 +364,13 @@ class SpriteBox extends RenderBox { _frameRate = 1.0/delta; - _callConstraintsPreUpdate(delta); - _runActions(delta); - _callUpdate(_rootNode, delta); - _callStepPhysics(delta); - _callConstraintsConstrain(delta); + if (_initialized) { + _callConstraintsPreUpdate(delta); + _runActions(delta); + _callUpdate(_rootNode, delta); + _callStepPhysics(delta); + _callConstraintsConstrain(delta); + } // Schedule next update _scheduleTick(); From 3a31f5f772f7ad4af93061d5752bf674c2d9f9c8 Mon Sep 17 00:00:00 2001 From: Adam Barth Date: Mon, 5 Oct 2015 12:09:59 -0700 Subject: [PATCH 23/27] Give Interval a Curve to apply between start and end This patch simplifies AnimationTiming and all the AnimatedValue base classes. Also, make PopupMenu a stateless component because it has no state. Fixes #1168 --- examples/widgets/progress_indicator.dart | 5 +- .../lib/src/animation/animated_value.dart | 61 +++++----------- .../flutter/lib/src/animation/curves.dart | 12 +++- .../lib/src/widgets/animated_container.dart | 16 ++--- .../flutter/lib/src/widgets/dismissable.dart | 5 +- .../flutter/lib/src/widgets/popup_menu.dart | 70 ++++++------------- 6 files changed, 58 insertions(+), 111 deletions(-) diff --git a/examples/widgets/progress_indicator.dart b/examples/widgets/progress_indicator.dart index e769784b46..b4b2b410a1 100644 --- a/examples/widgets/progress_indicator.dart +++ b/examples/widgets/progress_indicator.dart @@ -18,9 +18,8 @@ class ProgressIndicatorAppState extends State { ..variable = new AnimatedValue( 0.0, end: 1.0, - curve: ease, - reverseCurve: ease, - interval: new Interval(0.0, 0.9) + curve: new Interval(0.0, 0.9, curve: ease), + reverseCurve: ease ); valueAnimation.addStatusListener((PerformanceStatus status) { if (status == PerformanceStatus.dismissed || status == PerformanceStatus.completed) diff --git a/packages/flutter/lib/src/animation/animated_value.dart b/packages/flutter/lib/src/animation/animated_value.dart index f42c4b8910..9b4a8daa1b 100644 --- a/packages/flutter/lib/src/animation/animated_value.dart +++ b/packages/flutter/lib/src/animation/animated_value.dart @@ -25,70 +25,43 @@ abstract class Animatable { String toString(); } -/// Used by [AnimationPerformance] to convert the timing of a performance to a different timescale. +/// Used by [Performance] to convert the timing of a performance to a different timescale. /// For example, by setting different values for the interval and reverseInterval, a performance /// can be made to take longer in one direction that the other. class AnimationTiming { - AnimationTiming({ - this.interval: const Interval(0.0, 1.0), - this.reverseInterval, - this.curve: linear, - this.reverseCurve - }); + AnimationTiming({ this.curve, this.reverseCurve }); - /// The interval during which this timing is active in the forward direction - Interval interval; - - /// The interval during which this timing is active in the reverse direction - /// - /// If this field is null, the timing defaults to using [interval] in both directions. - Interval reverseInterval; - - /// The curve that this timing applies to the animation clock in the forward direction + /// The curve to use in the forward direction Curve curve; - /// The curve that this timing applies to the animation clock in the reverse direction + /// The curve to use in the reverse direction /// - /// If this field is null, the timing defaults to using [curve] in both directions. + /// If this field is null, use [curve] in both directions. Curve reverseCurve; /// Applies this timing to the given animation clock value in the given direction double transform(double t, AnimationDirection direction) { - Interval interval = _getInterval(direction); - if (interval != null) - t = interval.transform(t); - assert(t >= 0.0 && t <= 1.0); + Curve activeCurve = _getActiveCurve(direction); + if (activeCurve == null) + return t; if (t == 0.0 || t == 1.0) { - assert(t == _applyCurve(t, direction).round().toDouble()); + assert(activeCurve.transform(t).round() == t); return t; } - return _applyCurve(t, direction); + return activeCurve.transform(t); } - Interval _getInterval(AnimationDirection direction) { - if (direction == AnimationDirection.forward || reverseInterval == null) - return interval; - return reverseInterval; - } - - Curve _getCurve(AnimationDirection direction) { + Curve _getActiveCurve(AnimationDirection direction) { if (direction == AnimationDirection.forward || reverseCurve == null) return curve; return reverseCurve; } - - double _applyCurve(double t, AnimationDirection direction) { - Curve curve = _getCurve(direction); - if (curve == null) - return t; - return curve.transform(t); - } } /// An animated variable with a concrete type class AnimatedValue extends AnimationTiming implements Animatable { - AnimatedValue(this.begin, { this.end, Interval interval, Interval reverseInterval, Curve curve, Curve reverseCurve }) - : super(interval: interval, reverseInterval: reverseInterval, curve: curve, reverseCurve: reverseCurve) { + AnimatedValue(this.begin, { this.end, Curve curve, Curve reverseCurve }) + : super(curve: curve, reverseCurve: reverseCurve) { value = begin; } @@ -125,8 +98,8 @@ class AnimatedValue extends AnimationTiming implements Animat /// This class specializes the interpolation of AnimatedValue to be /// appropriate for colors. class AnimatedColorValue extends AnimatedValue { - AnimatedColorValue(Color begin, { Color end, Interval interval, Interval reverseInterval, Curve curve, Curve reverseCurve }) - : super(begin, end: end, interval: interval, reverseInterval: reverseInterval, curve: curve, reverseCurve: reverseCurve); + AnimatedColorValue(Color begin, { Color end, Curve curve, Curve reverseCurve }) + : super(begin, end: end, curve: curve, reverseCurve: reverseCurve); Color lerp(double t) => Color.lerp(begin, end, t); } @@ -136,8 +109,8 @@ class AnimatedColorValue extends AnimatedValue { /// This class specializes the interpolation of AnimatedValue to be /// appropriate for rectangles. class AnimatedRectValue extends AnimatedValue { - AnimatedRectValue(Rect begin, { Rect end, Interval interval, Interval reverseInterval, Curve curve, Curve reverseCurve }) - : super(begin, end: end, interval: interval, reverseInterval: reverseInterval, curve: curve, reverseCurve: reverseCurve); + AnimatedRectValue(Rect begin, { Rect end, Curve curve, Curve reverseCurve }) + : super(begin, end: end, curve: curve, reverseCurve: reverseCurve); Rect lerp(double t) => Rect.lerp(begin, end, t); } diff --git a/packages/flutter/lib/src/animation/curves.dart b/packages/flutter/lib/src/animation/curves.dart index 31fb4a911b..5b44cc43eb 100644 --- a/packages/flutter/lib/src/animation/curves.dart +++ b/packages/flutter/lib/src/animation/curves.dart @@ -27,9 +27,9 @@ class Linear implements Curve { double transform(double t) => t; } -/// A curve that is 0.0 until start, then linear from 0.0 to 1.0 at end, then 1.0 +/// A curve that is 0.0 until start, then curved from 0.0 to 1.0 at end, then 1.0 class Interval implements Curve { - const Interval(this.start, this.end); + const Interval(this.start, this.end, { this.curve: linear }); /// The smallest value for which this interval is 0.0 final double start; @@ -37,12 +37,18 @@ class Interval implements Curve { /// The smallest value for which this interval is 1.0 final double end; + /// The curve to apply between [start] and [end] + final Curve curve; + double transform(double t) { assert(start >= 0.0); assert(start <= 1.0); assert(end >= 0.0); assert(end <= 1.0); - return ((t - start) / (end - start)).clamp(0.0, 1.0); + t = ((t - start) / (end - start)).clamp(0.0, 1.0); + if (t == 0.0 || t == 1.0) + return t; + return curve.transform(t); } } diff --git a/packages/flutter/lib/src/widgets/animated_container.dart b/packages/flutter/lib/src/widgets/animated_container.dart index 4d1f1d4de0..e9c56a115a 100644 --- a/packages/flutter/lib/src/widgets/animated_container.dart +++ b/packages/flutter/lib/src/widgets/animated_container.dart @@ -9,29 +9,29 @@ import 'package:sky/src/widgets/framework.dart'; import 'package:vector_math/vector_math_64.dart'; class AnimatedBoxConstraintsValue extends AnimatedValue { - AnimatedBoxConstraintsValue(BoxConstraints begin, { BoxConstraints end, Curve curve: linear }) - : super(begin, end: end, curve: curve); + AnimatedBoxConstraintsValue(BoxConstraints begin, { BoxConstraints end, Curve curve, Curve reverseCurve }) + : super(begin, end: end, curve: curve, reverseCurve: reverseCurve); BoxConstraints lerp(double t) => BoxConstraints.lerp(begin, end, t); } class AnimatedBoxDecorationValue extends AnimatedValue { - AnimatedBoxDecorationValue(BoxDecoration begin, { BoxDecoration end, Curve curve: linear }) - : super(begin, end: end, curve: curve); + AnimatedBoxDecorationValue(BoxDecoration begin, { BoxDecoration end, Curve curve, Curve reverseCurve }) + : super(begin, end: end, curve: curve, reverseCurve: reverseCurve); BoxDecoration lerp(double t) => BoxDecoration.lerp(begin, end, t); } class AnimatedEdgeDimsValue extends AnimatedValue { - AnimatedEdgeDimsValue(EdgeDims begin, { EdgeDims end, Curve curve: linear }) - : super(begin, end: end, curve: curve); + AnimatedEdgeDimsValue(EdgeDims begin, { EdgeDims end, Curve curve, Curve reverseCurve }) + : super(begin, end: end, curve: curve, reverseCurve: reverseCurve); EdgeDims lerp(double t) => EdgeDims.lerp(begin, end, t); } class AnimatedMatrix4Value extends AnimatedValue { - AnimatedMatrix4Value(Matrix4 begin, { Matrix4 end, Curve curve: linear }) - : super(begin, end: end, curve: curve); + AnimatedMatrix4Value(Matrix4 begin, { Matrix4 end, Curve curve, Curve reverseCurve }) + : super(begin, end: end, curve: curve, reverseCurve: reverseCurve); Matrix4 lerp(double t) { // TODO(mpcomplete): Animate the full matrix. Will animating the cells diff --git a/packages/flutter/lib/src/widgets/dismissable.dart b/packages/flutter/lib/src/widgets/dismissable.dart index d019580a68..b9c11e2ecf 100644 --- a/packages/flutter/lib/src/widgets/dismissable.dart +++ b/packages/flutter/lib/src/widgets/dismissable.dart @@ -12,7 +12,7 @@ import 'package:sky/src/widgets/gesture_detector.dart'; const Duration _kCardDismissFadeout = const Duration(milliseconds: 200); const Duration _kCardDismissResize = const Duration(milliseconds: 300); -final Interval _kCardDismissResizeInterval = new Interval(0.4, 1.0); +const Curve _kCardDismissResizeCurve = const Interval(0.4, 1.0, curve: ease); const double _kMinFlingVelocity = 700.0; const double _kMinFlingVelocityDelta = 400.0; const double _kFlingVelocityScale = 1.0 / 300.0; @@ -226,8 +226,7 @@ class DismissableState extends State { AnimatedValue squashAxisExtent = new AnimatedValue( _directionIsYAxis ? _size.width : _size.height, end: 0.0, - curve: ease, - interval: _kCardDismissResizeInterval + curve: _kCardDismissResizeCurve ); return new SquashTransition( diff --git a/packages/flutter/lib/src/widgets/popup_menu.dart b/packages/flutter/lib/src/widgets/popup_menu.dart index 3812fb779a..c6af624244 100644 --- a/packages/flutter/lib/src/widgets/popup_menu.dart +++ b/packages/flutter/lib/src/widgets/popup_menu.dart @@ -29,7 +29,7 @@ const double _kMenuVerticalPadding = 8.0; typedef List PopupMenuItemsBuilder(NavigatorState navigator); -class PopupMenu extends StatefulComponent { +class PopupMenu extends StatelessComponent { PopupMenu({ Key key, this.items, @@ -46,76 +46,46 @@ class PopupMenu extends StatefulComponent { final NavigatorState navigator; final PerformanceView performance; - PopupMenuState createState() => new PopupMenuState(); -} - -class PopupMenuState extends State { - void initState() { - super.initState(); - config.performance.addListener(_performanceChanged); - } - - void didUpdateConfig(PopupMenu oldConfig) { - if (config.performance != oldConfig.performance) { - oldConfig.performance.removeListener(_performanceChanged); - config.performance.addListener(_performanceChanged); - } - } - - void dispose() { - config.performance.removeListener(_performanceChanged); - super.dispose(); - } - - void _performanceChanged() { - setState(() { - // the performance changed, and our state is tied up with the performance - }); - } - - BoxPainter _painter; - - void _updateBoxPainter(BoxDecoration decoration) { - if (_painter == null || _painter.decoration != decoration) - _painter = new BoxPainter(decoration); - } - Widget build(BuildContext context) { - _updateBoxPainter(new BoxDecoration( + final BoxPainter painter = new BoxPainter(new BoxDecoration( backgroundColor: Theme.of(context).canvasColor, borderRadius: 2.0, - boxShadow: shadows[config.level] + boxShadow: shadows[level] )); - double unit = 1.0 / (config.items.length + 1.5); // 1.0 for the width and 0.5 for the last item's fade. + + double unit = 1.0 / (items.length + 1.5); // 1.0 for the width and 0.5 for the last item's fade. List children = []; - for (int i = 0; i < config.items.length; ++i) { + + for (int i = 0; i < items.length; ++i) { double start = (i + 1) * unit; double end = (start + 1.5 * unit).clamp(0.0, 1.0); children.add(new FadeTransition( - performance: config.performance, - opacity: new AnimatedValue(0.0, end: 1.0, interval: new Interval(start, end)), + performance: performance, + opacity: new AnimatedValue(0.0, end: 1.0, curve: new Interval(start, end)), child: new InkWell( - onTap: () { config.navigator.pop(config.items[i].value); }, - child: config.items[i] + onTap: () { navigator.pop(items[i].value); }, + child: items[i] )) ); } - final width = new AnimatedValue(0.0, end: 1.0, interval: new Interval(0.0, unit)); - final height = new AnimatedValue(0.0, end: 1.0, interval: new Interval(0.0, unit * config.items.length)); + + final width = new AnimatedValue(0.0, end: 1.0, curve: new Interval(0.0, unit)); + final height = new AnimatedValue(0.0, end: 1.0, curve: new Interval(0.0, unit * items.length)); + return new FadeTransition( - performance: config.performance, - opacity: new AnimatedValue(0.0, end: 1.0, interval: new Interval(0.0, 1.0 / 3.0)), + performance: performance, + opacity: new AnimatedValue(0.0, end: 1.0, curve: new Interval(0.0, 1.0 / 3.0)), child: new Container( margin: new EdgeDims.all(_kMenuMargin), child: new BuilderTransition( - performance: config.performance, + performance: performance, variables: [width, height], builder: (BuildContext context) { return new CustomPaint( callback: (sky.Canvas canvas, Size size) { double widthValue = width.value * size.width; double heightValue = height.value * size.height; - _painter.paint(canvas, new Rect.fromLTWH(size.width - widthValue, 0.0, widthValue, heightValue)); + painter.paint(canvas, new Rect.fromLTWH(size.width - widthValue, 0.0, widthValue, heightValue)); }, child: new ConstrainedBox( constraints: new BoxConstraints( @@ -162,7 +132,7 @@ class MenuRoute extends Route { Performance createPerformance() { Performance result = super.createPerformance(); AnimationTiming timing = new AnimationTiming(); - timing.reverseInterval = new Interval(0.0, _kMenuCloseIntervalEnd); + timing.reverseCurve = new Interval(0.0, _kMenuCloseIntervalEnd); result.timing = timing; return result; } From f5834c9abec5b3434f3f3394709d53bb896b07a5 Mon Sep 17 00:00:00 2001 From: Hixie Date: Mon, 5 Oct 2015 10:24:22 -0700 Subject: [PATCH 24/27] Add more debugging information to Widgets. Also, fix comment mentioning syncConstructorArguments. --- packages/flutter/lib/src/widgets/basic.dart | 12 ++++++++++++ packages/flutter/lib/src/widgets/framework.dart | 15 +++++++++++---- packages/flutter/lib/src/widgets/scrollable.dart | 2 +- 3 files changed, 24 insertions(+), 5 deletions(-) diff --git a/packages/flutter/lib/src/widgets/basic.dart b/packages/flutter/lib/src/widgets/basic.dart index d5dbb44004..22f7d3e6a0 100644 --- a/packages/flutter/lib/src/widgets/basic.dart +++ b/packages/flutter/lib/src/widgets/basic.dart @@ -712,6 +712,11 @@ class DefaultTextStyle extends InheritedWidget { } bool updateShouldNotify(DefaultTextStyle old) => style != old.style; + + void debugFillDescription(List description) { + super.debugFillDescription(description); + '$style'.split('\n').forEach(description.add); + } } class Text extends StatelessComponent { @@ -738,6 +743,13 @@ class Text extends StatelessComponent { text = new StyledTextSpan(combinedStyle, [text]); return new Paragraph(text: text); } + + void debugFillDescription(List description) { + super.debugFillDescription(description); + description.add('"$data"'); + if (style != null) + '$style'.split('\n').forEach(description.add); + } } class Image extends LeafRenderObjectWidget { diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index c0cce2b28f..efce9af4ca 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -182,10 +182,15 @@ abstract class Widget { Element createElement(); String toString() { - if (key == null) - return '$runtimeType'; - return '$runtimeType-$key'; - } + final String name = key == null ? '$runtimeType' : '$runtimeType-$key'; + final List data = []; + debugFillDescription(data); + if (data.isEmpty) + return 'name'; + return 'name(${data.join("; ")})'; + } + + void debugFillDescription(List description) { } } /// RenderObjectWidgets provide the configuration for [RenderObjectElement]s, @@ -786,6 +791,8 @@ abstract class Element implements BuildContext { description.add('no depth'); if (widget == null) description.add('no widget'); + else + widget.debugFillDescription(description); } String toStringDeep([String prefixLineOne = '', String prefixOtherLines = '']) { diff --git a/packages/flutter/lib/src/widgets/scrollable.dart b/packages/flutter/lib/src/widgets/scrollable.dart index cf1693a4a1..da4d940a96 100644 --- a/packages/flutter/lib/src/widgets/scrollable.dart +++ b/packages/flutter/lib/src/widgets/scrollable.dart @@ -318,7 +318,7 @@ class ScrollableViewportState extends ScrollableState { }); } void _updateScrollBehaviour() { - // if you don't call this from build() or syncConstructorArguments(), you must call it from setState(). + // if you don't call this from build(), you must call it from setState(). scrollTo(scrollBehavior.updateExtents( contentExtent: _childSize, containerExtent: _viewportSize, From 290ed842b21f071ae9c22e2c941a696377f24947 Mon Sep 17 00:00:00 2001 From: Hixie Date: Mon, 5 Oct 2015 13:38:07 -0700 Subject: [PATCH 25/27] Handle a route being dismissed before being popped Ensure that if a route's performance is dismissed before the route is popped, that we pop the route. --- packages/flutter/lib/src/widgets/drawer.dart | 3 --- .../flutter/lib/src/widgets/navigator.dart | 20 ++++++++++++++----- 2 files changed, 15 insertions(+), 8 deletions(-) diff --git a/packages/flutter/lib/src/widgets/drawer.dart b/packages/flutter/lib/src/widgets/drawer.dart index 945542c5dd..1d73b9fc84 100644 --- a/packages/flutter/lib/src/widgets/drawer.dart +++ b/packages/flutter/lib/src/widgets/drawer.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; - import 'package:sky/animation.dart'; import 'package:sky/material.dart'; import 'package:sky/src/widgets/animated_container.dart'; @@ -11,7 +9,6 @@ import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/gesture_detector.dart'; import 'package:sky/src/widgets/navigator.dart'; -import 'package:sky/src/widgets/scrollable.dart'; import 'package:sky/src/widgets/theme.dart'; import 'package:sky/src/widgets/transitions.dart'; import 'package:sky/src/widgets/focus.dart'; diff --git a/packages/flutter/lib/src/widgets/navigator.dart b/packages/flutter/lib/src/widgets/navigator.dart index 7df0d8fe0a..212b37f1a1 100644 --- a/packages/flutter/lib/src/widgets/navigator.dart +++ b/packages/flutter/lib/src/widgets/navigator.dart @@ -129,8 +129,13 @@ class NavigatorState extends State { direction: (i <= _currentPosition) ? AnimationDirection.forward : AnimationDirection.reverse ); route._onDismissed = () { + assert(_history.contains(route)); + if (_history.lastIndexOf(route) <= _currentPosition) + popRoute(route); + }; + route._onRemoveRoute = () { + assert(_history.contains(route)); setState(() { - assert(_history.contains(route)); _history.remove(route); }); }; @@ -162,14 +167,19 @@ abstract class Route { PerformanceView get performance => _performance?.view; Performance _performance; NotificationCallback _onDismissed; + NotificationCallback _onRemoveRoute; Performance createPerformance() { Duration duration = transitionDuration; if (duration > Duration.ZERO) { return new Performance(duration: duration) ..addStatusListener((PerformanceStatus status) { - if (status == PerformanceStatus.dismissed && _onDismissed != null) - _onDismissed(); + if (status == PerformanceStatus.dismissed) { + if (_onDismissed != null) + _onDismissed(); + if (_onRemoveRoute != null) + _onRemoveRoute(); + } }); } return null; @@ -245,8 +255,8 @@ abstract class Route { Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance); void didPop([dynamic result]) { - if (performance == null && _onDismissed != null) - _onDismissed(); + if (performance == null && _onRemoveRoute != null) + _onRemoveRoute(); } String toString() => '$runtimeType()'; From ab104c809d82ca9117bb906edce22b64a57b7c42 Mon Sep 17 00:00:00 2001 From: Viktor Lidholt Date: Mon, 5 Oct 2015 13:47:51 -0700 Subject: [PATCH 26/27] Handle removal of sprite physics bodies during the physics simulation --- examples/game/test_physics.dart | 1 + .../flutter_sprites/lib/physics_body.dart | 2 +- .../flutter_sprites/lib/physics_node.dart | 20 +++++++++++++++++++ 3 files changed, 22 insertions(+), 1 deletion(-) diff --git a/examples/game/test_physics.dart b/examples/game/test_physics.dart index a7c23f9b11..2a9ad884ac 100644 --- a/examples/game/test_physics.dart +++ b/examples/game/test_physics.dart @@ -83,6 +83,7 @@ class TestBed extends NodeWithSize { void myCallback(PhysicsContactType type, PhysicsContact contact) { print("CONTACT type: $type"); + contact.nodeB.removeFromParent(); } bool handleEvent(SpriteBoxEvent event) { diff --git a/packages/flutter_sprites/lib/physics_body.dart b/packages/flutter_sprites/lib/physics_body.dart index 99c8ea426e..e9446b65e1 100644 --- a/packages/flutter_sprites/lib/physics_body.dart +++ b/packages/flutter_sprites/lib/physics_body.dart @@ -120,7 +120,7 @@ class PhysicsBody { void _detach() { if (_attached) { - _physicsNode.b2World.destroyBody(_body); + _physicsNode._bodiesScheduledForDestruction.add(_body); _attached = false; } } diff --git a/packages/flutter_sprites/lib/physics_node.dart b/packages/flutter_sprites/lib/physics_node.dart index bc19c6adbd..12ea4f8531 100644 --- a/packages/flutter_sprites/lib/physics_node.dart +++ b/packages/flutter_sprites/lib/physics_node.dart @@ -31,6 +31,8 @@ class PhysicsNode extends Node { _ContactHandler _contactHandler; + List _bodiesScheduledForDestruction = []; + double b2WorldToNodeConversionFactor = 500.0; Offset get gravity { @@ -57,6 +59,9 @@ class PhysicsNode extends Node { } void _stepPhysics(double dt) { + // Remove bodies that were marked for destruction during the update phase + _removeBodiesScheduledForDestruction(); + // Calculate a step in the simulation b2World.stepDt(dt, 10, 10); @@ -71,6 +76,16 @@ class PhysicsNode extends Node { body._node._setRotationFromPhysics(degrees(b2Body.getAngle())); } + + // Remove bodies that were marked for destruction during the simulation + _removeBodiesScheduledForDestruction(); + } + + void _removeBodiesScheduledForDestruction() { + for (box2d.Body b2Body in _bodiesScheduledForDestruction) { + b2World.destroyBody(b2Body); + } + _bodiesScheduledForDestruction.clear(); } void _updatePosition(PhysicsBody body, Point position) { @@ -257,6 +272,11 @@ class _ContactHandler extends box2d.ContactListener { b2Contact.isTouching(), b2Contact.isEnabled() ); + + if (type == PhysicsContactType.postSolve) { + + } + // Make callback info.callback(type, contact); From 90a0f6300f81da2e0a995536cb2e36fbffba5eaa Mon Sep 17 00:00:00 2001 From: Hixie Date: Fri, 2 Oct 2015 15:07:35 -0700 Subject: [PATCH 27/27] Simplify the usage of Navigator's routes argument (These are changes cherry-picked from in-flight branches since they are more independent and could be helpful even without those changes.) - Change RouteBuilder's signature to take a single argument in which the other fields are placed, so that we can keep iterating on those arguments without having to break compatibility each time. Also, this makes defining route builders much simpler (only one argument to ignore rather than a variable number). - Expose the next performance to RouteBuilders, since sometimes the route itself might not be where it's used. - Allow BuildContext to be used to walk children, just like it can for ancestors - Allow BuildContext to be used to get the Widget of the current BuildContext - Allow StatefulComponentElement to be referenced with a type specialisation so that you don't have to cast when you know what the type you're dealing with actually is. --- examples/address_book/lib/main.dart | 2 +- examples/demo_launcher/lib/main.dart | 2 +- examples/fitness/lib/main.dart | 74 +++++++++---------- examples/game/example_effect_line.dart | 2 +- examples/game/lib/main.dart | 8 +- examples/game/test_drawatlas.dart | 2 +- examples/game/test_physics.dart | 2 +- examples/stocks/lib/main.dart | 6 +- examples/widgets/card_collection.dart | 2 +- examples/widgets/drag_and_drop.dart | 2 +- examples/widgets/navigation.dart | 16 ++-- examples/widgets/overlay_geometry.dart | 2 +- examples/widgets/pageable_list.dart | 2 +- packages/flutter/lib/src/widgets/dialog.dart | 8 +- packages/flutter/lib/src/widgets/drawer.dart | 3 - .../flutter/lib/src/widgets/framework.dart | 27 ++++--- .../flutter/lib/src/widgets/navigator.dart | 12 ++- packages/unit/test/widget/draggable_test.dart | 4 +- packages/unit/test/widget/navigator_test.dart | 4 +- 19 files changed, 94 insertions(+), 86 deletions(-) diff --git a/examples/address_book/lib/main.dart b/examples/address_book/lib/main.dart index c12a7d2114..2bca9be10f 100644 --- a/examples/address_book/lib/main.dart +++ b/examples/address_book/lib/main.dart @@ -101,7 +101,7 @@ void main() { title: 'Address Book', theme: theme, routes: { - '/': (NavigatorState navigator, Route route) => new AddressBookHome(navigator: navigator) + '/': (RouteArguments args) => new AddressBookHome(navigator: args.navigator) } )); } diff --git a/examples/demo_launcher/lib/main.dart b/examples/demo_launcher/lib/main.dart index 4fc089c33d..2bbef2362b 100644 --- a/examples/demo_launcher/lib/main.dart +++ b/examples/demo_launcher/lib/main.dart @@ -206,7 +206,7 @@ void main() { title: 'Flutter Demos', theme: _theme, routes: { - '/': (NavigatorState navigator, Route route) => new DemoHome() + '/': (RouteArguments args) => new DemoHome() } )); } diff --git a/examples/fitness/lib/main.dart b/examples/fitness/lib/main.dart index 1f12e111a5..98f87db579 100644 --- a/examples/fitness/lib/main.dart +++ b/examples/fitness/lib/main.dart @@ -92,8 +92,6 @@ class FitnessApp extends StatefulComponent { class FitnessAppState extends State { UserDataImpl _userData; - Map _routes; - void initState() { super.initState(); loadFitnessData().then((UserData data) { @@ -102,36 +100,6 @@ class FitnessAppState extends State { print("Failed to load data: $e"); setState(() => _userData = new UserDataImpl()); }); - - _routes = { - '/': (NavigatorState navigator, Route route) { - return new FeedFragment( - navigator: navigator, - userData: _userData, - onItemCreated: _handleItemCreated, - onItemDeleted: _handleItemDeleted - ); - }, - '/meals/new': (navigator, route) { - return new MealFragment( - navigator: navigator, - onCreated: _handleItemCreated - ); - }, - '/measurements/new': (NavigatorState navigator, Route route) { - return new MeasurementFragment( - navigator: navigator, - onCreated: _handleItemCreated - ); - }, - '/settings': (navigator, route) { - return new SettingsFragment( - navigator: navigator, - userData: _userData, - updater: settingsUpdater - ); - } - }; } void _handleItemCreated(FitnessItem item) { @@ -158,17 +126,43 @@ class FitnessAppState extends State { }); } - final ThemeData _theme = new ThemeData( - brightness: ThemeBrightness.light, - primarySwatch: Colors.indigo, - accentColor: Colors.pinkAccent[200] - ); - Widget build(BuildContext) { return new App( - theme: _theme, + theme: new ThemeData( + brightness: ThemeBrightness.light, + primarySwatch: Colors.indigo, + accentColor: Colors.pinkAccent[200] + ), title: 'Fitness', - routes: _routes + routes: { + '/': (RouteArguments args) { + return new FeedFragment( + navigator: args.navigator, + userData: _userData, + onItemCreated: _handleItemCreated, + onItemDeleted: _handleItemDeleted + ); + }, + '/meals/new': (RouteArguments args) { + return new MealFragment( + navigator: args.navigator, + onCreated: _handleItemCreated + ); + }, + '/measurements/new': (RouteArguments args) { + return new MeasurementFragment( + navigator: args.navigator, + onCreated: _handleItemCreated + ); + }, + '/settings': (RouteArguments args) { + return new SettingsFragment( + navigator: args.navigator, + userData: _userData, + updater: settingsUpdater + ); + } + } ); } } diff --git a/examples/game/example_effect_line.dart b/examples/game/example_effect_line.dart index 4a6e7cc150..05323d00d9 100644 --- a/examples/game/example_effect_line.dart +++ b/examples/game/example_effect_line.dart @@ -62,7 +62,7 @@ class TestAppState extends State { ); } - Column _buildColumn(NavigatorState navigator, Route route) { + Column _buildColumn(RouteArguments args) { return new Column([ new Flexible(child: _buildSpriteWidget()), _buildTabBar() diff --git a/examples/game/lib/main.dart b/examples/game/lib/main.dart index a93038d44d..2cb407aa39 100644 --- a/examples/game/lib/main.dart +++ b/examples/game/lib/main.dart @@ -92,11 +92,11 @@ class GameDemoState extends State { ); } - Widget _buildGameScene(NavigatorState navigator, Route route) { + Widget _buildGameScene(RouteArguments args) { return new SpriteWidget(_game, SpriteBoxTransformMode.fixedWidth); } - Widget _buildMainScene(navigator, route) { + Widget _buildMainScene(RouteArguments args) { return new Stack([ new SpriteWidget(new MainScreenBackground(), SpriteBoxTransformMode.fixedWidth), new Column([ @@ -109,10 +109,10 @@ class GameDemoState extends State { _sounds, (lastScore) { setState(() {_lastScore = lastScore;}); - navigator.pop(); + args.navigator.pop(); } ); - navigator.pushNamed('/game'); + args.navigator.pushNamed('/game'); }, texture: _spriteSheetUI['btn_play_up.png'], textureDown: _spriteSheetUI['btn_play_down.png'], diff --git a/examples/game/test_drawatlas.dart b/examples/game/test_drawatlas.dart index 135334eccc..dba1748a25 100644 --- a/examples/game/test_drawatlas.dart +++ b/examples/game/test_drawatlas.dart @@ -36,7 +36,7 @@ main() async { title: 'Test drawAtlas', theme: _theme, routes: { - '/': (NavigatorState navigator, Route route) { + '/': (RouteArguments args) { return new SpriteWidget( new TestDrawAtlas(), SpriteBoxTransformMode.fixedWidth diff --git a/examples/game/test_physics.dart b/examples/game/test_physics.dart index 2a9ad884ac..aabb773bd2 100644 --- a/examples/game/test_physics.dart +++ b/examples/game/test_physics.dart @@ -34,7 +34,7 @@ main() async { primarySwatch: Colors.purple ), routes: { - '/': (navigator, route) { + '/': (RouteArguments args) { return new SpriteWidget( new TestBed(), SpriteBoxTransformMode.letterbox diff --git a/examples/stocks/lib/main.dart b/examples/stocks/lib/main.dart index 4b2c1e89c3..a931d547ea 100644 --- a/examples/stocks/lib/main.dart +++ b/examples/stocks/lib/main.dart @@ -82,7 +82,7 @@ class StocksAppState extends State { if (path.length != 3) return null; if (_stocks.containsKey(path[2])) - return (navigator, route) => new StockSymbolViewer(navigator, _stocks[path[2]]); + return (RouteArguments args) => new StockSymbolViewer(args.navigator, _stocks[path[2]]); return null; } return null; @@ -93,8 +93,8 @@ class StocksAppState extends State { title: 'Stocks', theme: theme, routes: { - '/': (navigator, route) => new StockHome(navigator, _stocks, _symbols, _optimismSetting, modeUpdater), - '/settings': (navigator, route) => new StockSettings(navigator, _optimismSetting, _backupSetting, settingsUpdater) + '/': (RouteArguments args) => new StockHome(args.navigator, _stocks, _symbols, _optimismSetting, modeUpdater), + '/settings': (RouteArguments args) => new StockSettings(args.navigator, _optimismSetting, _backupSetting, settingsUpdater) }, onGenerateRoute: _getRoute ); diff --git a/examples/widgets/card_collection.dart b/examples/widgets/card_collection.dart index 0e7a06c4f5..fb0458b0ab 100644 --- a/examples/widgets/card_collection.dart +++ b/examples/widgets/card_collection.dart @@ -357,7 +357,7 @@ void main() { accentColor: Colors.redAccent[200] ), routes: { - '/': (NavigatorState navigator, Route route) => new CardCollection(navigator: navigator), + '/': (RouteArguments args) => new CardCollection(navigator: args.navigator), } )); } diff --git a/examples/widgets/drag_and_drop.dart b/examples/widgets/drag_and_drop.dart index c7a618763d..f35934c3af 100644 --- a/examples/widgets/drag_and_drop.dart +++ b/examples/widgets/drag_and_drop.dart @@ -133,7 +133,7 @@ void main() { runApp(new App( title: 'Drag and Drop Flutter Demo', routes: { - '/': (NavigatorState navigator, Route route) => new DragAndDropApp(navigator: navigator) + '/': (RouteArguments args) => new DragAndDropApp(navigator: args.navigator) } )); } diff --git a/examples/widgets/navigation.dart b/examples/widgets/navigation.dart index c9dd69b161..7ade991408 100644 --- a/examples/widgets/navigation.dart +++ b/examples/widgets/navigation.dart @@ -6,46 +6,46 @@ import 'package:sky/material.dart'; import 'package:sky/widgets.dart'; final Map routes = { - '/': (NavigatorState navigator, Route route) => new Container( + '/': (RouteArguments args) => new Container( padding: const EdgeDims.all(30.0), decoration: new BoxDecoration(backgroundColor: const Color(0xFFCCCCCC)), child: new Column([ new Text("You are at home"), new RaisedButton( child: new Text('GO SHOPPING'), - onPressed: () => navigator.pushNamed('/shopping') + onPressed: () => args.navigator.pushNamed('/shopping') ), new RaisedButton( child: new Text('START ADVENTURE'), - onPressed: () => navigator.pushNamed('/adventure') + onPressed: () => args.navigator.pushNamed('/adventure') )], justifyContent: FlexJustifyContent.center ) ), - '/shopping': (NavigatorState navigator, Route route) => new Container( + '/shopping': (RouteArguments args) => new Container( padding: const EdgeDims.all(20.0), decoration: new BoxDecoration(backgroundColor: const Color(0xFFBF5FFF)), child: new Column([ new Text("Village Shop"), new RaisedButton( child: new Text('RETURN HOME'), - onPressed: () => navigator.pop() + onPressed: () => args.navigator.pop() ), new RaisedButton( child: new Text('GO TO DUNGEON'), - onPressed: () => navigator.pushNamed('/adventure') + onPressed: () => args.navigator.pushNamed('/adventure') )], justifyContent: FlexJustifyContent.center ) ), - '/adventure': (NavigatorState navigator, Route route) => new Container( + '/adventure': (RouteArguments args) => new Container( padding: const EdgeDims.all(20.0), decoration: new BoxDecoration(backgroundColor: const Color(0xFFDC143C)), child: new Column([ new Text("Monster's Lair"), new RaisedButton( child: new Text('RUN!!!'), - onPressed: () => navigator.pop() + onPressed: () => args.navigator.pop() )], justifyContent: FlexJustifyContent.center ) diff --git a/examples/widgets/overlay_geometry.dart b/examples/widgets/overlay_geometry.dart index 8a525204d4..6aa66e91ed 100644 --- a/examples/widgets/overlay_geometry.dart +++ b/examples/widgets/overlay_geometry.dart @@ -165,7 +165,7 @@ void main() { ), title: 'Cards', routes: { - '/': (navigator, route) => new OverlayGeometryApp() + '/': (RouteArguments args) => new OverlayGeometryApp() } )); } diff --git a/examples/widgets/pageable_list.dart b/examples/widgets/pageable_list.dart index c00da4b488..e68ebbc87a 100644 --- a/examples/widgets/pageable_list.dart +++ b/examples/widgets/pageable_list.dart @@ -165,7 +165,7 @@ void main() { accentColor: Colors.redAccent[200] ), routes: { - '/': (NavigatorState navigator, Route route) => new PageableListApp(navigator: navigator), + '/': (RouteArguments args) => new PageableListApp(navigator: args.navigator), } )); } diff --git a/packages/flutter/lib/src/widgets/dialog.dart b/packages/flutter/lib/src/widgets/dialog.dart index 54c2a6f174..4efe02b5a9 100644 --- a/packages/flutter/lib/src/widgets/dialog.dart +++ b/packages/flutter/lib/src/widgets/dialog.dart @@ -145,7 +145,7 @@ class DialogRoute extends Route { return new FadeTransition( performance: performance, opacity: new AnimatedValue(0.0, end: 1.0, curve: easeOut), - child: builder(navigator, this) + child: builder(new RouteArguments(navigator: navigator, previousPerformance: this.performance, nextPerformance: nextRoutePerformance)) ); } @@ -159,11 +159,11 @@ Future showDialog(NavigatorState navigator, DialogBuilder builder) { Completer completer = new Completer(); navigator.push(new DialogRoute( completer: completer, - builder: (navigator, route) { + builder: (RouteArguments args) { return new Focus( - key: new GlobalObjectKey(route), + key: new GlobalObjectKey(completer), autofocus: true, - child: builder(navigator) + child: builder(args.navigator) ); } )); diff --git a/packages/flutter/lib/src/widgets/drawer.dart b/packages/flutter/lib/src/widgets/drawer.dart index 945542c5dd..1d73b9fc84 100644 --- a/packages/flutter/lib/src/widgets/drawer.dart +++ b/packages/flutter/lib/src/widgets/drawer.dart @@ -2,8 +2,6 @@ // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. -import 'dart:async'; - import 'package:sky/animation.dart'; import 'package:sky/material.dart'; import 'package:sky/src/widgets/animated_container.dart'; @@ -11,7 +9,6 @@ import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/gesture_detector.dart'; import 'package:sky/src/widgets/navigator.dart'; -import 'package:sky/src/widgets/scrollable.dart'; import 'package:sky/src/widgets/theme.dart'; import 'package:sky/src/widgets/transitions.dart'; import 'package:sky/src/widgets/focus.dart'; diff --git a/packages/flutter/lib/src/widgets/framework.dart b/packages/flutter/lib/src/widgets/framework.dart index c0cce2b28f..0d769af67c 100644 --- a/packages/flutter/lib/src/widgets/framework.dart +++ b/packages/flutter/lib/src/widgets/framework.dart @@ -486,9 +486,11 @@ final _InactiveElements _inactiveElements = new _InactiveElements(); typedef void ElementVisitor(Element element); abstract class BuildContext { - InheritedWidget inheritedWidgetOfType(Type targetType); + Widget get widget; RenderObject findRenderObject(); + InheritedWidget inheritedWidgetOfType(Type targetType); void visitAncestorElements(bool visitor(Element element)); + void visitChildElements(void visitor(Element element)); } /// Elements are the instantiations of Widget configurations. @@ -540,6 +542,13 @@ abstract class Element implements BuildContext { /// Calls the argument for each child. Must be overridden by subclasses that support having children. void visitChildren(ElementVisitor visitor) { } + /// Wrapper around visitChildren for BuildContext. + void visitChildElements(void visitor(Element element)) { + // don't allow visitChildElements() during build, since children aren't necessarily built yet + assert(BuildableElement._debugStateLockLevel == 0); + visitChildren(visitor); + } + bool detachChild(Element child) => false; /// This method is the core of the system. @@ -952,11 +961,11 @@ abstract class BuildableElement extends Element { /// Instantiation of StatelessComponent widgets. class StatelessComponentElement extends BuildableElement { - StatelessComponentElement(StatelessComponent widget) : super(widget) { + StatelessComponentElement(T widget) : super(widget) { _builder = widget.build; } - void update(StatelessComponent newWidget) { + void update(T newWidget) { super.update(newWidget); assert(widget == newWidget); _builder = widget.build; @@ -966,10 +975,10 @@ class StatelessComponentElement extends BuildableE } /// Instantiation of StatefulComponent widgets. -class StatefulComponentElement extends BuildableElement { - StatefulComponentElement(StatefulComponent widget) +class StatefulComponentElement> extends BuildableElement { + StatefulComponentElement(T widget) : _state = widget.createState(), super(widget) { - assert(_state._debugTypesAreRight(widget)); + assert(_state._debugTypesAreRight(widget)); // can't use T and U, since normally we don't actually set those assert(_state._element == null); _state._element = this; assert(_builder == null); @@ -992,10 +1001,10 @@ class StatefulComponentElement extends BuildableElement { assert(() { _state._debugLifecycleState = _StateLifecycle.ready; return true; }); } - State get state => _state; - State _state; + U get state => _state; + U _state; - void update(StatefulComponent newWidget) { + void update(T newWidget) { super.update(newWidget); assert(widget == newWidget); StatefulComponent oldConfig = _state._config; diff --git a/packages/flutter/lib/src/widgets/navigator.dart b/packages/flutter/lib/src/widgets/navigator.dart index 7df0d8fe0a..f47c70ad5b 100644 --- a/packages/flutter/lib/src/widgets/navigator.dart +++ b/packages/flutter/lib/src/widgets/navigator.dart @@ -8,7 +8,14 @@ import 'package:sky/src/widgets/focus.dart'; import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/transitions.dart'; -typedef Widget RouteBuilder(NavigatorState navigator, Route route); +class RouteArguments { + const RouteArguments({ this.navigator, this.previousPerformance, this.nextPerformance }); + final NavigatorState navigator; + final PerformanceView previousPerformance; + final PerformanceView nextPerformance; +} + +typedef Widget RouteBuilder(RouteArguments args); typedef RouteBuilder RouteGenerator(String name); typedef void StateRouteCallback(StateRoute route); typedef void NotificationCallback(); @@ -154,6 +161,7 @@ class NavigatorState extends State { } return new Focus(child: new Stack(visibleRoutes.reversed.toList())); } + } @@ -272,7 +280,7 @@ class PageRoute extends Route { child: new FadeTransition( performance: performance, opacity: new AnimatedValue(0.0, end: 1.0, curve: easeOut), - child: builder(navigator, this) + child: builder(new RouteArguments(navigator: navigator, previousPerformance: this.performance, nextPerformance: nextRoutePerformance)) ) ); } diff --git a/packages/unit/test/widget/draggable_test.dart b/packages/unit/test/widget/draggable_test.dart index 31120bc376..8e8b224d2a 100644 --- a/packages/unit/test/widget/draggable_test.dart +++ b/packages/unit/test/widget/draggable_test.dart @@ -13,9 +13,9 @@ void main() { tester.pumpWidget(new Navigator( routes: { - '/': (NavigatorState navigator, Route route) { return new Column([ + '/': (RouteArguments args) { return new Column([ new Draggable( - navigator: navigator, + navigator: args.navigator, data: 1, child: new Text('Source'), feedback: new Text('Dragging') diff --git a/packages/unit/test/widget/navigator_test.dart b/packages/unit/test/widget/navigator_test.dart index 5da23d03ef..a9bfcd5e63 100644 --- a/packages/unit/test/widget/navigator_test.dart +++ b/packages/unit/test/widget/navigator_test.dart @@ -49,8 +49,8 @@ void main() { test('Can navigator navigate to and from a stateful component', () { testWidgets((WidgetTester tester) { final Map routes = { - '/': (navigator, route) => new FirstComponent(navigator), - '/second': (navigator, route) => new SecondComponent(navigator), + '/': (RouteArguments args) => new FirstComponent(args.navigator), + '/second': (RouteArguments args) => new SecondComponent(args.navigator), }; tester.pumpWidget(new Navigator(routes: routes));