Commit 43a5dff8 authored by Kris Giesing's avatar Kris Giesing

Merge remote-tracking branch 'upstream/master'

parents c9986651 48142d68
...@@ -11,7 +11,7 @@ class Field extends StatelessComponent { ...@@ -11,7 +11,7 @@ class Field extends StatelessComponent {
this.inputKey, this.inputKey,
this.icon, this.icon,
this.placeholder this.placeholder
}): super(key: key); }) : super(key: key);
final GlobalKey inputKey; final GlobalKey inputKey;
final String icon; final String icon;
...@@ -101,7 +101,7 @@ void main() { ...@@ -101,7 +101,7 @@ void main() {
title: 'Address Book', title: 'Address Book',
theme: theme, theme: theme,
routes: <String, RouteBuilder>{ routes: <String, RouteBuilder>{
'/': (NavigatorState navigator, Route route) => new AddressBookHome(navigator: navigator) '/': (RouteArguments args) => new AddressBookHome(navigator: args.navigator)
} }
)); ));
} }
...@@ -42,8 +42,8 @@ void launch(String relativeUrl, String bundle) { ...@@ -42,8 +42,8 @@ void launch(String relativeUrl, String bundle) {
activity.startActivity(intent); activity.startActivity(intent);
} }
class SkyDemo { class FlutterDemo {
SkyDemo({ FlutterDemo({
name, name,
this.href, this.href,
this.bundle, this.bundle,
...@@ -60,8 +60,8 @@ class SkyDemo { ...@@ -60,8 +60,8 @@ class SkyDemo {
final BoxDecoration decoration; final BoxDecoration decoration;
} }
List<SkyDemo> demos = [ List<FlutterDemo> demos = [
new SkyDemo( new FlutterDemo(
name: 'Stocks', name: 'Stocks',
href: '../../stocks/lib/main.dart', href: '../../stocks/lib/main.dart',
bundle: 'stocks.skyx', bundle: 'stocks.skyx',
...@@ -74,7 +74,7 @@ List<SkyDemo> demos = [ ...@@ -74,7 +74,7 @@ List<SkyDemo> demos = [
) )
) )
), ),
new SkyDemo( new FlutterDemo(
name: 'Asteroids', name: 'Asteroids',
href: '../../game/lib/main.dart', href: '../../game/lib/main.dart',
bundle: 'game.skyx', bundle: 'game.skyx',
...@@ -87,7 +87,7 @@ List<SkyDemo> demos = [ ...@@ -87,7 +87,7 @@ List<SkyDemo> demos = [
) )
) )
), ),
new SkyDemo( new FlutterDemo(
name: 'Fitness', name: 'Fitness',
href: '../../fitness/lib/main.dart', href: '../../fitness/lib/main.dart',
bundle: 'fitness.skyx', bundle: 'fitness.skyx',
...@@ -97,7 +97,7 @@ List<SkyDemo> demos = [ ...@@ -97,7 +97,7 @@ List<SkyDemo> demos = [
backgroundColor: Colors.indigo[500] backgroundColor: Colors.indigo[500]
) )
), ),
new SkyDemo( new FlutterDemo(
name: 'Swipe Away', name: 'Swipe Away',
href: '../../widgets/card_collection.dart', href: '../../widgets/card_collection.dart',
bundle: 'cards.skyx', bundle: 'cards.skyx',
...@@ -107,7 +107,7 @@ List<SkyDemo> demos = [ ...@@ -107,7 +107,7 @@ List<SkyDemo> demos = [
backgroundColor: Colors.redAccent[200] backgroundColor: Colors.redAccent[200]
) )
), ),
new SkyDemo( new FlutterDemo(
name: 'Interactive Text', name: 'Interactive Text',
href: '../../rendering/interactive_flex.dart', href: '../../rendering/interactive_flex.dart',
bundle: 'interactive_flex.skyx', bundle: 'interactive_flex.skyx',
...@@ -120,7 +120,7 @@ List<SkyDemo> demos = [ ...@@ -120,7 +120,7 @@ List<SkyDemo> demos = [
// new SkyDemo( // new SkyDemo(
// 'Touch Demo', '../../rendering/touch_demo.dart', 'Simple example showing handling of touch events at a low level'), // 'Touch Demo', '../../rendering/touch_demo.dart', 'Simple example showing handling of touch events at a low level'),
new SkyDemo( new FlutterDemo(
name: 'Minedigger Game', name: 'Minedigger Game',
href: '../../mine_digger/lib/main.dart', href: '../../mine_digger/lib/main.dart',
bundle: 'mine_digger.skyx', bundle: 'mine_digger.skyx',
...@@ -138,11 +138,19 @@ List<SkyDemo> demos = [ ...@@ -138,11 +138,19 @@ List<SkyDemo> demos = [
const double kCardHeight = 120.0; const double kCardHeight = 120.0;
const EdgeDims kListPadding = const EdgeDims.all(4.0); const EdgeDims kListPadding = const EdgeDims.all(4.0);
class DemoList extends StatelessComponent { class DemoCard extends StatelessComponent {
Widget buildCardContents(SkyDemo demo) { DemoCard({ Key key, this.demo }) : super(key: key);
final FlutterDemo demo;
Widget build(BuildContext context) {
return new Container( return new Container(
height: kCardHeight,
child: new Card(
child: new Container(
decoration: demo.decoration, decoration: demo.decoration,
child: new InkWell( child: new InkWell(
onTap: () => launch(demo.href, demo.bundle),
child: new Container( child: new Container(
margin: const EdgeDims.only(top: 24.0, left: 24.0), margin: const EdgeDims.only(top: 24.0, left: 24.0),
child: new Column([ child: new Column([
...@@ -155,27 +163,22 @@ class DemoList extends StatelessComponent { ...@@ -155,27 +163,22 @@ class DemoList extends StatelessComponent {
) )
) )
) )
);
}
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) { Widget build(BuildContext context) {
return new ScrollableList<SkyDemo>( return new ScrollableList<FlutterDemo>(
items: demos, items: demos,
itemExtent: kCardHeight, itemExtent: kCardHeight,
itemBuilder: buildDemo, itemBuilder: _buildDemoCard,
padding: kListPadding padding: kListPadding
); );
} }
...@@ -200,10 +203,10 @@ class DemoHome extends StatelessComponent { ...@@ -200,10 +203,10 @@ class DemoHome extends StatelessComponent {
void main() { void main() {
runApp(new App( runApp(new App(
title: 'Sky Demos', title: 'Flutter Demos',
theme: _theme, theme: _theme,
routes: { routes: {
'/': (NavigatorState navigator, Route route) => new DemoHome() '/': (RouteArguments args) => new DemoHome()
} }
)); ));
} }
...@@ -33,17 +33,15 @@ class DialogMenuItem extends StatelessComponent { ...@@ -33,17 +33,15 @@ class DialogMenuItem extends StatelessComponent {
Function onPressed; Function onPressed;
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new GestureDetector( return new Container(
onTap: onPressed,
child: new Container(
height: 48.0, height: 48.0,
child: new InkWell( child: new InkWell(
onTap: onPressed,
child: new Padding( child: new Padding(
padding: const EdgeDims.symmetric(horizontal: 16.0), padding: const EdgeDims.symmetric(horizontal: 16.0),
child: new Row(children) child: new Row(children)
) )
) )
)
); );
} }
} }
...@@ -62,25 +60,20 @@ class FeedFragment extends StatefulComponent { ...@@ -62,25 +60,20 @@ class FeedFragment extends StatefulComponent {
class FeedFragmentState extends State<FeedFragment> { class FeedFragmentState extends State<FeedFragment> {
FitnessMode _fitnessMode = FitnessMode.feed; FitnessMode _fitnessMode = FitnessMode.feed;
AnimationStatus _snackBarStatus = AnimationStatus.dismissed; PerformanceStatus _snackBarStatus = PerformanceStatus.dismissed;
bool _isShowingSnackBar = false; bool _isShowingSnackBar = false;
void _handleFitnessModeChange(FitnessMode value) { void _handleFitnessModeChange(FitnessMode value) {
setState(() { setState(() {
_fitnessMode = value; _fitnessMode = value;
_drawerShowing = false;
}); });
config.navigator.pop();
} }
Drawer buildDrawer() { void _showDrawer() {
if (_drawerStatus == AnimationStatus.dismissed) showDrawer(
return null;
return new Drawer(
showing: _drawerShowing,
level: 3,
onDismissed: _handleDrawerDismissed,
navigator: config.navigator, navigator: config.navigator,
children: [ child: new Block([
new DrawerHeader(child: new Text('Fitness')), new DrawerHeader(child: new Text('Fitness')),
new DrawerItem( new DrawerItem(
icon: 'action/view_list', icon: 'action/view_list',
...@@ -100,26 +93,10 @@ class FeedFragmentState extends State<FeedFragment> { ...@@ -100,26 +93,10 @@ class FeedFragmentState extends State<FeedFragment> {
new DrawerItem( new DrawerItem(
icon: 'action/help', icon: 'action/help',
child: new Text('Help & Feedback')) 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() { void _handleShowSettings() {
config.navigator.pop(); config.navigator.pop();
config.navigator.pushNamed('/settings'); config.navigator.pushNamed('/settings');
...@@ -137,7 +114,7 @@ class FeedFragmentState extends State<FeedFragment> { ...@@ -137,7 +114,7 @@ class FeedFragmentState extends State<FeedFragment> {
return new ToolBar( return new ToolBar(
left: new IconButton( left: new IconButton(
icon: "navigation/menu", icon: "navigation/menu",
onPressed: _handleOpenDrawer), onPressed: _showDrawer),
center: new Text(fitnessModeTitle) center: new Text(fitnessModeTitle)
); );
} }
...@@ -149,7 +126,7 @@ class FeedFragmentState extends State<FeedFragment> { ...@@ -149,7 +126,7 @@ class FeedFragmentState extends State<FeedFragment> {
setState(() { setState(() {
_undoItem = item; _undoItem = item;
_isShowingSnackBar = true; _isShowingSnackBar = true;
_snackBarStatus = AnimationStatus.forward; _snackBarStatus = PerformanceStatus.forward;
}); });
} }
...@@ -230,13 +207,13 @@ class FeedFragmentState extends State<FeedFragment> { ...@@ -230,13 +207,13 @@ class FeedFragmentState extends State<FeedFragment> {
} }
Widget buildSnackBar() { Widget buildSnackBar() {
if (_snackBarStatus == AnimationStatus.dismissed) if (_snackBarStatus == PerformanceStatus.dismissed)
return null; return null;
return new SnackBar( return new SnackBar(
showing: _isShowingSnackBar, showing: _isShowingSnackBar,
content: new Text("Item deleted."), content: new Text("Item deleted."),
actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)], actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)],
onDismissed: () { setState(() { _snackBarStatus = AnimationStatus.dismissed; }); } onDismissed: () { setState(() { _snackBarStatus = PerformanceStatus.dismissed; }); }
); );
} }
...@@ -264,8 +241,7 @@ class FeedFragmentState extends State<FeedFragment> { ...@@ -264,8 +241,7 @@ class FeedFragmentState extends State<FeedFragment> {
toolbar: buildToolBar(), toolbar: buildToolBar(),
body: buildBody(), body: buildBody(),
snackBar: buildSnackBar(), snackBar: buildSnackBar(),
floatingActionButton: buildFloatingActionButton(), floatingActionButton: buildFloatingActionButton()
drawer: buildDrawer()
); );
} }
} }
......
...@@ -92,8 +92,6 @@ class FitnessApp extends StatefulComponent { ...@@ -92,8 +92,6 @@ class FitnessApp extends StatefulComponent {
class FitnessAppState extends State<FitnessApp> { class FitnessAppState extends State<FitnessApp> {
UserDataImpl _userData; UserDataImpl _userData;
Map<String, RouteBuilder> _routes;
void initState() { void initState() {
super.initState(); super.initState();
loadFitnessData().then((UserData data) { loadFitnessData().then((UserData data) {
...@@ -102,36 +100,6 @@ class FitnessAppState extends State<FitnessApp> { ...@@ -102,36 +100,6 @@ class FitnessAppState extends State<FitnessApp> {
print("Failed to load data: $e"); print("Failed to load data: $e");
setState(() => _userData = new UserDataImpl()); 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) { void _handleItemCreated(FitnessItem item) {
...@@ -158,17 +126,43 @@ class FitnessAppState extends State<FitnessApp> { ...@@ -158,17 +126,43 @@ class FitnessAppState extends State<FitnessApp> {
}); });
} }
final ThemeData _theme = new ThemeData( Widget build(BuildContext) {
return new App(
theme: new ThemeData(
brightness: ThemeBrightness.light, brightness: ThemeBrightness.light,
primarySwatch: Colors.indigo, primarySwatch: Colors.indigo,
accentColor: Colors.pinkAccent[200] accentColor: Colors.pinkAccent[200]
); ),
Widget build(BuildContext) {
return new App(
theme: _theme,
title: 'Fitness', 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
);
}
}
); );
} }
} }
......
...@@ -65,12 +65,13 @@ class MealFragmentState extends State<MealFragment> { ...@@ -65,12 +65,13 @@ class MealFragmentState extends State<MealFragment> {
icon: "navigation/close", icon: "navigation/close",
onPressed: config.navigator.pop), onPressed: config.navigator.pop),
center: new Text('New Meal'), center: new Text('New Meal'),
right: [new InkWell( right: [
child: new GestureDetector( // TODO(abarth): Should this be a FlatButton?
new InkWell(
onTap: _handleSave, onTap: _handleSave,
child: new Text('SAVE') child: new Text('SAVE')
) )
)] ]
); );
} }
......
...@@ -136,12 +136,13 @@ class MeasurementFragmentState extends State<MeasurementFragment> { ...@@ -136,12 +136,13 @@ class MeasurementFragmentState extends State<MeasurementFragment> {
icon: "navigation/close", icon: "navigation/close",
onPressed: config.navigator.pop), onPressed: config.navigator.pop),
center: new Text('New Measurement'), center: new Text('New Measurement'),
right: [new InkWell( right: [
child: new GestureDetector( // TODO(abarth): Should this be a FlatButton?
new InkWell(
onTap: _handleSave, onTap: _handleSave,
child: new Text('SAVE') child: new Text('SAVE')
) )
)] ]
); );
} }
......
...@@ -62,7 +62,7 @@ class TestAppState extends State<TestApp> { ...@@ -62,7 +62,7 @@ class TestAppState extends State<TestApp> {
); );
} }
Column _buildColumn(NavigatorState navigator, Route route) { Column _buildColumn(RouteArguments args) {
return new Column([ return new Column([
new Flexible(child: _buildSpriteWidget()), new Flexible(child: _buildSpriteWidget()),
_buildTabBar() _buildTabBar()
......
...@@ -92,11 +92,11 @@ class GameDemoState extends State<GameDemo> { ...@@ -92,11 +92,11 @@ class GameDemoState extends State<GameDemo> {
); );
} }
Widget _buildGameScene(NavigatorState navigator, Route route) { Widget _buildGameScene(RouteArguments args) {
return new SpriteWidget(_game, SpriteBoxTransformMode.fixedWidth); return new SpriteWidget(_game, SpriteBoxTransformMode.fixedWidth);
} }
Widget _buildMainScene(navigator, route) { Widget _buildMainScene(RouteArguments args) {
return new Stack([ return new Stack([
new SpriteWidget(new MainScreenBackground(), SpriteBoxTransformMode.fixedWidth), new SpriteWidget(new MainScreenBackground(), SpriteBoxTransformMode.fixedWidth),
new Column([ new Column([
...@@ -109,10 +109,10 @@ class GameDemoState extends State<GameDemo> { ...@@ -109,10 +109,10 @@ class GameDemoState extends State<GameDemo> {
_sounds, _sounds,
(lastScore) { (lastScore) {
setState(() {_lastScore = lastScore;}); setState(() {_lastScore = lastScore;});
navigator.pop(); args.navigator.pop();
} }
); );
navigator.pushNamed('/game'); args.navigator.pushNamed('/game');
}, },
texture: _spriteSheetUI['btn_play_up.png'], texture: _spriteSheetUI['btn_play_up.png'],
textureDown: _spriteSheetUI['btn_play_down.png'], textureDown: _spriteSheetUI['btn_play_down.png'],
......
...@@ -3,6 +3,7 @@ dependencies: ...@@ -3,6 +3,7 @@ dependencies:
sky: any sky: any
sky_tools: any sky_tools: any
skysprites: any skysprites: any
box2d: any
dependency_overrides: dependency_overrides:
material_design_icons: material_design_icons:
path: ../../sky/packages/material_design_icons path: ../../sky/packages/material_design_icons
......
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)) {
}
}
...@@ -36,7 +36,7 @@ main() async { ...@@ -36,7 +36,7 @@ main() async {
title: 'Test drawAtlas', title: 'Test drawAtlas',
theme: _theme, theme: _theme,
routes: { routes: {
'/': (NavigatorState navigator, Route route) { '/': (RouteArguments args) {
return new SpriteWidget( return new SpriteWidget(
new TestDrawAtlas(), new TestDrawAtlas(),
SpriteBoxTransformMode.fixedWidth SpriteBoxTransformMode.fixedWidth
......
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;
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);
runApp(new App(
title: 'Test Physics',
theme: new ThemeData(
brightness: ThemeBrightness.light,
primarySwatch: Colors.purple
),
routes: {
'/': (RouteArguments args) {
return 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");
contact.nodeB.removeFromParent();
}
bool handleEvent(SpriteBoxEvent event) {
if (event.type == "pointerdown") {
Point pos = convertPointToNodeSpace(event.boxPosition);
_ship.position = pos;
}
return true;
}
}
...@@ -64,33 +64,25 @@ class MineDiggerState extends State<MineDigger> { ...@@ -64,33 +64,25 @@ class MineDiggerState extends State<MineDigger> {
alive = true; alive = true;
hasWon = false; hasWon = false;
detectedCount = 0; detectedCount = 0;
// Build the arrays. // Initialize matrices.
cells = new List<List<bool>>(); cells = new List<List>.generate(rows, (int row) {
uiState = new List<List<CellState>>(); return new List<bool>.filled(cols, false);
for (int iy = 0; iy != rows; iy++) { });
cells.add(new List<bool>()); uiState = new List<List>.generate(rows, (int row) {
uiState.add(new List<CellState>()); return new List<CellState>.filled(cols, CellState.covered);
for (int ix = 0; ix != cols; ix++) { });
cells[iy].add(false);
uiState[iy].add(CellState.covered);
}
}
// Place the mines. // Place the mines.
Random random = new Random(); Random random = new Random();
int cellsRemaining = rows * cols;
int minesRemaining = totalMineCount; int minesRemaining = totalMineCount;
for (int x = 0; x < cols; x += 1) { while (minesRemaining > 0) {
for (int y = 0; y < rows; y += 1) { int pos = random.nextInt(rows * cols);
if (random.nextInt(cellsRemaining) < minesRemaining) { int row = pos ~/ rows;
cells[y][x] = true; int col = pos % cols;
minesRemaining -= 1; if (!cells[row][col]) {
if (minesRemaining <= 0) cells[row][col] = true;
return; minesRemaining--;
}
cellsRemaining -= 1;
} }
} }
assert(false);
} }
PointerEventListener _pointerDownHandlerFor(int posX, int posY) { PointerEventListener _pointerDownHandlerFor(int posX, int posY) {
...@@ -106,9 +98,9 @@ class MineDiggerState extends State<MineDigger> { ...@@ -106,9 +98,9 @@ class MineDiggerState extends State<MineDigger> {
Widget buildBoard() { Widget buildBoard() {
bool hasCoveredCell = false; bool hasCoveredCell = false;
List<Row> flexRows = <Row>[]; List<Row> flexRows = <Row>[];
for (int iy = 0; iy != 9; iy++) { for (int iy = 0; iy < rows; iy++) {
List<Widget> row = <Widget>[]; List<Widget> row = <Widget>[];
for (int ix = 0; ix != 9; ix++) { for (int ix = 0; ix < cols; ix++) {
CellState state = uiState[iy][ix]; CellState state = uiState[iy][ix];
int count = mineCount(ix, iy); int count = mineCount(ix, iy);
if (!alive) { if (!alive) {
......
...@@ -9,7 +9,7 @@ import 'package:sky/rendering.dart'; ...@@ -9,7 +9,7 @@ import 'package:sky/rendering.dart';
import 'solid_color_box.dart'; import 'solid_color_box.dart';
double timeBase; Duration timeBase;
RenderTransform transformBox; RenderTransform transformBox;
void main() { void main() {
...@@ -34,10 +34,10 @@ void main() { ...@@ -34,10 +34,10 @@ void main() {
scheduler.addPersistentFrameCallback(rotate); scheduler.addPersistentFrameCallback(rotate);
} }
void rotate(double timeStamp) { void rotate(Duration timeStamp) {
if (timeBase == null) if (timeBase == null)
timeBase = timeStamp; timeBase = timeStamp;
double delta = (timeStamp - timeBase) / 1000; // radians double delta = (timeStamp - timeBase).inMicroseconds.toDouble() / Duration.MICROSECONDS_PER_SECOND; // radians
transformBox.setIdentity(); transformBox.setIdentity();
transformBox.translate(transformBox.size.width / 2.0, transformBox.size.height / 2.0); transformBox.translate(transformBox.size.width / 2.0, transformBox.size.height / 2.0);
......
...@@ -82,7 +82,7 @@ class StocksAppState extends State<StocksApp> { ...@@ -82,7 +82,7 @@ class StocksAppState extends State<StocksApp> {
if (path.length != 3) if (path.length != 3)
return null; return null;
if (_stocks.containsKey(path[2])) 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;
} }
return null; return null;
...@@ -93,8 +93,8 @@ class StocksAppState extends State<StocksApp> { ...@@ -93,8 +93,8 @@ class StocksAppState extends State<StocksApp> {
title: 'Stocks', title: 'Stocks',
theme: theme, theme: theme,
routes: <String, RouteBuilder>{ routes: <String, RouteBuilder>{
'/': (navigator, route) => new StockHome(navigator, _stocks, _symbols, _optimismSetting, modeUpdater), '/': (RouteArguments args) => new StockHome(args.navigator, _stocks, _symbols, _optimismSetting, modeUpdater),
'/settings': (navigator, route) => new StockSettings(navigator, _optimismSetting, _backupSetting, settingsUpdater) '/settings': (RouteArguments args) => new StockSettings(args.navigator, _optimismSetting, _backupSetting, settingsUpdater)
}, },
onGenerateRoute: _getRoute onGenerateRoute: _getRoute
); );
......
...@@ -25,7 +25,7 @@ class StockHomeState extends State<StockHome> { ...@@ -25,7 +25,7 @@ class StockHomeState extends State<StockHome> {
bool _isSearching = false; bool _isSearching = false;
String _searchQuery; String _searchQuery;
AnimationStatus _snackBarStatus = AnimationStatus.dismissed; PerformanceStatus _snackBarStatus = PerformanceStatus.dismissed;
bool _isSnackBarShowing = false; bool _isSnackBarShowing = false;
void _handleSearchBegin() { void _handleSearchBegin() {
...@@ -56,22 +56,6 @@ class StockHomeState extends State<StockHome> { ...@@ -56,22 +56,6 @@ class StockHomeState extends State<StockHome> {
}); });
} }
bool _drawerShowing = false;
AnimationStatus _drawerStatus = AnimationStatus.dismissed;
void _handleOpenDrawer() {
setState(() {
_drawerShowing = true;
_drawerStatus = AnimationStatus.forward;
});
}
void _handleDrawerDismissed() {
setState(() {
_drawerStatus = AnimationStatus.dismissed;
});
}
bool _autorefresh = false; bool _autorefresh = false;
void _handleAutorefreshChanged(bool value) { void _handleAutorefreshChanged(bool value) {
setState(() { setState(() {
...@@ -91,16 +75,10 @@ class StockHomeState extends State<StockHome> { ...@@ -91,16 +75,10 @@ class StockHomeState extends State<StockHome> {
); );
} }
Drawer buildDrawer() { void _showDrawer() {
if (_drawerStatus == AnimationStatus.dismissed) showDrawer(
return null;
assert(_drawerShowing); // TODO(mpcomplete): this is always true
return new Drawer(
level: 3,
showing: _drawerShowing,
onDismissed: _handleDrawerDismissed,
navigator: config.navigator, navigator: config.navigator,
children: [ child: new Block([
new DrawerHeader(child: new Text('Stocks')), new DrawerHeader(child: new Text('Stocks')),
new DrawerItem( new DrawerItem(
icon: 'action/assessment', icon: 'action/assessment',
...@@ -141,7 +119,7 @@ class StockHomeState extends State<StockHome> { ...@@ -141,7 +119,7 @@ class StockHomeState extends State<StockHome> {
new DrawerItem( new DrawerItem(
icon: 'action/help', icon: 'action/help',
child: new Text('Help & Feedback')) child: new Text('Help & Feedback'))
] ])
); );
} }
...@@ -154,7 +132,7 @@ class StockHomeState extends State<StockHome> { ...@@ -154,7 +132,7 @@ class StockHomeState extends State<StockHome> {
return new ToolBar( return new ToolBar(
left: new IconButton( left: new IconButton(
icon: "navigation/menu", icon: "navigation/menu",
onPressed: _handleOpenDrawer onPressed: _showDrawer
), ),
center: new Text('Stocks'), center: new Text('Stocks'),
right: [ right: [
...@@ -246,20 +224,20 @@ class StockHomeState extends State<StockHome> { ...@@ -246,20 +224,20 @@ class StockHomeState extends State<StockHome> {
GlobalKey snackBarKey = new GlobalKey(label: 'snackbar'); GlobalKey snackBarKey = new GlobalKey(label: 'snackbar');
Widget buildSnackBar() { Widget buildSnackBar() {
if (_snackBarStatus == AnimationStatus.dismissed) if (_snackBarStatus == PerformanceStatus.dismissed)
return null; return null;
return new SnackBar( return new SnackBar(
showing: _isSnackBarShowing, showing: _isSnackBarShowing,
content: new Text("Stock purchased!"), content: new Text("Stock purchased!"),
actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)], actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)],
onDismissed: () { setState(() { _snackBarStatus = AnimationStatus.dismissed; }); } onDismissed: () { setState(() { _snackBarStatus = PerformanceStatus.dismissed; }); }
); );
} }
void _handleStockPurchased() { void _handleStockPurchased() {
setState(() { setState(() {
_isSnackBarShowing = true; _isSnackBarShowing = true;
_snackBarStatus = AnimationStatus.forward; _snackBarStatus = PerformanceStatus.forward;
}); });
} }
...@@ -276,8 +254,7 @@ class StockHomeState extends State<StockHome> { ...@@ -276,8 +254,7 @@ class StockHomeState extends State<StockHome> {
toolbar: _isSearching ? buildSearchBar() : buildToolBar(), toolbar: _isSearching ? buildSearchBar() : buildToolBar(),
body: buildTabNavigator(), body: buildTabNavigator(),
snackBar: buildSnackBar(), snackBar: buildSnackBar(),
floatingActionButton: buildFloatingActionButton(), floatingActionButton: buildFloatingActionButton()
drawer: buildDrawer()
); );
} }
} }
...@@ -37,13 +37,13 @@ class StockRow extends StatelessComponent { ...@@ -37,13 +37,13 @@ class StockRow extends StatelessComponent {
static const double kHeight = 79.0; static const double kHeight = 79.0;
GestureTapListener _getTapHandler(StockRowActionCallback callback) { GestureTapCallback _getTapHandler(StockRowActionCallback callback) {
if (callback == null) if (callback == null)
return null; return null;
return () => callback(stock, key, arrowKey, symbolKey, priceKey); return () => callback(stock, key, arrowKey, symbolKey, priceKey);
} }
GestureLongPressListener _getLongPressHandler(StockRowActionCallback callback) { GestureLongPressCallback _getLongPressHandler(StockRowActionCallback callback) {
if (callback == null) if (callback == null)
return null; return null;
return () => callback(stock, key, arrowKey, symbolKey, priceKey); return () => callback(stock, key, arrowKey, symbolKey, priceKey);
...@@ -55,10 +55,9 @@ class StockRow extends StatelessComponent { ...@@ -55,10 +55,9 @@ class StockRow extends StatelessComponent {
String changeInPrice = "${stock.percentChange.toStringAsFixed(2)}%"; String changeInPrice = "${stock.percentChange.toStringAsFixed(2)}%";
if (stock.percentChange > 0) changeInPrice = "+" + changeInPrice; if (stock.percentChange > 0) changeInPrice = "+" + changeInPrice;
return new GestureDetector( return new InkWell(
onTap: _getTapHandler(onPressed), onTap: _getTapHandler(onPressed),
onLongPress: _getLongPressHandler(onLongPressed), onLongPress: _getLongPressHandler(onLongPressed),
child: new InkWell(
child: new Container( child: new Container(
padding: const EdgeDims.TRBL(16.0, 16.0, 20.0, 16.0), padding: const EdgeDims.TRBL(16.0, 16.0, 20.0, 16.0),
decoration: new BoxDecoration( decoration: new BoxDecoration(
...@@ -101,7 +100,6 @@ class StockRow extends StatelessComponent { ...@@ -101,7 +100,6 @@ class StockRow extends StatelessComponent {
) )
]) ])
) )
)
); );
} }
} }
...@@ -2,7 +2,8 @@ ...@@ -2,7 +2,8 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:sky/animation.dart'; import 'dart:sky' as sky;
import 'package:sky/material.dart'; import 'package:sky/material.dart';
import 'package:sky/painting.dart'; import 'package:sky/painting.dart';
import 'package:sky/widgets.dart'; import 'package:sky/widgets.dart';
...@@ -16,15 +17,22 @@ class CardModel { ...@@ -16,15 +17,22 @@ class CardModel {
Key get key => new ObjectKey(this); Key get key => new ObjectKey(this);
} }
class CardCollectionApp extends StatefulComponent { class CardCollection extends StatefulComponent {
CardCollectionAppState createState() => new CardCollectionAppState(); CardCollection({ this.navigator });
final NavigatorState navigator;
CardCollectionState createState() => new CardCollectionState();
} }
class CardCollectionAppState extends State<CardCollectionApp> { class CardCollectionState extends State<CardCollection> {
static const TextStyle cardLabelStyle = static const TextStyle cardLabelStyle =
const TextStyle(color: Colors.white, fontSize: 18.0, fontWeight: bold); 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 = final TextStyle backgroundTextStyle =
Typography.white.title.copyWith(textAlign: TextAlign.center); Typography.white.title.copyWith(textAlign: TextAlign.center);
...@@ -32,8 +40,7 @@ class CardCollectionAppState extends State<CardCollectionApp> { ...@@ -32,8 +40,7 @@ class CardCollectionAppState extends State<CardCollectionApp> {
DismissDirection _dismissDirection = DismissDirection.horizontal; DismissDirection _dismissDirection = DismissDirection.horizontal;
bool _snapToCenter = false; bool _snapToCenter = false;
bool _fixedSizeCards = false; bool _fixedSizeCards = false;
bool _drawerShowing = false; bool _sunshine = false;
AnimationStatus _drawerStatus = AnimationStatus.dismissed;
InvalidatorCallback _invalidator; InvalidatorCallback _invalidator;
Size _cardCollectionSize = new Size(200.0, 200.0); Size _cardCollectionSize = new Size(200.0, 200.0);
...@@ -108,17 +115,23 @@ class CardCollectionAppState extends State<CardCollectionApp> { ...@@ -108,17 +115,23 @@ class CardCollectionAppState extends State<CardCollectionApp> {
} }
} }
void _handleOpenDrawer() { void _showDrawer() {
setState(() { showDrawer(
_drawerShowing = true; navigator: config.navigator,
_drawerStatus = AnimationStatus.forward; child: new IconTheme(
}); data: const IconThemeData(color: IconThemeColor.black),
} child: new Block([
new DrawerHeader(child: new Text('Options')),
void _handleDrawerDismissed() { buildDrawerCheckbox("Snap fling scrolls to center", _snapToCenter, _toggleSnapToCenter),
setState(() { buildDrawerCheckbox("Fixed size cards", _fixedSizeCards, _toggleFixedSizeCards),
_drawerStatus = AnimationStatus.dismissed; 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) { String _dismissDirectionText(DismissDirection direction) {
...@@ -139,16 +152,18 @@ class CardCollectionAppState extends State<CardCollectionApp> { ...@@ -139,16 +152,18 @@ class CardCollectionAppState extends State<CardCollectionApp> {
}); });
} }
_changeDismissDirection(DismissDirection newDismissDirection) { void _toggleSunshine() {
setState(() { setState(() {
_dismissDirection = newDismissDirection; _sunshine = !_sunshine;
_drawerStatus = AnimationStatus.dismissed;
}); });
} }
Widget buildDrawer() { void _changeDismissDirection(DismissDirection newDismissDirection) {
if (_drawerStatus == AnimationStatus.dismissed) setState(() {
return null; _dismissDirection = newDismissDirection;
});
config.navigator.pop();
}
Widget buildDrawerCheckbox(String label, bool value, Function callback) { Widget buildDrawerCheckbox(String label, bool value, Function callback) {
return new DrawerItem( return new DrawerItem(
...@@ -175,28 +190,9 @@ class CardCollectionAppState extends State<CardCollectionApp> { ...@@ -175,28 +190,9 @@ class CardCollectionAppState extends State<CardCollectionApp> {
); );
} }
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),
new DrawerDivider(),
buildDrawerRadioItem(DismissDirection.horizontal, 'action/code'),
buildDrawerRadioItem(DismissDirection.left, 'navigation/arrow_back'),
buildDrawerRadioItem(DismissDirection.right, 'navigation/arrow_forward'),
]
)
);
}
Widget buildToolBar() { Widget buildToolBar() {
return new ToolBar( return new ToolBar(
left: new IconButton(icon: "navigation/menu", onPressed: _handleOpenDrawer), left: new IconButton(icon: "navigation/menu", onPressed: _showDrawer),
center: new Text('Swipe Away'), center: new Text('Swipe Away'),
right: [ right: [
new Text(_dismissDirectionText(_dismissDirection)) new Text(_dismissDirectionText(_dismissDirection))
...@@ -285,6 +281,16 @@ class CardCollectionAppState extends State<CardCollectionApp> { ...@@ -285,6 +281,16 @@ class CardCollectionAppState extends State<CardCollectionApp> {
}); });
} }
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 build(BuildContext context) {
Widget cardCollection; Widget cardCollection;
...@@ -306,6 +312,12 @@ class CardCollectionAppState extends State<CardCollectionApp> { ...@@ -306,6 +312,12 @@ class CardCollectionAppState extends State<CardCollectionApp> {
); );
} }
if (_sunshine)
cardCollection = new Stack([
new Column([new NetworkImage(src: _sunshineURL)]),
new ShaderMask(child: cardCollection, shaderCallback: _createShader)
]);
Widget body = new SizeObserver( Widget body = new SizeObserver(
callback: _updateCardCollectionSize, callback: _updateCardCollectionSize,
child: new Container( child: new Container(
...@@ -329,24 +341,23 @@ class CardCollectionAppState extends State<CardCollectionApp> { ...@@ -329,24 +341,23 @@ class CardCollectionAppState extends State<CardCollectionApp> {
body = new Stack([body, indicator]); body = new Stack([body, indicator]);
} }
return new Theme( return new Scaffold(
data: new ThemeData(
brightness: ThemeBrightness.light,
primarySwatch: Colors.blue,
accentColor: Colors.redAccent[200]
),
child: new Title(
title: 'Cards',
child: new Scaffold(
toolbar: buildToolBar(), toolbar: buildToolBar(),
drawer: buildDrawer(),
body: body body: body
)
)
); );
} }
} }
void main() { void main() {
runApp(new CardCollectionApp()); runApp(new App(
title: 'Cards',
theme: new ThemeData(
brightness: ThemeBrightness.light,
primarySwatch: Colors.blue,
accentColor: Colors.redAccent[200]
),
routes: {
'/': (RouteArguments args) => new CardCollection(navigator: args.navigator),
}
));
} }
...@@ -50,7 +50,7 @@ class ExampleDragTargetState extends State<ExampleDragTarget> { ...@@ -50,7 +50,7 @@ class ExampleDragTargetState extends State<ExampleDragTarget> {
} }
class Dot extends StatelessComponent { 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 Color color;
final double size; final double size;
Widget build(BuildContext context) { Widget build(BuildContext context) {
...@@ -66,7 +66,7 @@ class Dot extends StatelessComponent { ...@@ -66,7 +66,7 @@ class Dot extends StatelessComponent {
} }
class ExampleDragSource 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 NavigatorState navigator;
final String name; final String name;
final Color color; final Color color;
...@@ -133,7 +133,7 @@ void main() { ...@@ -133,7 +133,7 @@ void main() {
runApp(new App( runApp(new App(
title: 'Drag and Drop Flutter Demo', title: 'Drag and Drop Flutter Demo',
routes: { routes: {
'/': (NavigatorState navigator, Route route) => new DragAndDropApp(navigator: navigator) '/': (RouteArguments args) => new DragAndDropApp(navigator: args.navigator)
} }
)); ));
} }
...@@ -6,46 +6,46 @@ import 'package:sky/material.dart'; ...@@ -6,46 +6,46 @@ import 'package:sky/material.dart';
import 'package:sky/widgets.dart'; import 'package:sky/widgets.dart';
final Map<String, RouteBuilder> routes = <String, RouteBuilder>{ final Map<String, RouteBuilder> routes = <String, RouteBuilder>{
'/': (NavigatorState navigator, Route route) => new Container( '/': (RouteArguments args) => new Container(
padding: const EdgeDims.all(30.0), padding: const EdgeDims.all(30.0),
decoration: new BoxDecoration(backgroundColor: const Color(0xFFCCCCCC)), decoration: new BoxDecoration(backgroundColor: const Color(0xFFCCCCCC)),
child: new Column([ child: new Column([
new Text("You are at home"), new Text("You are at home"),
new RaisedButton( new RaisedButton(
child: new Text('GO SHOPPING'), child: new Text('GO SHOPPING'),
onPressed: () => navigator.pushNamed('/shopping') onPressed: () => args.navigator.pushNamed('/shopping')
), ),
new RaisedButton( new RaisedButton(
child: new Text('START ADVENTURE'), child: new Text('START ADVENTURE'),
onPressed: () => navigator.pushNamed('/adventure') onPressed: () => args.navigator.pushNamed('/adventure')
)], )],
justifyContent: FlexJustifyContent.center justifyContent: FlexJustifyContent.center
) )
), ),
'/shopping': (NavigatorState navigator, Route route) => new Container( '/shopping': (RouteArguments args) => new Container(
padding: const EdgeDims.all(20.0), padding: const EdgeDims.all(20.0),
decoration: new BoxDecoration(backgroundColor: const Color(0xFFBF5FFF)), decoration: new BoxDecoration(backgroundColor: const Color(0xFFBF5FFF)),
child: new Column([ child: new Column([
new Text("Village Shop"), new Text("Village Shop"),
new RaisedButton( new RaisedButton(
child: new Text('RETURN HOME'), child: new Text('RETURN HOME'),
onPressed: () => navigator.pop() onPressed: () => args.navigator.pop()
), ),
new RaisedButton( new RaisedButton(
child: new Text('GO TO DUNGEON'), child: new Text('GO TO DUNGEON'),
onPressed: () => navigator.pushNamed('/adventure') onPressed: () => args.navigator.pushNamed('/adventure')
)], )],
justifyContent: FlexJustifyContent.center justifyContent: FlexJustifyContent.center
) )
), ),
'/adventure': (NavigatorState navigator, Route route) => new Container( '/adventure': (RouteArguments args) => new Container(
padding: const EdgeDims.all(20.0), padding: const EdgeDims.all(20.0),
decoration: new BoxDecoration(backgroundColor: const Color(0xFFDC143C)), decoration: new BoxDecoration(backgroundColor: const Color(0xFFDC143C)),
child: new Column([ child: new Column([
new Text("Monster's Lair"), new Text("Monster's Lair"),
new RaisedButton( new RaisedButton(
child: new Text('RUN!!!'), child: new Text('RUN!!!'),
onPressed: () => navigator.pop() onPressed: () => args.navigator.pop()
)], )],
justifyContent: FlexJustifyContent.center justifyContent: FlexJustifyContent.center
) )
......
...@@ -165,7 +165,7 @@ void main() { ...@@ -165,7 +165,7 @@ void main() {
), ),
title: 'Cards', title: 'Cards',
routes: { routes: {
'/': (navigator, route) => new OverlayGeometryApp() '/': (RouteArguments args) => new OverlayGeometryApp()
} }
)); ));
} }
...@@ -2,7 +2,6 @@ ...@@ -2,7 +2,6 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:sky/animation.dart';
import 'package:sky/material.dart'; import 'package:sky/material.dart';
import 'package:sky/painting.dart'; import 'package:sky/painting.dart';
import 'package:sky/widgets.dart'; import 'package:sky/widgets.dart';
...@@ -17,6 +16,10 @@ class CardModel { ...@@ -17,6 +16,10 @@ class CardModel {
} }
class PageableListApp extends StatefulComponent { class PageableListApp extends StatefulComponent {
PageableListApp({ this.navigator });
final NavigatorState navigator;
PageableListAppState createState() => new PageableListAppState(); PageableListAppState createState() => new PageableListAppState();
} }
...@@ -85,31 +88,10 @@ class PageableListAppState extends State<PageableListApp> { ...@@ -85,31 +88,10 @@ class PageableListAppState extends State<PageableListApp> {
}); });
} }
bool _drawerShowing = false; void _showDrawer() {
AnimationStatus _drawerStatus = AnimationStatus.dismissed; showDrawer(
navigator: config.navigator,
void _handleOpenDrawer() { child: new Block([
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: [
new DrawerHeader(child: new Text('Options')), new DrawerHeader(child: new Text('Options')),
new DrawerItem( new DrawerItem(
icon: 'navigation/more_horiz', icon: 'navigation/more_horiz',
...@@ -130,14 +112,13 @@ class PageableListAppState extends State<PageableListApp> { ...@@ -130,14 +112,13 @@ class PageableListAppState extends State<PageableListApp> {
new Checkbox(value: itemsWrap) new Checkbox(value: itemsWrap)
]) ])
) )
] ])
); );
} }
Widget buildToolBar() { Widget buildToolBar() {
return new ToolBar( return new ToolBar(
left: new IconButton(icon: "navigation/menu", onPressed: _handleOpenDrawer), left: new IconButton(icon: "navigation/menu", onPressed: _showDrawer),
center: new Text('PageableList'), center: new Text('PageableList'),
right: [ right: [
new Text(scrollDirection == ScrollDirection.horizontal ? "horizontal" : "vertical") new Text(scrollDirection == ScrollDirection.horizontal ? "horizontal" : "vertical")
...@@ -167,25 +148,24 @@ class PageableListAppState extends State<PageableListApp> { ...@@ -167,25 +148,24 @@ class PageableListAppState extends State<PageableListApp> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new IconTheme( return new IconTheme(
data: const IconThemeData(color: IconThemeColor.white), 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( child: new Scaffold(
drawer: buildDrawer(),
toolbar: buildToolBar(), toolbar: buildToolBar(),
body: buildBody(context) body: buildBody(context)
) )
)
)
); );
} }
} }
void main() { void main() {
runApp(new PageableListApp()); runApp(new App(
title: 'PageableList',
theme: new ThemeData(
brightness: ThemeBrightness.light,
primarySwatch: Colors.blue,
accentColor: Colors.redAccent[200]
),
routes: {
'/': (RouteArguments args) => new PageableListApp(navigator: args.navigator),
}
));
} }
...@@ -13,24 +13,23 @@ class ProgressIndicatorApp extends StatefulComponent { ...@@ -13,24 +13,23 @@ class ProgressIndicatorApp extends StatefulComponent {
class ProgressIndicatorAppState extends State<ProgressIndicatorApp> { class ProgressIndicatorAppState extends State<ProgressIndicatorApp> {
void initState() { void initState() {
super.initState(); super.initState();
valueAnimation = new ValueAnimation<double>() valueAnimation = new ValuePerformance<double>()
..duration = const Duration(milliseconds: 1500) ..duration = const Duration(milliseconds: 1500)
..variable = new AnimatedValue<double>( ..variable = new AnimatedValue<double>(
0.0, 0.0,
end: 1.0, end: 1.0,
curve: ease, curve: new Interval(0.0, 0.9, curve: ease),
reverseCurve: ease, reverseCurve: ease
interval: new Interval(0.0, 0.9)
); );
valueAnimation.addStatusListener((AnimationStatus status) { valueAnimation.addStatusListener((PerformanceStatus status) {
if (status == AnimationStatus.dismissed || status == AnimationStatus.completed) if (status == PerformanceStatus.dismissed || status == PerformanceStatus.completed)
reverseValueAnimationDirection(); reverseValueAnimationDirection();
}); });
valueAnimation.play(valueAnimationDirection); valueAnimation.play(valueAnimationDirection);
} }
ValueAnimation<double> valueAnimation; ValuePerformance<double> valueAnimation;
Direction valueAnimationDirection = Direction.forward; AnimationDirection valueAnimationDirection = AnimationDirection.forward;
void handleTap() { void handleTap() {
setState(() { setState(() {
...@@ -43,9 +42,9 @@ class ProgressIndicatorAppState extends State<ProgressIndicatorApp> { ...@@ -43,9 +42,9 @@ class ProgressIndicatorAppState extends State<ProgressIndicatorApp> {
} }
void reverseValueAnimationDirection() { void reverseValueAnimationDirection() {
valueAnimationDirection = (valueAnimationDirection == Direction.forward) valueAnimationDirection = (valueAnimationDirection == AnimationDirection.forward)
? Direction.reverse ? AnimationDirection.reverse
: Direction.forward; : AnimationDirection.forward;
valueAnimation.play(valueAnimationDirection); valueAnimation.play(valueAnimationDirection);
} }
......
...@@ -54,13 +54,13 @@ Widget builder() { ...@@ -54,13 +54,13 @@ Widget builder() {
); );
} }
double timeBase; Duration timeBase;
RenderTransform transformBox; RenderTransform transformBox;
void rotate(double timeStamp) { void rotate(Duration timeStamp) {
if (timeBase == null) if (timeBase == null)
timeBase = timeStamp; timeBase = timeStamp;
double delta = (timeStamp - timeBase) / 1000; // radians double delta = (timeStamp - timeBase).inMicroseconds.toDouble() / Duration.MICROSECONDS_PER_SECOND; // radians
transformBox.setIdentity(); transformBox.setIdentity();
transformBox.translate(transformBox.size.width / 2.0, transformBox.size.height / 2.0); transformBox.translate(transformBox.size.width / 2.0, transformBox.size.height / 2.0);
......
...@@ -7,12 +7,12 @@ ...@@ -7,12 +7,12 @@
/// This library depends only on core Dart libraries and the `newton` package. /// This library depends only on core Dart libraries and the `newton` package.
library animation; library animation;
export 'src/animation/animated_simulation.dart';
export 'src/animation/animated_value.dart'; 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/clamped_simulation.dart';
export 'src/animation/curves.dart'; export 'src/animation/curves.dart';
export 'src/animation/forces.dart'; export 'src/animation/forces.dart';
export 'src/animation/scheduler.dart'; export 'src/animation/scheduler.dart';
export 'src/animation/scroll_behavior.dart'; export 'src/animation/scroll_behavior.dart';
export 'src/animation/timeline.dart'; export 'src/animation/simulation_stepper.dart';
export 'src/animation/ticker.dart';
...@@ -2,12 +2,12 @@ ...@@ -2,12 +2,12 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import "dart:sky"; import 'dart:sky' show Color, Rect;
import 'package:sky/src/animation/curves.dart'; import 'package:sky/src/animation/curves.dart';
/// The direction in which an animation is running /// The direction in which an animation is running
enum Direction { enum AnimationDirection {
/// The animation is running from beginning to end /// The animation is running from beginning to end
forward, forward,
...@@ -17,71 +17,51 @@ enum Direction { ...@@ -17,71 +17,51 @@ enum Direction {
/// An interface describing a variable that changes as an animation progresses. /// 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 /// Animatable objects, by convention, must be cheap to create. This allows them
/// build functions in Widgets. /// to be used in build functions in Widgets.
abstract class AnimatedVariable { abstract class Animatable {
/// Update the variable to a given time in an animation that is running in the given direction /// 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(); 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 /// 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. /// can be made to take longer in one direction that the other.
class AnimationTiming { class AnimationTiming {
AnimationTiming({ AnimationTiming({ this.curve, this.reverseCurve });
this.interval: const Interval(0.0, 1.0),
this.reverseInterval,
this.curve: linear,
this.reverseCurve
});
/// The interval during which this timing is active in the forward direction /// The curve to use 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
Curve curve; 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; Curve reverseCurve;
/// Applies this timing to the given animation clock value in the given direction /// 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); Curve activeCurve = _getActiveCurve(direction);
if (interval != null) if (activeCurve == null)
t = interval.transform(t);
if (t == 1.0) // Or should we support inverse curves?
return t; return t;
Curve curve = _getCurve(direction); if (t == 0.0 || t == 1.0) {
if (curve != null) assert(activeCurve.transform(t).round() == t);
t = curve.transform(t);
return t; return t;
} }
return activeCurve.transform(t);
Interval _getInterval(Direction direction) {
if (direction == Direction.forward || reverseInterval == null)
return interval;
return reverseInterval;
} }
Curve _getCurve(Direction direction) { Curve _getActiveCurve(AnimationDirection direction) {
if (direction == Direction.forward || reverseCurve == null) if (direction == AnimationDirection.forward || reverseCurve == null)
return curve; return curve;
return reverseCurve; return reverseCurve;
} }
} }
/// An animated variable with a concrete type /// An animated variable with a concrete type
class AnimatedValue<T extends dynamic> extends AnimationTiming implements AnimatedVariable { class AnimatedValue<T extends dynamic> extends AnimationTiming implements Animatable {
AnimatedValue(this.begin, { this.end, Interval interval, Interval reverseInterval, Curve curve, Curve reverseCurve }) AnimatedValue(this.begin, { this.end, Curve curve, Curve reverseCurve })
: super(interval: interval, reverseInterval: reverseInterval, curve: curve, reverseCurve: reverseCurve) { : super(curve: curve, reverseCurve: reverseCurve) {
value = begin; value = begin;
} }
...@@ -98,41 +78,28 @@ class AnimatedValue<T extends dynamic> extends AnimationTiming implements Animat ...@@ -98,41 +78,28 @@ class AnimatedValue<T extends dynamic> extends AnimationTiming implements Animat
T lerp(double t) => begin + (end - begin) * t; T lerp(double t) => begin + (end - begin) * t;
/// Updates the value of this variable according to the given animation clock value and direction /// 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) { if (end != null) {
t = transform(t, direction); 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);
} }
} }
String toString() => 'AnimatedValue(begin=$begin, end=$end, value=$value)'; 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<AnimatedVariable> 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 /// An animated variable containing a color
/// ///
/// This class specializes the interpolation of AnimatedValue<Color> to be /// This class specializes the interpolation of AnimatedValue<Color> to be
/// appropriate for colors. /// appropriate for colors.
class AnimatedColorValue extends AnimatedValue<Color> { class AnimatedColorValue extends AnimatedValue<Color> {
AnimatedColorValue(Color begin, { Color end, Interval interval, Interval reverseInterval, Curve curve, Curve reverseCurve }) AnimatedColorValue(Color begin, { Color end, Curve curve, Curve reverseCurve })
: super(begin, end: end, interval: interval, reverseInterval: reverseInterval, curve: curve, reverseCurve: reverseCurve); : super(begin, end: end, curve: curve, reverseCurve: reverseCurve);
Color lerp(double t) => Color.lerp(begin, end, t); Color lerp(double t) => Color.lerp(begin, end, t);
} }
...@@ -141,9 +108,9 @@ class AnimatedColorValue extends AnimatedValue<Color> { ...@@ -141,9 +108,9 @@ class AnimatedColorValue extends AnimatedValue<Color> {
/// ///
/// This class specializes the interpolation of AnimatedValue<Rect> to be /// This class specializes the interpolation of AnimatedValue<Rect> to be
/// appropriate for rectangles. /// appropriate for rectangles.
class AnimatedRect extends AnimatedValue<Rect> { class AnimatedRectValue extends AnimatedValue<Rect> {
AnimatedRect(Rect begin, { Rect end, Interval interval, Interval reverseInterval, Curve curve, Curve reverseCurve }) AnimatedRectValue(Rect begin, { Rect end, Curve curve, Curve reverseCurve })
: super(begin, end: end, interval: interval, reverseInterval: reverseInterval, curve: curve, reverseCurve: reverseCurve); : super(begin, end: end, curve: curve, reverseCurve: reverseCurve);
Rect lerp(double t) => Rect.lerp(begin, end, t); Rect lerp(double t) => Rect.lerp(begin, end, t);
} }
...@@ -27,9 +27,9 @@ class Linear implements Curve { ...@@ -27,9 +27,9 @@ class Linear implements Curve {
double transform(double t) => t; 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 { 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 /// The smallest value for which this interval is 0.0
final double start; final double start;
...@@ -37,12 +37,18 @@ class Interval implements Curve { ...@@ -37,12 +37,18 @@ class Interval implements Curve {
/// The smallest value for which this interval is 1.0 /// The smallest value for which this interval is 1.0
final double end; final double end;
/// The curve to apply between [start] and [end]
final Curve curve;
double transform(double t) { double transform(double t) {
assert(start >= 0.0); assert(start >= 0.0);
assert(start <= 1.0); assert(start <= 1.0);
assert(end >= 0.0); assert(end >= 0.0);
assert(end <= 1.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);
} }
} }
......
...@@ -6,10 +6,10 @@ import 'dart:async'; ...@@ -6,10 +6,10 @@ import 'dart:async';
import 'package:sky/src/animation/animated_value.dart'; import 'package:sky/src/animation/animated_value.dart';
import 'package:sky/src/animation/forces.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 /// The status of an animation
enum AnimationStatus { enum PerformanceStatus {
/// The animation is stopped at the beginning /// The animation is stopped at the beginning
dismissed, dismissed,
...@@ -23,27 +23,27 @@ enum AnimationStatus { ...@@ -23,27 +23,27 @@ enum AnimationStatus {
completed, completed,
} }
typedef void AnimationPerformanceListener(); typedef void PerformanceListener();
typedef void AnimationPerformanceStatusListener(AnimationStatus status); 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 /// 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 /// want to watch a performance but should not be able to change the
/// performance's state. /// performance's state.
abstract class WatchableAnimationPerformance { abstract class PerformanceView {
/// Update the given variable according to the current progress of the performance /// 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 /// 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 /// 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 /// 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 /// 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 /// 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 /// sliding and fading in (changing Y value and opacity) over .5 seconds. The
...@@ -51,41 +51,38 @@ abstract class WatchableAnimationPerformance { ...@@ -51,41 +51,38 @@ abstract class WatchableAnimationPerformance {
/// may also take direct control of the timeline by manipulating [progress], or /// may also take direct control of the timeline by manipulating [progress], or
/// [fling] the timeline causing a physics-based simulation to take over the /// [fling] the timeline causing a physics-based simulation to take over the
/// progression. /// progression.
class AnimationPerformance implements WatchableAnimationPerformance { class Performance implements PerformanceView {
AnimationPerformance({ this.duration, double progress }) { Performance({ this.duration, double progress }) {
_timeline = new Timeline(_tick); _timeline = new SimulationStepper(_tick);
if (progress != null) if (progress != null)
_timeline.value = progress.clamp(0.0, 1.0); _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 /// so that a pointer to this object can be passed around without
/// allowing users of that pointer to mutate the AnimationPerformance state. /// 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 /// The length of time this performance should last
Duration duration; Duration duration;
Timeline _timeline; SimulationStepper _timeline;
Direction _direction; AnimationDirection _direction;
/// The direction used to select the current curve /// The direction used to select the current curve
/// ///
/// Curve direction is only reset when we hit the beginning or the end of the /// 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 /// timeline to avoid discontinuities in the value of any variables this
/// performance is used to animate. /// performance is used to animate.
Direction _curveDirection; AnimationDirection _curveDirection;
/// If non-null, animate with this timing instead of a linear timing /// If non-null, animate with this timing instead of a linear timing
AnimationTiming 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 /// The progress of this performance along the timeline
/// ///
/// Note: Setting this value stops the current animation. /// 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) { void set progress(double t) {
// TODO(mpcomplete): should this affect |direction|? // TODO(mpcomplete): should this affect |direction|?
stop(); stop();
...@@ -98,51 +95,45 @@ class AnimationPerformance implements WatchableAnimationPerformance { ...@@ -98,51 +95,45 @@ class AnimationPerformance implements WatchableAnimationPerformance {
} }
/// Whether this animation is stopped at the beginning /// 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 /// 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 /// Whether this animation is currently animating in either the forward or reverse direction
bool get isAnimating => _timeline.isAnimating; bool get isAnimating => _timeline.isAnimating;
/// The current status of this animation /// The current status of this animation
AnimationStatus get status { PerformanceStatus get status {
if (!isAnimating && progress == 1.0) if (!isAnimating && progress == 1.0)
return AnimationStatus.completed; return PerformanceStatus.completed;
if (!isAnimating && progress == 0.0) if (!isAnimating && progress == 0.0)
return AnimationStatus.dismissed; return PerformanceStatus.dismissed;
return _direction == Direction.forward ? return _direction == AnimationDirection.forward ?
AnimationStatus.forward : PerformanceStatus.forward :
AnimationStatus.reverse; PerformanceStatus.reverse;
} }
/// Update the given varaible according to the current progress of this performance /// Update the given varaible according to the current progress of this performance
void updateVariable(AnimatedVariable variable) { void updateVariable(Animatable variable) {
variable.setProgress(_curvedProgress, _curveDirection); variable.setProgress(_curvedProgress, _curveDirection);
} }
/// Start running this animation forwards (towards the end) /// 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) /// 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 /// Start running this animation in the given direction
Future play([Direction direction = Direction.forward]) { Future play([AnimationDirection direction = AnimationDirection.forward]) {
_direction = direction; _direction = direction;
return resume(); return resume();
} }
/// Start running this animation in the most recently direction /// Start running this animation in the most recent direction
Future resume() { Future resume() {
if (attachedForce != null) { return _animateTo(_direction == AnimationDirection.forward ? 1.0 : 0.0);
return fling(
velocity: _direction == Direction.forward ? 1.0 : -1.0,
force: attachedForce
);
}
return _animateTo(_direction == Direction.forward ? 1.0 : 0.0);
} }
/// Stop running this animation /// Stop running this animation
...@@ -158,46 +149,46 @@ class AnimationPerformance implements WatchableAnimationPerformance { ...@@ -158,46 +149,46 @@ class AnimationPerformance implements WatchableAnimationPerformance {
Future fling({double velocity: 1.0, Force force}) { Future fling({double velocity: 1.0, Force force}) {
if (force == null) if (force == null)
force = kDefaultSpringForce; force = kDefaultSpringForce;
_direction = velocity < 0.0 ? Direction.reverse : Direction.forward; _direction = velocity < 0.0 ? AnimationDirection.reverse : AnimationDirection.forward;
return _timeline.fling(force.release(progress, velocity)); return _timeline.animateWith(force.release(progress, velocity));
} }
final List<AnimationPerformanceListener> _listeners = new List<AnimationPerformanceListener>(); final List<PerformanceListener> _listeners = new List<PerformanceListener>();
/// Calls the listener every time the progress of this performance changes /// Calls the listener every time the progress of this performance changes
void addListener(AnimationPerformanceListener listener) { void addListener(PerformanceListener listener) {
_listeners.add(listener); _listeners.add(listener);
} }
/// Stop calling the listener every time the progress of this performance changes /// Stop calling the listener every time the progress of this performance changes
void removeListener(AnimationPerformanceListener listener) { void removeListener(PerformanceListener listener) {
_listeners.remove(listener); _listeners.remove(listener);
} }
void _notifyListeners() { void _notifyListeners() {
List<AnimationPerformanceListener> localListeners = new List<AnimationPerformanceListener>.from(_listeners); List<PerformanceListener> localListeners = new List<PerformanceListener>.from(_listeners);
for (AnimationPerformanceListener listener in localListeners) for (PerformanceListener listener in localListeners)
listener(); listener();
} }
final List<AnimationPerformanceStatusListener> _statusListeners = new List<AnimationPerformanceStatusListener>(); final List<PerformanceStatusListener> _statusListeners = new List<PerformanceStatusListener>();
/// Calls listener every time the status of this performance changes /// Calls listener every time the status of this performance changes
void addStatusListener(AnimationPerformanceStatusListener listener) { void addStatusListener(PerformanceStatusListener listener) {
_statusListeners.add(listener); _statusListeners.add(listener);
} }
/// Stops calling the listener every time the status of this performance changes /// Stops calling the listener every time the status of this performance changes
void removeStatusListener(AnimationPerformanceStatusListener listener) { void removeStatusListener(PerformanceStatusListener listener) {
_statusListeners.remove(listener); _statusListeners.remove(listener);
} }
AnimationStatus _lastStatus = AnimationStatus.dismissed; PerformanceStatus _lastStatus = PerformanceStatus.dismissed;
void _checkStatusChanged() { void _checkStatusChanged() {
AnimationStatus currentStatus = status; PerformanceStatus currentStatus = status;
if (currentStatus != _lastStatus) { if (currentStatus != _lastStatus) {
List<AnimationPerformanceStatusListener> localListeners = new List<AnimationPerformanceStatusListener>.from(_statusListeners); List<PerformanceStatusListener> localListeners = new List<PerformanceStatusListener>.from(_statusListeners);
for (AnimationPerformanceStatusListener listener in localListeners) for (PerformanceStatusListener listener in localListeners)
listener(currentStatus); listener(currentStatus);
} }
_lastStatus = currentStatus; _lastStatus = currentStatus;
...@@ -205,7 +196,7 @@ class AnimationPerformance implements WatchableAnimationPerformance { ...@@ -205,7 +196,7 @@ class AnimationPerformance implements WatchableAnimationPerformance {
void _updateCurveDirection() { void _updateCurveDirection() {
if (status != _lastStatus) { if (status != _lastStatus) {
if (_lastStatus == AnimationStatus.dismissed || _lastStatus == AnimationStatus.completed) if (_lastStatus == PerformanceStatus.dismissed || _lastStatus == PerformanceStatus.completed)
_curveDirection = _direction; _curveDirection = _direction;
} }
} }
...@@ -230,8 +221,8 @@ class AnimationPerformance implements WatchableAnimationPerformance { ...@@ -230,8 +221,8 @@ class AnimationPerformance implements WatchableAnimationPerformance {
} }
/// An animation performance with an animated variable with a concrete type /// An animation performance with an animated variable with a concrete type
class ValueAnimation<T> extends AnimationPerformance { class ValuePerformance<T> extends Performance {
ValueAnimation({ this.variable, Duration duration, double progress }) : ValuePerformance({ this.variable, Duration duration, double progress }) :
super(duration: duration, progress: progress); super(duration: duration, progress: progress);
AnimatedValue<T> variable; AnimatedValue<T> variable;
......
...@@ -14,7 +14,7 @@ double timeDilation = 1.0; ...@@ -14,7 +14,7 @@ double timeDilation = 1.0;
/// scheduler's epoch. Use timeStamp to determine how far to advance animation /// 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 /// timelines so that all the animations in the system are synchronized to a
/// common time base. /// 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 /// Schedules callbacks to run in concert with the engine's animation system
class Scheduler { class Scheduler {
...@@ -35,8 +35,10 @@ class Scheduler { ...@@ -35,8 +35,10 @@ class Scheduler {
/// This function first calls all the callbacks registered by /// This function first calls all the callbacks registered by
/// [requestAnimationFrame] and then calls all the callbacks registered by /// [requestAnimationFrame] and then calls all the callbacks registered by
/// [addPersistentFrameCallback], which typically drive the rendering pipeline. /// [addPersistentFrameCallback], which typically drive the rendering pipeline.
void beginFrame(double timeStamp) { void beginFrame(double timeStampMS) {
timeStamp /= timeDilation; timeStampMS /= timeDilation;
Duration timeStamp = new Duration(microseconds: (timeStampMS * Duration.MICROSECONDS_PER_MILLISECOND).round());
_haveScheduledVisualUpdate = false; _haveScheduledVisualUpdate = false;
......
...@@ -5,30 +5,31 @@ ...@@ -5,30 +5,31 @@
import 'dart:async'; import 'dart:async';
import 'package:newton/newton.dart'; import 'package:newton/newton.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]
class TweenSimulation extends Simulation { /// A simulation that varies from [begin] to [end] over [duration] using [curve]
final double _durationInSeconds; ///
/// This class is an adaptor between the Simulation interface and the
/// The initial value of the simulation /// AnimatedValue interface.
final double begin; class _TweenSimulation extends Simulation {
_TweenSimulation(double begin, double end, Duration duration, Curve curve)
/// The terminal value of the simulation : _durationInSeconds = duration.inMicroseconds.toDouble() / Duration.MICROSECONDS_PER_SECOND,
final double end; _tween = new AnimatedValue<double>(begin, end: end, curve: curve) {
TweenSimulation(Duration duration, this.begin, this.end) :
_durationInSeconds = duration.inMicroseconds / Duration.MICROSECONDS_PER_SECOND {
assert(_durationInSeconds > 0.0); assert(_durationInSeconds > 0.0);
assert(begin != null && begin >= 0.0 && begin <= 1.0); assert(begin != null);
assert(end != null && end >= 0.0 && end <= 1.0); assert(end != null);
} }
final double _durationInSeconds;
final AnimatedValue<double> _tween;
double x(double timeInSeconds) { double x(double timeInSeconds) {
assert(timeInSeconds >= 0.0); assert(timeInSeconds >= 0.0);
final double t = timeInSeconds / _durationInSeconds; final double t = (timeInSeconds / _durationInSeconds).clamp(0.0, 1.0);
return t >= 1.0 ? end : begin + (end - begin) * t; _tween.setProgress(t, AnimationDirection.forward);
return _tween.value;
} }
double dx(double timeInSeconds) => 1.0; double dx(double timeInSeconds) => 1.0;
...@@ -36,52 +37,69 @@ class TweenSimulation extends Simulation { ...@@ -36,52 +37,69 @@ class TweenSimulation extends Simulation {
bool isDone(double timeInSeconds) => timeInSeconds > _durationInSeconds; bool isDone(double timeInSeconds) => timeInSeconds > _durationInSeconds;
} }
/// A timeline for an animation typedef TimelineCallback(double value);
class Timeline {
Timeline(Function onTick) { /// Steps a simulation one per frame
_animation = new AnimatedSimulation(onTick); 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 /// The current value of the timeline
double get value => _animation.value.clamp(0.0, 1.0); double get value => _value;
double _value = 0.0;
void set value(double newValue) { void set value(double newValue) {
assert(newValue != null && newValue >= 0.0 && newValue <= 1.0); assert(newValue != null);
assert(!isAnimating); assert(!isAnimating);
_animation.value = newValue; _value = newValue;
_onTick(_value);
} }
/// Whether the timeline is currently animating /// Whether the timeline is currently animating
bool get isAnimating => _animation.isAnimating; bool get isAnimating => _ticker.isTicking;
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 /// Animate value of the timeline to the given target over the given duration
/// ///
/// Returns a future that resolves when the timeline stops animating, /// Returns a future that resolves when the timeline stops animating,
/// typically when the timeline arives at the target value. /// 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); assert(duration > Duration.ZERO);
return _start(duration: duration, begin: value, end: target); 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 /// Stop animating the timeline
void stop() { void stop() {
_animation.stop(); _simulation = null;
_ticker.stop();
} }
// Gives the given simulation control over the timeline void _tick(Duration elapsed) {
Future fling(Simulation simulation) { double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.MICROSECONDS_PER_SECOND;
_value = _simulation.x(elapsedInSeconds);
if (_simulation.isDone(elapsedInSeconds))
stop(); stop();
return _animation.start(simulation); _onTick(_value);
} }
} }
...@@ -4,29 +4,27 @@ ...@@ -4,29 +4,27 @@
import 'dart:async'; import 'dart:async';
import 'package:newton/newton.dart';
import 'package:sky/src/animation/scheduler.dart'; import 'package:sky/src/animation/scheduler.dart';
const double _kSecondsPerMillisecond = 1000.0; typedef TickerCallback(Duration elapsed);
// TODO(abarth): Change from double to Duration.
typedef _TickerCallback(double timeStamp);
/// Calls its callback once per animation frame /// Calls its callback once per animation frame
class Ticker { class Ticker {
/// Constructs a ticker that will call onTick once per frame while running /// 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; Completer _completer;
int _animationId; int _animationId;
Duration _startTime;
/// Start calling onTick once per animation frame /// Start calling onTick once per animation frame
/// ///
/// The returned future resolves once the ticker stops ticking. /// The returned future resolves once the ticker stops ticking.
Future start() { Future start() {
assert(!isTicking); assert(!isTicking);
assert(_startTime == null);
_completer = new Completer(); _completer = new Completer();
_scheduleTick(); _scheduleTick();
return _completer.future; return _completer.future;
...@@ -39,12 +37,14 @@ class Ticker { ...@@ -39,12 +37,14 @@ class Ticker {
if (!isTicking) if (!isTicking)
return; return;
_startTime = null;
if (_animationId != null) { if (_animationId != null) {
scheduler.cancelAnimationFrame(_animationId); scheduler.cancelAnimationFrame(_animationId);
_animationId = null; _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 // when we actually complete the future (isTicking uses _completer
// to determine its state). // to determine its state).
Completer localCompleter = _completer; Completer localCompleter = _completer;
...@@ -56,12 +56,15 @@ class Ticker { ...@@ -56,12 +56,15 @@ class Ticker {
/// Whether this ticker has scheduled a call to onTick /// Whether this ticker has scheduled a call to onTick
bool get isTicking => _completer != null; bool get isTicking => _completer != null;
void _tick(double timeStamp) { void _tick(Duration timeStamp) {
assert(isTicking); assert(isTicking);
assert(_animationId != null); assert(_animationId != null);
_animationId = null; _animationId = null;
_onTick(timeStamp); if (_startTime == null)
_startTime = timeStamp;
_onTick(timeStamp - _startTime);
// The onTick callback may have scheduled another tick already. // The onTick callback may have scheduled another tick already.
if (isTicking && _animationId == null) if (isTicking && _animationId == null)
...@@ -74,63 +77,3 @@ class Ticker { ...@@ -74,63 +77,3 @@ class Ticker {
_animationId = scheduler.requestAnimationFrame(_tick); _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 _startTime;
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;
_startTime = null;
_value = simulation.x(0.0);
return _ticker.start();
}
/// 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(double timeStamp) {
if (_startTime == null)
_startTime = timeStamp;
double timeInSeconds = (timeStamp - _startTime) / _kSecondsPerMillisecond;
_value = _simulation.x(timeInSeconds);
final bool isLastTick = _simulation.isDone(timeInSeconds);
if (isLastTick)
stop();
_onTick(_value);
}
}
...@@ -9,13 +9,13 @@ import 'package:sky/src/gestures/constants.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/pointer_router.dart';
import 'package:sky/src/gestures/recognizer.dart'; import 'package:sky/src/gestures/recognizer.dart';
typedef void GestureLongPressListener(); typedef void GestureLongPressCallback();
class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer { class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
LongPressGestureRecognizer({ PointerRouter router, this.onLongPress }) LongPressGestureRecognizer({ PointerRouter router, this.onLongPress })
: super(router: router, deadline: kTapTimeout + kLongPressTimeout); : super(router: router, deadline: kTapTimeout + kLongPressTimeout);
GestureLongPressListener onLongPress; GestureLongPressCallback onLongPress;
void didExceedDeadline() { void didExceedDeadline() {
resolve(GestureDisposition.accepted); resolve(GestureDisposition.accepted);
......
...@@ -12,7 +12,9 @@ import 'package:sky/src/gestures/pointer_router.dart'; ...@@ -12,7 +12,9 @@ import 'package:sky/src/gestures/pointer_router.dart';
export 'package:sky/src/gestures/pointer_router.dart' show PointerRouter; export 'package:sky/src/gestures/pointer_router.dart' show PointerRouter;
abstract class GestureRecognizer extends GestureArenaMember { abstract class GestureRecognizer extends GestureArenaMember {
GestureRecognizer({ PointerRouter router }) : _router = router; GestureRecognizer({ PointerRouter router }) : _router = router {
assert(_router != null);
}
PointerRouter _router; PointerRouter _router;
......
...@@ -8,13 +8,13 @@ import 'package:sky/src/gestures/arena.dart'; ...@@ -8,13 +8,13 @@ import 'package:sky/src/gestures/arena.dart';
import 'package:sky/src/gestures/constants.dart'; import 'package:sky/src/gestures/constants.dart';
import 'package:sky/src/gestures/recognizer.dart'; import 'package:sky/src/gestures/recognizer.dart';
typedef void GestureShowPressListener(); typedef void GestureShowPressCallback();
class ShowPressGestureRecognizer extends PrimaryPointerGestureRecognizer { class ShowPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
ShowPressGestureRecognizer({ PointerRouter router, this.onShowPress }) ShowPressGestureRecognizer({ PointerRouter router, this.onShowPress })
: super(router: router, deadline: kTapTimeout); : super(router: router, deadline: kTapTimeout);
GestureShowPressListener onShowPress; GestureShowPressCallback onShowPress;
void didExceedDeadline() { void didExceedDeadline() {
// Show press isn't an exclusive gesture. We can recognize a show press // Show press isn't an exclusive gesture. We can recognize a show press
......
...@@ -8,13 +8,15 @@ import 'package:sky/src/gestures/arena.dart'; ...@@ -8,13 +8,15 @@ import 'package:sky/src/gestures/arena.dart';
import 'package:sky/src/gestures/constants.dart'; import 'package:sky/src/gestures/constants.dart';
import 'package:sky/src/gestures/recognizer.dart'; import 'package:sky/src/gestures/recognizer.dart';
typedef void GestureTapListener(); typedef void GestureTapCallback();
class TapGestureRecognizer extends PrimaryPointerGestureRecognizer { class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
TapGestureRecognizer({ PointerRouter router, this.onTap }) TapGestureRecognizer({ PointerRouter router, this.onTap })
: super(router: router, deadline: kTapTimeout); : super(router: router, deadline: kTapTimeout);
GestureTapListener onTap; GestureTapCallback onTap;
GestureTapCallback onTapDown;
GestureTapCallback onTapCancel;
void didExceedDeadline() { void didExceedDeadline() {
stopTrackingPointer(primaryPointer); stopTrackingPointer(primaryPointer);
...@@ -22,9 +24,22 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer { ...@@ -22,9 +24,22 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
} }
void handlePrimaryPointer(sky.PointerEvent event) { 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); resolve(GestureDisposition.accepted);
if (onTap != null)
onTap(); onTap();
} }
} }
void rejectGesture(int pointer) {
super.rejectGesture(pointer);
if (pointer == primaryPointer) {
assert(state == GestureRecognizerState.defunct);
if (onTapCancel != null)
onTapCancel();
}
}
} }
...@@ -30,13 +30,13 @@ class RadialReaction { ...@@ -30,13 +30,13 @@ class RadialReaction {
_outerOpacity = new AnimatedValue<double>(0.0, end: _kMaxOpacity, curve: easeOut); _outerOpacity = new AnimatedValue<double>(0.0, end: _kMaxOpacity, curve: easeOut);
_innerCenter = new AnimatedValue<Point>(startPosition, end: center, curve: easeOut); _innerCenter = new AnimatedValue<Point>(startPosition, end: center, curve: easeOut);
_innerRadius = new AnimatedValue<double>(0.0, end: radius, curve: easeOut); _innerRadius = new AnimatedValue<double>(0.0, end: radius, curve: easeOut);
_showPerformance = new AnimationPerformance(duration: _kShowDuration) _showPerformance = new Performance(duration: _kShowDuration)
..addListener(() { ..addListener(() {
_showPerformance.updateVariable(_outerOpacity); _showPerformance.updateVariable(_outerOpacity);
_showPerformance.updateVariable(_innerCenter); _showPerformance.updateVariable(_innerCenter);
_showPerformance.updateVariable(_innerRadius); _showPerformance.updateVariable(_innerRadius);
}); });
_fade = new ValueAnimation<double>( _fade = new ValuePerformance<double>(
variable: new AnimatedValue(1.0, end: 0.0, curve: easeIn), variable: new AnimatedValue(1.0, end: 0.0, curve: easeIn),
duration: _kHideDuration duration: _kHideDuration
); );
...@@ -48,14 +48,14 @@ class RadialReaction { ...@@ -48,14 +48,14 @@ class RadialReaction {
/// The radius of the circle in which the reaction occurs /// The radius of the circle in which the reaction occurs
final double radius; final double radius;
AnimationPerformance _showPerformance; Performance _showPerformance;
AnimatedValue<double> _outerOpacity; AnimatedValue<double> _outerOpacity;
AnimatedValue<Point> _innerCenter; AnimatedValue<Point> _innerCenter;
AnimatedValue<double> _innerRadius; AnimatedValue<double> _innerRadius;
Future _showComplete; Future _showComplete;
ValueAnimation<double> _fade; ValuePerformance<double> _fade;
/// Show the reaction /// Show the reaction
/// ///
......
...@@ -78,7 +78,7 @@ class FlutterBinding extends HitTestTarget { ...@@ -78,7 +78,7 @@ class FlutterBinding extends HitTestTarget {
} }
/// Pump the rendering pipeline to generate a frame for the given time stamp /// Pump the rendering pipeline to generate a frame for the given time stamp
void beginFrame(double timeStamp) { void beginFrame(Duration timeStamp) {
RenderObject.flushLayout(); RenderObject.flushLayout();
_renderView.updateCompositingBits(); _renderView.updateCompositingBits();
RenderObject.flushPaint(); RenderObject.flushPaint();
......
...@@ -178,6 +178,60 @@ class BoxConstraints extends Constraints { ...@@ -178,6 +178,60 @@ class BoxConstraints extends Constraints {
(minHeight <= size.height) && (size.height <= math.max(minHeight, maxHeight)); (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) { bool operator ==(other) {
if (identical(this, other)) if (identical(this, other))
return true; return true;
......
...@@ -87,6 +87,29 @@ class PictureLayer extends Layer { ...@@ -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 /// A composited layer that has a list of children
class ContainerLayer extends Layer { class ContainerLayer extends Layer {
ContainerLayer({ Offset offset: Offset.zero }) : super(offset: offset); ContainerLayer({ Offset offset: Offset.zero }) : super(offset: offset);
......
...@@ -16,6 +16,8 @@ import 'package:vector_math/vector_math_64.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 'dart:sky' show Point, Offset, Size, Rect, Color, Paint, Path;
export 'package:sky/src/rendering/hit_test.dart' show HitTestTarget, HitTestEntry, HitTestResult; 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 /// Base class for data associated with a [RenderObject] by its parent
/// ///
/// Some render objects wish to store data on their children, such as their /// Some render objects wish to store data on their children, such as their
...@@ -130,6 +132,15 @@ class PaintingContext { ...@@ -130,6 +132,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 // Below we have various variants of the paintChild() method, which
// do additional work, such as clipping or transforming, at the same // do additional work, such as clipping or transforming, at the same
// time as painting the children. // time as painting the children.
...@@ -300,6 +311,34 @@ class PaintingContext { ...@@ -300,6 +311,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 /// 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 /// Do not call directly. This function is visible so that it can be
......
...@@ -659,6 +659,37 @@ class RenderColorFilter extends RenderProxyBox { ...@@ -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 /// Clips its child using a rectangle
/// ///
/// Prevents its child from painting outside its bounds. /// Prevents its child from painting outside its bounds.
......
// 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);
}
}
...@@ -24,15 +24,15 @@ abstract class RenderToggleable extends RenderConstrainedBox { ...@@ -24,15 +24,15 @@ abstract class RenderToggleable extends RenderConstrainedBox {
: _value = value, : _value = value,
_onChanged = onChanged, _onChanged = onChanged,
super(additionalConstraints: new BoxConstraints.tight(size)) { super(additionalConstraints: new BoxConstraints.tight(size)) {
_performance = new ValueAnimation<double>( _performance = new ValuePerformance<double>(
variable: new AnimatedValue<double>(0.0, end: 1.0, curve: easeIn, reverseCurve: easeOut), variable: new AnimatedValue<double>(0.0, end: 1.0, curve: easeIn, reverseCurve: easeOut),
duration: _kToggleDuration, duration: _kToggleDuration,
progress: _value ? 1.0 : 0.0 progress: _value ? 1.0 : 0.0
)..addListener(markNeedsPaint); )..addListener(markNeedsPaint);
} }
ValueAnimation<double> get performance => _performance; ValuePerformance<double> get performance => _performance;
ValueAnimation<double> _performance; ValuePerformance<double> _performance;
double get position => _performance.value; double get position => _performance.value;
...@@ -51,7 +51,7 @@ abstract class RenderToggleable extends RenderConstrainedBox { ...@@ -51,7 +51,7 @@ abstract class RenderToggleable extends RenderConstrainedBox {
); );
} }
void detatch() { void detach() {
_tap.dispose(); _tap.dispose();
_tap = null; _tap = null;
super.detach(); super.detach();
...@@ -68,7 +68,7 @@ abstract class RenderToggleable extends RenderConstrainedBox { ...@@ -68,7 +68,7 @@ abstract class RenderToggleable extends RenderConstrainedBox {
if (value == _value) if (value == _value)
return; return;
_value = value; _value = value;
performance.play(value ? Direction.forward : Direction.reverse); performance.play(value ? AnimationDirection.forward : AnimationDirection.reverse);
} }
ValueChanged get onChanged => _onChanged; ValueChanged get onChanged => _onChanged;
......
...@@ -9,13 +9,13 @@ abstract class AnimatedComponent extends StatefulComponent { ...@@ -9,13 +9,13 @@ abstract class AnimatedComponent extends StatefulComponent {
const AnimatedComponent({ Key key, this.direction, this.duration }) : super(key: key); const AnimatedComponent({ Key key, this.direction, this.duration }) : super(key: key);
final Duration duration; final Duration duration;
final Direction direction; final AnimationDirection direction;
} }
abstract class AnimatedState<T extends AnimatedComponent> extends State<T> { abstract class AnimatedState<T extends AnimatedComponent> extends State<T> {
void initState() { void initState() {
super.initState(); super.initState();
_performance = new AnimationPerformance(duration: config.duration); _performance = new Performance(duration: config.duration);
performance.addStatusListener(_handleAnimationStatusChanged); performance.addStatusListener(_handleAnimationStatusChanged);
if (buildDependsOnPerformance) { if (buildDependsOnPerformance) {
performance.addListener(() { performance.addListener(() {
...@@ -34,13 +34,13 @@ abstract class AnimatedState<T extends AnimatedComponent> extends State<T> { ...@@ -34,13 +34,13 @@ abstract class AnimatedState<T extends AnimatedComponent> extends State<T> {
performance.play(config.direction); performance.play(config.direction);
} }
AnimationPerformance get performance => _performance; Performance get performance => _performance;
AnimationPerformance _performance; Performance _performance;
void _handleAnimationStatusChanged(AnimationStatus status) { void _handleAnimationStatusChanged(PerformanceStatus status) {
if (status == AnimationStatus.completed) if (status == PerformanceStatus.completed)
handleCompleted(); handleCompleted();
else if (status == AnimationStatus.dismissed) else if (status == PerformanceStatus.dismissed)
handleDismissed(); handleDismissed();
} }
......
// 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<BoxConstraints> {
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<BoxDecoration> {
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<EdgeDims> {
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<Matrix4> {
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
// 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<AnimatedContainer> {
AnimatedBoxConstraintsValue _constraints;
AnimatedBoxDecorationValue _decoration;
AnimatedBoxDecorationValue _foregroundDecoration;
AnimatedEdgeDimsValue _margin;
AnimatedEdgeDimsValue _padding;
AnimatedMatrix4Value _transform;
AnimatedValue<double> _width;
AnimatedValue<double> _height;
Performance _performance;
void initState() {
super.initState();
_performance = new Performance(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(Animatable 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<double>(config.width);
if (_configVariable(_width, config.width))
needsAnimation = true;
} else {
_width = null;
}
if (config.height != null) {
_height ??= new AnimatedValue<double>(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
);
}
}
...@@ -32,7 +32,15 @@ class App extends StatefulComponent { ...@@ -32,7 +32,15 @@ class App extends StatefulComponent {
this.theme, this.theme,
this.routes, this.routes,
this.onGenerateRoute 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 String title;
final ThemeData theme; final ThemeData theme;
......
...@@ -22,6 +22,7 @@ export 'package:sky/rendering.dart' show ...@@ -22,6 +22,7 @@ export 'package:sky/rendering.dart' show
FlexAlignItems, FlexAlignItems,
FlexDirection, FlexDirection,
FlexJustifyContent, FlexJustifyContent,
Matrix4,
Offset, Offset,
Paint, Paint,
Path, Path,
...@@ -69,6 +70,33 @@ class ColorFilter extends OneChildRenderObjectWidget { ...@@ -69,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 { class DecoratedBox extends OneChildRenderObjectWidget {
DecoratedBox({ DecoratedBox({
Key key, Key key,
...@@ -392,11 +420,11 @@ class Container extends StatelessComponent { ...@@ -392,11 +420,11 @@ class Container extends StatelessComponent {
this.constraints, this.constraints,
this.decoration, this.decoration,
this.foregroundDecoration, this.foregroundDecoration,
this.width,
this.height,
this.margin, this.margin,
this.padding, this.padding,
this.transform this.transform,
this.width,
this.height
}) : super(key: key) { }) : super(key: key) {
assert(margin == null || margin.isNonNegative); assert(margin == null || margin.isNonNegative);
assert(padding == null || padding.isNonNegative); assert(padding == null || padding.isNonNegative);
...@@ -684,6 +712,11 @@ class DefaultTextStyle extends InheritedWidget { ...@@ -684,6 +712,11 @@ class DefaultTextStyle extends InheritedWidget {
} }
bool updateShouldNotify(DefaultTextStyle old) => style != old.style; bool updateShouldNotify(DefaultTextStyle old) => style != old.style;
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
'$style'.split('\n').forEach(description.add);
}
} }
class Text extends StatelessComponent { class Text extends StatelessComponent {
...@@ -710,6 +743,13 @@ class Text extends StatelessComponent { ...@@ -710,6 +743,13 @@ class Text extends StatelessComponent {
text = new StyledTextSpan(combinedStyle, [text]); text = new StyledTextSpan(combinedStyle, [text]);
return new Paragraph(text: text); return new Paragraph(text: text);
} }
void debugFillDescription(List<String> description) {
super.debugFillDescription(description);
description.add('"$data"');
if (style != null)
'$style'.split('\n').forEach(description.add);
}
} }
class Image extends LeafRenderObjectWidget { class Image extends LeafRenderObjectWidget {
...@@ -882,7 +922,7 @@ class Listener extends OneChildRenderObjectWidget { ...@@ -882,7 +922,7 @@ class Listener extends OneChildRenderObjectWidget {
this.onPointerMove, this.onPointerMove,
this.onPointerUp, this.onPointerUp,
this.onPointerCancel this.onPointerCancel
}): super(key: key, child: child); }) : super(key: key, child: child);
final PointerEventListener onPointerDown; final PointerEventListener onPointerDown;
final PointerEventListener onPointerMove; final PointerEventListener onPointerMove;
......
...@@ -35,7 +35,7 @@ class WidgetFlutterBinding extends FlutterBinding { ...@@ -35,7 +35,7 @@ class WidgetFlutterBinding extends FlutterBinding {
); );
} }
void beginFrame(double timeStamp) { void beginFrame(Duration timeStamp) {
buildDirtyElements(); buildDirtyElements();
super.beginFrame(timeStamp); super.beginFrame(timeStamp);
Element.finalizeTree(); Element.finalizeTree();
......
...@@ -61,7 +61,7 @@ class _CheckboxWrapper extends LeafRenderObjectWidget { ...@@ -61,7 +61,7 @@ class _CheckboxWrapper extends LeafRenderObjectWidget {
this.onChanged, this.onChanged,
this.uncheckedColor, this.uncheckedColor,
this.accentColor this.accentColor
}): super(key: key) { }) : super(key: key) {
assert(uncheckedColor != null); assert(uncheckedColor != null);
assert(accentColor != null); assert(accentColor != null);
} }
......
...@@ -375,13 +375,12 @@ class YearPickerState extends ScrollableWidgetListState<YearPicker> { ...@@ -375,13 +375,12 @@ class YearPickerState extends ScrollableWidgetListState<YearPicker> {
for(int i = start; i < start + count; i++) { for(int i = start; i < start + count; i++) {
int year = config.firstDate.year + i; int year = config.firstDate.year + i;
String label = year.toString(); String label = year.toString();
Widget item = new GestureDetector( Widget item = new InkWell(
key: new Key(label), key: new Key(label),
onTap: () { onTap: () {
DateTime result = new DateTime(year, config.selectedDate.month, config.selectedDate.day); DateTime result = new DateTime(year, config.selectedDate.month, config.selectedDate.day);
config.onChanged(result); config.onChanged(result);
}, },
child: new InkWell(
child: new Container( child: new Container(
height: config.itemExtent, height: config.itemExtent,
decoration: year == config.selectedDate.year ? new BoxDecoration( decoration: year == config.selectedDate.year ? new BoxDecoration(
...@@ -392,7 +391,6 @@ class YearPickerState extends ScrollableWidgetListState<YearPicker> { ...@@ -392,7 +391,6 @@ class YearPickerState extends ScrollableWidgetListState<YearPicker> {
child: new Text(label, style: style) child: new Text(label, style: style)
) )
) )
)
); );
items.add(item); items.add(item);
} }
......
...@@ -5,6 +5,7 @@ ...@@ -5,6 +5,7 @@
import 'dart:async'; import 'dart:async';
import 'package:sky/animation.dart'; import 'package:sky/animation.dart';
import 'package:sky/gestures.dart';
import 'package:sky/material.dart'; import 'package:sky/material.dart';
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/focus.dart'; import 'package:sky/src/widgets/focus.dart';
...@@ -30,7 +31,7 @@ class Dialog extends StatelessComponent { ...@@ -30,7 +31,7 @@ class Dialog extends StatelessComponent {
this.contentPadding, this.contentPadding,
this.actions, this.actions,
this.onDismiss this.onDismiss
}): super(key: key); }) : super(key: key);
/// The (optional) title of the dialog is displayed in a large font at the top /// The (optional) title of the dialog is displayed in a large font at the top
/// of the dialog. /// of the dialog.
...@@ -52,7 +53,7 @@ class Dialog extends StatelessComponent { ...@@ -52,7 +53,7 @@ class Dialog extends StatelessComponent {
final List<Widget> actions; final List<Widget> actions;
/// An (optional) callback that is called when the dialog is dismissed. /// An (optional) callback that is called when the dialog is dismissed.
final Function onDismiss; final GestureTapCallback onDismiss;
Color _getColor(BuildContext context) { Color _getColor(BuildContext context) {
switch (Theme.of(context).brightness) { switch (Theme.of(context).brightness) {
...@@ -140,11 +141,11 @@ class DialogRoute extends Route { ...@@ -140,11 +141,11 @@ class DialogRoute extends Route {
Duration get transitionDuration => _kTransitionDuration; Duration get transitionDuration => _kTransitionDuration;
bool get opaque => false; bool get opaque => false;
Widget build(NavigatorState navigator, WatchableAnimationPerformance nextRoutePerformance) { Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance) {
return new FadeTransition( return new FadeTransition(
performance: performance, performance: performance,
opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: easeOut), opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: easeOut),
child: builder(navigator, this) child: builder(new RouteArguments(navigator: navigator, previousPerformance: this.performance, nextPerformance: nextRoutePerformance))
); );
} }
...@@ -158,11 +159,11 @@ Future showDialog(NavigatorState navigator, DialogBuilder builder) { ...@@ -158,11 +159,11 @@ Future showDialog(NavigatorState navigator, DialogBuilder builder) {
Completer completer = new Completer(); Completer completer = new Completer();
navigator.push(new DialogRoute( navigator.push(new DialogRoute(
completer: completer, completer: completer,
builder: (navigator, route) { builder: (RouteArguments args) {
return new Focus( return new Focus(
key: new GlobalObjectKey(route), key: new GlobalObjectKey(completer),
autofocus: true, autofocus: true,
child: builder(navigator) child: builder(args.navigator)
); );
} }
)); ));
......
...@@ -12,7 +12,7 @@ import 'package:sky/src/widgets/gesture_detector.dart'; ...@@ -12,7 +12,7 @@ import 'package:sky/src/widgets/gesture_detector.dart';
const Duration _kCardDismissFadeout = const Duration(milliseconds: 200); const Duration _kCardDismissFadeout = const Duration(milliseconds: 200);
const Duration _kCardDismissResize = const Duration(milliseconds: 300); 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 _kMinFlingVelocity = 700.0;
const double _kMinFlingVelocityDelta = 400.0; const double _kMinFlingVelocityDelta = 400.0;
const double _kFlingVelocityScale = 1.0 / 300.0; const double _kFlingVelocityScale = 1.0 / 300.0;
...@@ -50,15 +50,15 @@ class Dismissable extends StatefulComponent { ...@@ -50,15 +50,15 @@ class Dismissable extends StatefulComponent {
class DismissableState extends State<Dismissable> { class DismissableState extends State<Dismissable> {
void initState() { void initState() {
super.initState(); super.initState();
_fadePerformance = new AnimationPerformance(duration: _kCardDismissFadeout); _fadePerformance = new Performance(duration: _kCardDismissFadeout);
_fadePerformance.addStatusListener((AnimationStatus status) { _fadePerformance.addStatusListener((PerformanceStatus status) {
if (status == AnimationStatus.completed) if (status == PerformanceStatus.completed)
_handleFadeCompleted(); _handleFadeCompleted();
}); });
} }
AnimationPerformance _fadePerformance; Performance _fadePerformance;
AnimationPerformance _resizePerformance; Performance _resizePerformance;
Size _size; Size _size;
double _dragExtent = 0.0; double _dragExtent = 0.0;
...@@ -97,16 +97,11 @@ class DismissableState extends State<Dismissable> { ...@@ -97,16 +97,11 @@ class DismissableState extends State<Dismissable> {
assert(_resizePerformance == null); assert(_resizePerformance == null);
setState(() { setState(() {
_resizePerformance = new AnimationPerformance() _resizePerformance = new Performance()
..duration = _kCardDismissResize ..duration = _kCardDismissResize
..addListener(_handleResizeProgressChanged); ..addListener(_handleResizeProgressChanged);
_resizePerformance.play(); _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() { void _handleResizeProgressChanged() {
...@@ -226,13 +221,12 @@ class DismissableState extends State<Dismissable> { ...@@ -226,13 +221,12 @@ class DismissableState extends State<Dismissable> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
if (_resizePerformance != null) { if (_resizePerformance != null) {
// make sure you remove this widget once it's been dismissed! // make sure you remove this widget once it's been dismissed!
assert(_resizePerformance.status == AnimationStatus.forward); assert(_resizePerformance.status == PerformanceStatus.forward);
AnimatedValue<double> squashAxisExtent = new AnimatedValue<double>( AnimatedValue<double> squashAxisExtent = new AnimatedValue<double>(
_directionIsYAxis ? _size.width : _size.height, _directionIsYAxis ? _size.width : _size.height,
end: 0.0, end: 0.0,
curve: ease, curve: _kCardDismissResizeCurve
interval: _kCardDismissResizeInterval
); );
return new SquashTransition( return new SquashTransition(
......
...@@ -45,7 +45,7 @@ class Draggable extends StatefulComponent { ...@@ -45,7 +45,7 @@ class Draggable extends StatefulComponent {
this.feedback, this.feedback,
this.feedbackOffset: Offset.zero, this.feedbackOffset: Offset.zero,
this.dragAnchor: DragAnchor.child this.dragAnchor: DragAnchor.child
}): super(key: key) { }) : super(key: key) {
assert(navigator != null); assert(navigator != null);
assert(child != null); assert(child != null);
assert(feedback != null); assert(feedback != null);
...@@ -258,7 +258,7 @@ class DragRoute extends Route { ...@@ -258,7 +258,7 @@ class DragRoute extends Route {
bool get opaque => false; bool get opaque => false;
Duration get transitionDuration => const Duration(); Duration get transitionDuration => const Duration();
Widget build(NavigatorState navigator, WatchableAnimationPerformance nextRoutePerformance) { Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance) {
return new Positioned( return new Positioned(
left: _lastOffset.dx, left: _lastOffset.dx,
top: _lastOffset.dy, top: _lastOffset.dy,
......
...@@ -2,17 +2,16 @@ ...@@ -2,17 +2,16 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async';
import 'package:sky/animation.dart'; import 'package:sky/animation.dart';
import 'package:sky/material.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/framework.dart';
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/gesture_detector.dart'; import 'package:sky/src/widgets/gesture_detector.dart';
import 'package:sky/src/widgets/navigator.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/theme.dart';
import 'package:sky/src/widgets/transitions.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: // TODO(eseidel): Draw width should vary based on device size:
// http://www.google.com/design/spec/layout/structure.html#structure-side-nav // http://www.google.com/design/spec/layout/structure.html#structure-side-nav
...@@ -35,22 +34,16 @@ const Duration _kThemeChangeDuration = const Duration(milliseconds: 200); ...@@ -35,22 +34,16 @@ const Duration _kThemeChangeDuration = const Duration(milliseconds: 200);
const Point _kOpenPosition = Point.origin; const Point _kOpenPosition = Point.origin;
const Point _kClosedPosition = const Point(-_kWidth, 0.0); const Point _kClosedPosition = const Point(-_kWidth, 0.0);
typedef void DrawerDismissedCallback();
class Drawer extends StatefulComponent { class Drawer extends StatefulComponent {
Drawer({ Drawer({
Key key, Key key,
this.children, this.child,
this.showing: false, this.level: 3,
this.level: 0,
this.onDismissed,
this.navigator this.navigator
}) : super(key: key); }) : super(key: key);
final List<Widget> children; final Widget child;
final bool showing;
final int level; final int level;
final DrawerDismissedCallback onDismissed;
final NavigatorState navigator; final NavigatorState navigator;
DrawerState createState() => new DrawerState(); DrawerState createState() => new DrawerState();
...@@ -59,57 +52,37 @@ class Drawer extends StatefulComponent { ...@@ -59,57 +52,37 @@ class Drawer extends StatefulComponent {
class DrawerState extends State<Drawer> { class DrawerState extends State<Drawer> {
void initState() { void initState() {
super.initState(); super.initState();
_performance = new AnimationPerformance(duration: _kBaseSettleDuration); _performance = new Performance(duration: _kBaseSettleDuration)
_performance.addStatusListener((AnimationStatus status) { ..addStatusListener((PerformanceStatus status) {
if (status == AnimationStatus.dismissed) if (status == PerformanceStatus.dismissed)
_handleDismissed(); config.navigator.pop();
});
// 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());
}); });
_open();
} }
_performance.play(_direction);
}
AnimationPerformance _performance;
Direction get _direction => config.showing ? Direction.forward : Direction.reverse; Performance _performance;
void didUpdateConfig(Drawer oldConfig) {
if (config.showing != oldConfig.showing)
_performance.play(_direction);
}
Widget build(BuildContext context) { Widget build(BuildContext context) {
var mask = new GestureDetector( Widget mask = new GestureDetector(
onTap: _close,
child: new ColorTransition( child: new ColorTransition(
performance: _performance.view, performance: _performance.view,
color: new AnimatedColorValue(Colors.transparent, end: const Color(0x7F000000)), color: new AnimatedColorValue(Colors.transparent, end: Colors.black54),
child: new Container() child: new Container()
), )
onTap: () {
_performance.reverse();
}
); );
Widget content = new SlideTransition( Widget content = new SlideTransition(
performance: _performance.view, performance: _performance.view,
position: new AnimatedValue<Point>(_kClosedPosition, end: _kOpenPosition), position: new AnimatedValue<Point>(_kClosedPosition, end: _kOpenPosition),
// TODO(abarth): Use AnimatedContainer child: new AnimatedContainer(
child: new Container( curve: ease,
// behavior: implicitlyAnimate(const Duration(milliseconds: 200)), duration: _kThemeChangeDuration,
decoration: new BoxDecoration( decoration: new BoxDecoration(
backgroundColor: Theme.of(context).canvasColor, backgroundColor: Theme.of(context).canvasColor,
boxShadow: shadows[config.level]), boxShadow: shadows[config.level]),
width: _kWidth, width: _kWidth,
child: new Block(config.children) child: config.child
) )
); );
...@@ -117,33 +90,64 @@ class DrawerState extends State<Drawer> { ...@@ -117,33 +90,64 @@ class DrawerState extends State<Drawer> {
onHorizontalDragStart: _performance.stop, onHorizontalDragStart: _performance.stop,
onHorizontalDragUpdate: _handleDragUpdate, onHorizontalDragUpdate: _handleDragUpdate,
onHorizontalDragEnd: _handleDragEnd, 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; bool get _isMostlyClosed => _performance.progress < 0.5;
void _settle() { _isMostlyClosed ? _performance.reverse() : _performance.play(); }
void _handleDragUpdate(double delta) { void _handleDragUpdate(double delta) {
_performance.progress += delta / _kWidth; _performance.progress += delta / _kWidth;
} }
void _open() {
_performance.fling(velocity: 1.0);
}
void _close() {
_performance.fling(velocity: -1.0);
}
void _handleDragEnd(Offset velocity) { void _handleDragEnd(Offset velocity) {
if (velocity.dx.abs() >= _kMinFlingVelocity) { if (velocity.dx.abs() >= _kMinFlingVelocity) {
_performance.fling(velocity: velocity.dx * _kFlingVelocityScale); _performance.fling(velocity: velocity.dx * _kFlingVelocityScale);
} else if (_isMostlyClosed) {
_close();
} else { } 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, PerformanceView 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));
} }
...@@ -10,7 +10,6 @@ import 'package:sky/painting.dart'; ...@@ -10,7 +10,6 @@ import 'package:sky/painting.dart';
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/button_state.dart'; import 'package:sky/src/widgets/button_state.dart';
import 'package:sky/src/widgets/framework.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/icon.dart';
import 'package:sky/src/widgets/ink_well.dart'; import 'package:sky/src/widgets/ink_well.dart';
import 'package:sky/src/widgets/theme.dart'; import 'package:sky/src/widgets/theme.dart';
...@@ -21,7 +20,7 @@ class DrawerItem extends StatefulComponent { ...@@ -21,7 +20,7 @@ class DrawerItem extends StatefulComponent {
final String icon; final String icon;
final Widget child; final Widget child;
final GestureTapListener onPressed; final GestureTapCallback onPressed;
final bool selected; final bool selected;
DrawerItemState createState() => new DrawerItemState(); DrawerItemState createState() => new DrawerItemState();
...@@ -76,15 +75,13 @@ class DrawerItemState extends ButtonState<DrawerItem> { ...@@ -76,15 +75,13 @@ class DrawerItemState extends ButtonState<DrawerItem> {
) )
); );
return new GestureDetector( return new Container(
onTap: config.onPressed,
child: new Container(
height: 48.0, height: 48.0,
decoration: new BoxDecoration(backgroundColor: _getBackgroundColor(themeData)), decoration: new BoxDecoration(backgroundColor: _getBackgroundColor(themeData)),
child: new InkWell( child: new InkWell(
onTap: config.onPressed,
child: new Row(flexChildren) child: new Row(flexChildren)
) )
)
); );
} }
} }
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:sky/gestures.dart';
import 'package:sky/material.dart'; import 'package:sky/material.dart';
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/framework.dart';
...@@ -13,7 +14,7 @@ class FlatButton extends MaterialButton { ...@@ -13,7 +14,7 @@ class FlatButton extends MaterialButton {
Key key, Key key,
Widget child, Widget child,
bool enabled: true, bool enabled: true,
Function onPressed GestureTapCallback onPressed
}) : super(key: key, }) : super(key: key,
child: child, child: child,
enabled: enabled, enabled: enabled,
......
...@@ -6,7 +6,6 @@ import 'package:sky/gestures.dart'; ...@@ -6,7 +6,6 @@ import 'package:sky/gestures.dart';
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/button_state.dart'; import 'package:sky/src/widgets/button_state.dart';
import 'package:sky/src/widgets/framework.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/icon.dart';
import 'package:sky/src/widgets/ink_well.dart'; import 'package:sky/src/widgets/ink_well.dart';
import 'package:sky/src/widgets/material.dart'; import 'package:sky/src/widgets/material.dart';
...@@ -26,7 +25,7 @@ class FloatingActionButton extends StatefulComponent { ...@@ -26,7 +25,7 @@ class FloatingActionButton extends StatefulComponent {
final Widget child; final Widget child;
final Color backgroundColor; final Color backgroundColor;
final GestureTapListener onPressed; final GestureTapCallback onPressed;
FloatingActionButtonState createState() => new FloatingActionButtonState(); FloatingActionButtonState createState() => new FloatingActionButtonState();
} }
...@@ -46,12 +45,11 @@ class FloatingActionButtonState extends ButtonState<FloatingActionButton> { ...@@ -46,12 +45,11 @@ class FloatingActionButtonState extends ButtonState<FloatingActionButton> {
type: MaterialType.circle, type: MaterialType.circle,
level: highlight ? 3 : 2, level: highlight ? 3 : 2,
child: new ClipOval( child: new ClipOval(
child: new GestureDetector(
onTap: config.onPressed,
child: new Container( child: new Container(
width: _kSize, width: _kSize,
height: _kSize, height: _kSize,
child: new InkWell( child: new InkWell(
onTap: config.onPressed,
child: new Center( child: new Center(
child: new IconTheme( child: new IconTheme(
data: new IconThemeData(color: iconThemeColor), data: new IconThemeData(color: iconThemeColor),
...@@ -61,7 +59,6 @@ class FloatingActionButtonState extends ButtonState<FloatingActionButton> { ...@@ -61,7 +59,6 @@ class FloatingActionButtonState extends ButtonState<FloatingActionButton> {
) )
) )
) )
)
); );
} }
} }
...@@ -181,7 +181,16 @@ abstract class Widget { ...@@ -181,7 +181,16 @@ abstract class Widget {
/// Inflates this configuration to a concrete instance. /// Inflates this configuration to a concrete instance.
Element createElement(); Element createElement();
String toString() => '$runtimeType'; String toString() {
final String name = key == null ? '$runtimeType' : '$runtimeType-$key';
final List<String> data = <String>[];
debugFillDescription(data);
if (data.isEmpty)
return 'name';
return 'name(${data.join("; ")})';
}
void debugFillDescription(List<String> description) { }
} }
/// RenderObjectWidgets provide the configuration for [RenderObjectElement]s, /// RenderObjectWidgets provide the configuration for [RenderObjectElement]s,
...@@ -482,9 +491,11 @@ final _InactiveElements _inactiveElements = new _InactiveElements(); ...@@ -482,9 +491,11 @@ final _InactiveElements _inactiveElements = new _InactiveElements();
typedef void ElementVisitor(Element element); typedef void ElementVisitor(Element element);
abstract class BuildContext { abstract class BuildContext {
InheritedWidget inheritedWidgetOfType(Type targetType); Widget get widget;
RenderObject findRenderObject(); RenderObject findRenderObject();
InheritedWidget inheritedWidgetOfType(Type targetType);
void visitAncestorElements(bool visitor(Element element)); void visitAncestorElements(bool visitor(Element element));
void visitChildElements(void visitor(Element element));
} }
/// Elements are the instantiations of Widget configurations. /// Elements are the instantiations of Widget configurations.
...@@ -536,6 +547,13 @@ abstract class Element<T extends Widget> implements BuildContext { ...@@ -536,6 +547,13 @@ abstract class Element<T extends Widget> implements BuildContext {
/// Calls the argument for each child. Must be overridden by subclasses that support having children. /// Calls the argument for each child. Must be overridden by subclasses that support having children.
void visitChildren(ElementVisitor visitor) { } 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; bool detachChild(Element child) => false;
/// This method is the core of the system. /// This method is the core of the system.
...@@ -782,6 +800,8 @@ abstract class Element<T extends Widget> implements BuildContext { ...@@ -782,6 +800,8 @@ abstract class Element<T extends Widget> implements BuildContext {
description.add('no depth'); description.add('no depth');
if (widget == null) if (widget == null)
description.add('no widget'); description.add('no widget');
else
widget.debugFillDescription(description);
} }
String toStringDeep([String prefixLineOne = '', String prefixOtherLines = '']) { String toStringDeep([String prefixLineOne = '', String prefixOtherLines = '']) {
...@@ -948,11 +968,11 @@ abstract class BuildableElement<T extends Widget> extends Element<T> { ...@@ -948,11 +968,11 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
/// Instantiation of StatelessComponent widgets. /// Instantiation of StatelessComponent widgets.
class StatelessComponentElement<T extends StatelessComponent> extends BuildableElement<T> { class StatelessComponentElement<T extends StatelessComponent> extends BuildableElement<T> {
StatelessComponentElement(StatelessComponent widget) : super(widget) { StatelessComponentElement(T widget) : super(widget) {
_builder = widget.build; _builder = widget.build;
} }
void update(StatelessComponent newWidget) { void update(T newWidget) {
super.update(newWidget); super.update(newWidget);
assert(widget == newWidget); assert(widget == newWidget);
_builder = widget.build; _builder = widget.build;
...@@ -962,10 +982,10 @@ class StatelessComponentElement<T extends StatelessComponent> extends BuildableE ...@@ -962,10 +982,10 @@ class StatelessComponentElement<T extends StatelessComponent> extends BuildableE
} }
/// Instantiation of StatefulComponent widgets. /// Instantiation of StatefulComponent widgets.
class StatefulComponentElement extends BuildableElement<StatefulComponent> { class StatefulComponentElement<T extends StatefulComponent, U extends State<T>> extends BuildableElement<T> {
StatefulComponentElement(StatefulComponent widget) StatefulComponentElement(T widget)
: _state = widget.createState(), super(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); assert(_state._element == null);
_state._element = this; _state._element = this;
assert(_builder == null); assert(_builder == null);
...@@ -988,10 +1008,10 @@ class StatefulComponentElement extends BuildableElement<StatefulComponent> { ...@@ -988,10 +1008,10 @@ class StatefulComponentElement extends BuildableElement<StatefulComponent> {
assert(() { _state._debugLifecycleState = _StateLifecycle.ready; return true; }); assert(() { _state._debugLifecycleState = _StateLifecycle.ready; return true; });
} }
State get state => _state; U get state => _state;
State _state; U _state;
void update(StatefulComponent newWidget) { void update(T newWidget) {
super.update(newWidget); super.update(newWidget);
assert(widget == newWidget); assert(widget == newWidget);
StatefulComponent oldConfig = _state._config; StatefulComponent oldConfig = _state._config;
......
...@@ -15,6 +15,8 @@ class GestureDetector extends StatefulComponent { ...@@ -15,6 +15,8 @@ class GestureDetector extends StatefulComponent {
this.child, this.child,
this.onTap, this.onTap,
this.onDoubleTap, this.onDoubleTap,
this.onTapDown,
this.onTapCancel,
this.onShowPress, this.onShowPress,
this.onLongPress, this.onLongPress,
this.onVerticalDragStart, this.onVerticalDragStart,
...@@ -32,10 +34,14 @@ class GestureDetector extends StatefulComponent { ...@@ -32,10 +34,14 @@ class GestureDetector extends StatefulComponent {
}) : super(key: key); }) : super(key: key);
final Widget child; final Widget child;
final GestureTapListener onTap;
final GestureTapCallback onTap;
final GestureTapCallback onTapDown;
final GestureTapCallback onTapCancel;
final GestureTapListener onDoubleTap; final GestureTapListener onDoubleTap;
final GestureShowPressListener onShowPress;
final GestureLongPressListener onLongPress; final GestureShowPressCallback onShowPress;
final GestureLongPressCallback onLongPress;
final GestureDragStartCallback onVerticalDragStart; final GestureDragStartCallback onVerticalDragStart;
final GestureDragUpdateCallback onVerticalDragUpdate; final GestureDragUpdateCallback onVerticalDragUpdate;
...@@ -57,69 +63,24 @@ class GestureDetector extends StatefulComponent { ...@@ -57,69 +63,24 @@ class GestureDetector extends StatefulComponent {
} }
class GestureDetectorState extends State<GestureDetector> { class GestureDetectorState extends State<GestureDetector> {
void initState() {
super.initState();
didUpdateConfig(null);
}
final PointerRouter _router = FlutterBinding.instance.pointerRouter; final PointerRouter _router = FlutterBinding.instance.pointerRouter;
TapGestureRecognizer _tap; TapGestureRecognizer _tap;
TapGestureRecognizer _ensureTap() {
if (_tap == null)
_tap = new TapGestureRecognizer(router: _router);
return _tap;
}
DoubleTapGestureRecognizer _doubleTap; DoubleTapGestureRecognizer _doubleTap;
DoubleTapGestureRecognizer _ensureDoubleTap() {
if (_doubleTap == null)
_doubleTap = new DoubleTapGestureRecognizer(router: _router);
return _doubleTap;
}
ShowPressGestureRecognizer _showPress; ShowPressGestureRecognizer _showPress;
ShowPressGestureRecognizer _ensureShowPress() {
if (_showPress == null)
_showPress = new ShowPressGestureRecognizer(router: _router);
return _showPress;
}
LongPressGestureRecognizer _longPress; LongPressGestureRecognizer _longPress;
LongPressGestureRecognizer _ensureLongPress() {
if (_longPress == null)
_longPress = new LongPressGestureRecognizer(router: _router);
return _longPress;
}
VerticalDragGestureRecognizer _verticalDrag; VerticalDragGestureRecognizer _verticalDrag;
VerticalDragGestureRecognizer _ensureVerticalDrag() {
if (_verticalDrag == null)
_verticalDrag = new VerticalDragGestureRecognizer(router: _router);
return _verticalDrag;
}
HorizontalDragGestureRecognizer _horizontalDrag; HorizontalDragGestureRecognizer _horizontalDrag;
HorizontalDragGestureRecognizer _ensureHorizontalDrag() {
if (_horizontalDrag == null)
_horizontalDrag = new HorizontalDragGestureRecognizer(router: _router);
return _horizontalDrag;
}
PanGestureRecognizer _pan; PanGestureRecognizer _pan;
PanGestureRecognizer _ensurePan() { ScaleGestureRecognizer _scale;
assert(_scale == null); // Scale is a superset of pan; just use scale
if (_pan == null) void initState() {
_pan = new PanGestureRecognizer(router: _router); super.initState();
return _pan; _syncAll();
} }
ScaleGestureRecognizer _scale; void didUpdateConfig(GestureDetector oldConfig) {
ScaleGestureRecognizer _ensureScale() { _syncAll();
assert(_pan == null); // Scale is a superset of pan; just use scale
if (_scale == null)
_scale = new ScaleGestureRecognizer(router: _router);
return _scale;
} }
void dispose() { void dispose() {
...@@ -134,7 +95,7 @@ class GestureDetectorState extends State<GestureDetector> { ...@@ -134,7 +95,7 @@ class GestureDetectorState extends State<GestureDetector> {
super.dispose(); super.dispose();
} }
void didUpdateConfig(GestureDetector oldConfig) { void _syncAll() {
_syncTap(); _syncTap();
_syncDoubleTap(); _syncDoubleTap();
_syncShowPress(); _syncShowPress();
...@@ -146,10 +107,15 @@ class GestureDetectorState extends State<GestureDetector> { ...@@ -146,10 +107,15 @@ class GestureDetectorState extends State<GestureDetector> {
} }
void _syncTap() { void _syncTap() {
if (config.onTap == null) if (config.onTap == null && config.onTapDown == null && config.onTapCancel == null) {
_tap = _ensureDisposed(_tap); _tap = _ensureDisposed(_tap);
else } else {
_ensureTap().onTap = config.onTap; _tap ??= new TapGestureRecognizer(router: _router);
_tap
..onTap = config.onTap
..onTapDown = config.onTapDown
..onTapCancel = config.onTapCancel;
}
} }
void _syncDoubleTap() { void _syncDoubleTap() {
...@@ -160,24 +126,29 @@ class GestureDetectorState extends State<GestureDetector> { ...@@ -160,24 +126,29 @@ class GestureDetectorState extends State<GestureDetector> {
} }
void _syncShowPress() { void _syncShowPress() {
if (config.onShowPress == null) if (config.onShowPress == null) {
_showPress = _ensureDisposed(_showPress); _showPress = _ensureDisposed(_showPress);
else } else {
_ensureShowPress().onShowPress = config.onShowPress; _showPress ??= new ShowPressGestureRecognizer(router: _router);
_showPress.onShowPress = config.onShowPress;
}
} }
void _syncLongPress() { void _syncLongPress() {
if (config.onLongPress == null) if (config.onLongPress == null) {
_longPress = _ensureDisposed(_longPress); _longPress = _ensureDisposed(_longPress);
else } else {
_ensureLongPress().onLongPress = config.onLongPress; _longPress ??= new LongPressGestureRecognizer(router: _router);
_longPress.onLongPress = config.onLongPress;
}
} }
void _syncVerticalDrag() { void _syncVerticalDrag() {
if (config.onVerticalDragStart == null && config.onVerticalDragUpdate == null && config.onVerticalDragEnd == null) { if (config.onVerticalDragStart == null && config.onVerticalDragUpdate == null && config.onVerticalDragEnd == null) {
_verticalDrag = _ensureDisposed(_verticalDrag); _verticalDrag = _ensureDisposed(_verticalDrag);
} else { } else {
_ensureVerticalDrag() _verticalDrag ??= new VerticalDragGestureRecognizer(router: _router);
_verticalDrag
..onStart = config.onVerticalDragStart ..onStart = config.onVerticalDragStart
..onUpdate = config.onVerticalDragUpdate ..onUpdate = config.onVerticalDragUpdate
..onEnd = config.onVerticalDragEnd; ..onEnd = config.onVerticalDragEnd;
...@@ -188,7 +159,8 @@ class GestureDetectorState extends State<GestureDetector> { ...@@ -188,7 +159,8 @@ class GestureDetectorState extends State<GestureDetector> {
if (config.onHorizontalDragStart == null && config.onHorizontalDragUpdate == null && config.onHorizontalDragEnd == null) { if (config.onHorizontalDragStart == null && config.onHorizontalDragUpdate == null && config.onHorizontalDragEnd == null) {
_horizontalDrag = _ensureDisposed(_horizontalDrag); _horizontalDrag = _ensureDisposed(_horizontalDrag);
} else { } else {
_ensureHorizontalDrag() _horizontalDrag ??= new HorizontalDragGestureRecognizer(router: _router);
_horizontalDrag
..onStart = config.onHorizontalDragStart ..onStart = config.onHorizontalDragStart
..onUpdate = config.onHorizontalDragUpdate ..onUpdate = config.onHorizontalDragUpdate
..onEnd = config.onHorizontalDragEnd; ..onEnd = config.onHorizontalDragEnd;
...@@ -199,7 +171,9 @@ class GestureDetectorState extends State<GestureDetector> { ...@@ -199,7 +171,9 @@ class GestureDetectorState extends State<GestureDetector> {
if (config.onPanStart == null && config.onPanUpdate == null && config.onPanEnd == null) { if (config.onPanStart == null && config.onPanUpdate == null && config.onPanEnd == null) {
_pan = _ensureDisposed(_pan); _pan = _ensureDisposed(_pan);
} else { } else {
_ensurePan() assert(_scale == null); // Scale is a superset of pan; just use scale
_pan ??= new PanGestureRecognizer(router: _router);
_pan
..onStart = config.onPanStart ..onStart = config.onPanStart
..onUpdate = config.onPanUpdate ..onUpdate = config.onPanUpdate
..onEnd = config.onPanEnd; ..onEnd = config.onPanEnd;
...@@ -208,9 +182,11 @@ class GestureDetectorState extends State<GestureDetector> { ...@@ -208,9 +182,11 @@ class GestureDetectorState extends State<GestureDetector> {
void _syncScale() { void _syncScale() {
if (config.onScaleStart == null && config.onScaleUpdate == null && config.onScaleEnd == null) { if (config.onScaleStart == null && config.onScaleUpdate == null && config.onScaleEnd == null) {
_scale = _ensureDisposed(_pan); _scale = _ensureDisposed(_scale);
} else { } else {
_ensureScale() assert(_pan == null); // Scale is a superset of pan; just use scale
_scale ??= new ScaleGestureRecognizer(router: _router);
_scale
..onStart = config.onScaleStart ..onStart = config.onScaleStart
..onUpdate = config.onScaleUpdate ..onUpdate = config.onScaleUpdate
..onEnd = config.onScaleEnd; ..onEnd = config.onScaleEnd;
......
...@@ -4,6 +4,7 @@ ...@@ -4,6 +4,7 @@
import 'dart:sky' as sky; import 'dart:sky' as sky;
import 'package:sky/gestures.dart';
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/icon.dart'; import 'package:sky/src/widgets/icon.dart';
import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/framework.dart';
...@@ -13,8 +14,8 @@ class IconButton extends StatelessComponent { ...@@ -13,8 +14,8 @@ class IconButton extends StatelessComponent {
const IconButton({ Key key, this.icon, this.onPressed, this.color }) : super(key: key); const IconButton({ Key key, this.icon, this.onPressed, this.color }) : super(key: key);
final String icon; final String icon;
final Function onPressed;
final Color color; final Color color;
final GestureTapCallback onPressed;
Widget build(BuildContext context) { Widget build(BuildContext context) {
Widget child = new Icon(type: icon, size: 24); Widget child = new Icon(type: icon, size: 24);
......
...@@ -2,16 +2,18 @@ ...@@ -2,16 +2,18 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'dart:async';
import 'dart:math' as math; import 'dart:math' as math;
import 'dart:sky' as sky; import 'dart:sky' as sky;
import 'package:sky/animation.dart'; import 'package:sky/animation.dart';
import 'package:sky/gestures.dart';
import 'package:sky/rendering.dart'; import 'package:sky/rendering.dart';
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/framework.dart';
const int _kSplashInitialOpacity = 0x30; const int _kSplashInitialOpacity = 0x30;
const double _kSplashCancelledVelocity = 0.7; const double _kSplashCanceledVelocity = 0.7;
const double _kSplashConfirmedVelocity = 0.7; const double _kSplashConfirmedVelocity = 0.7;
const double _kSplashInitialSize = 0.0; const double _kSplashInitialSize = 0.0;
const double _kSplashUnconfirmedVelocity = 0.2; const double _kSplashUnconfirmedVelocity = 0.2;
...@@ -25,40 +27,60 @@ double _getSplashTargetSize(Size bounds, Point position) { ...@@ -25,40 +27,60 @@ double _getSplashTargetSize(Size bounds, Point position) {
} }
class InkSplash { class InkSplash {
InkSplash(this.pointer, this.position, this.well) { InkSplash(this.position, this.well) {
_targetRadius = _getSplashTargetSize(well.size, position); _targetRadius = _getSplashTargetSize(well.size, position);
_radius = new AnimatedValue<double>( _radius = new AnimatedValue<double>(
_kSplashInitialSize, end: _targetRadius, curve: easeOut); _kSplashInitialSize, end: _targetRadius, curve: easeOut);
_performance = new ValueAnimation<double>( _performance = new ValuePerformance<double>(
variable: _radius, variable: _radius,
duration: new Duration(milliseconds: (_targetRadius / _kSplashUnconfirmedVelocity).floor()) duration: new Duration(milliseconds: (_targetRadius / _kSplashUnconfirmedVelocity).floor())
)..addListener(_handleRadiusChange) )..addListener(_handleRadiusChange);
..play();
// Wait kTapTimeout to avoid creating tiny splashes during scrolls.
_startTimer = new Timer(kTapTimeout, _play);
} }
final int pointer;
final Point position; final Point position;
final RenderInkWell well; final RenderInkWell well;
double _targetRadius; double _targetRadius;
double _pinnedRadius; double _pinnedRadius;
AnimatedValue<double> _radius; AnimatedValue<double> _radius;
AnimationPerformance _performance; Performance _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) { void _updateVelocity(double velocity) {
int duration = (_targetRadius / velocity).floor(); int duration = (_targetRadius / velocity).floor();
_performance _performance.duration = new Duration(milliseconds: duration);
..duration = new Duration(milliseconds: duration) _play();
..play();
} }
void confirm() { void confirm() {
if (_cancelStartTimer())
return;
_updateVelocity(_kSplashConfirmedVelocity); _updateVelocity(_kSplashConfirmedVelocity);
_pinnedRadius = null;
} }
void cancel() { void cancel() {
_updateVelocity(_kSplashCancelledVelocity); if (_cancelStartTimer())
return;
_updateVelocity(_kSplashCanceledVelocity);
_pinnedRadius = _radius.value; _pinnedRadius = _radius.value;
} }
...@@ -77,38 +99,95 @@ class InkSplash { ...@@ -77,38 +99,95 @@ class InkSplash {
} }
class RenderInkWell extends RenderProxyBox { 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<InkSplash> _splashes = new List<InkSplash>(); final List<InkSplash> _splashes = new List<InkSplash>();
TapGestureRecognizer _tap;
LongPressGestureRecognizer _longPress;
void handleEvent(sky.Event event, BoxHitTestEntry entry) { void handleEvent(sky.Event event, BoxHitTestEntry entry) {
// TODO(abarth): We should trigger these effects based on gestures. if (event.type == 'pointerdown' && (_tap != null || _longPress != null)) {
// https://github.com/flutter/engine/issues/1271 _tap?.addPointer(event);
if (event is sky.PointerEvent) { _longPress?.addPointer(event);
switch (event.type) { _splashes.add(new InkSplash(entry.localPosition, this));
case 'pointerdown': }
_startSplash(event.pointer, entry.localPosition); }
break;
case 'pointerup': void attach() {
_confirmSplash(event.pointer); super.attach();
break; _syncTapRecognizer();
_syncLongPressRecognizer();
}
void detach() {
_disposeTapRecognizer();
_disposeLongPressRecognizer();
super.detach();
}
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 _startSplash(int pointer, Point position) { void _handleTap() {
_splashes.add(new InkSplash(pointer, position, this)); _splashes.last?.confirm();
markNeedsPaint(); onTap();
} }
void _forEachSplash(int pointer, Function callback) { void _handleTapCancel() {
_splashes.where((splash) => splash.pointer == pointer) _splashes.last?.cancel();
.forEach(callback);
} }
void _confirmSplash(int pointer) { void _handleLongPress() {
_forEachSplash(pointer, (splash) { splash.confirm(); }); _splashes.last?.confirm();
markNeedsPaint(); onLongPress();
} }
void paint(PaintingContext context, Offset offset) { void paint(PaintingContext context, Offset offset) {
...@@ -126,6 +205,20 @@ class RenderInkWell extends RenderProxyBox { ...@@ -126,6 +205,20 @@ class RenderInkWell extends RenderProxyBox {
} }
class InkWell extends OneChildRenderObjectWidget { class InkWell extends OneChildRenderObjectWidget {
InkWell({ Key key, Widget child }) : super(key: key, child: child); InkWell({
RenderInkWell createRenderObject() => new RenderInkWell(); Key key,
Widget child,
this.onTap,
this.onLongPress
}) : super(key: key, child: child);
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;
}
} }
...@@ -28,7 +28,7 @@ class Input extends Scrollable { ...@@ -28,7 +28,7 @@ class Input extends Scrollable {
this.placeholder, this.placeholder,
this.onChanged, this.onChanged,
this.keyboardType: KeyboardType.TEXT this.keyboardType: KeyboardType.TEXT
}): super( }) : super(
key: key, key: key,
initialScrollOffset: 0.0, initialScrollOffset: 0.0,
scrollDirection: ScrollDirection.horizontal scrollDirection: ScrollDirection.horizontal
......
...@@ -2,8 +2,10 @@ ...@@ -2,8 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:sky/animation.dart';
import 'package:sky/painting.dart'; import 'package:sky/painting.dart';
import 'package:sky/material.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/basic.dart';
import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/framework.dart';
import 'package:sky/src/widgets/theme.dart'; import 'package:sky/src/widgets/theme.dart';
...@@ -61,10 +63,11 @@ class Material extends StatelessComponent { ...@@ -61,10 +63,11 @@ class Material extends StatelessComponent {
); );
} }
} }
// TODO(abarth): This should use AnimatedContainer.
return new DefaultTextStyle( return new DefaultTextStyle(
style: Theme.of(context).text.body1, style: Theme.of(context).text.body1,
child: new Container( child: new AnimatedContainer(
curve: ease,
duration: const Duration(milliseconds: 200),
decoration: new BoxDecoration( decoration: new BoxDecoration(
backgroundColor: getBackgroundColor(context), backgroundColor: getBackgroundColor(context),
borderRadius: edges[type], borderRadius: edges[type],
......
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:sky/gestures.dart';
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/button_state.dart'; import 'package:sky/src/widgets/button_state.dart';
import 'package:sky/src/widgets/framework.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/ink_well.dart';
import 'package:sky/src/widgets/material.dart'; import 'package:sky/src/widgets/material.dart';
...@@ -22,7 +22,7 @@ abstract class MaterialButton extends StatefulComponent { ...@@ -22,7 +22,7 @@ abstract class MaterialButton extends StatefulComponent {
final Widget child; final Widget child;
final bool enabled; final bool enabled;
final Function onPressed; final GestureTapCallback onPressed;
} }
abstract class MaterialButtonState<T extends MaterialButton> extends ButtonState<T> { abstract class MaterialButtonState<T extends MaterialButton> extends ButtonState<T> {
...@@ -37,17 +37,17 @@ abstract class MaterialButtonState<T extends MaterialButton> extends ButtonState ...@@ -37,17 +37,17 @@ abstract class MaterialButtonState<T extends MaterialButton> extends ButtonState
child: config.child // TODO(ianh): figure out a way to compell the child to have gray text when disabled... child: config.child // TODO(ianh): figure out a way to compell the child to have gray text when disabled...
) )
); );
return new GestureDetector( return new Container(
onTap: config.enabled ? config.onPressed : null,
child: new Container(
height: 36.0, height: 36.0,
constraints: new BoxConstraints(minWidth: 88.0), constraints: new BoxConstraints(minWidth: 88.0),
margin: new EdgeDims.all(8.0), margin: new EdgeDims.all(8.0),
child: new Material( child: new Material(
type: MaterialType.button, type: MaterialType.button,
child: config.enabled ? new InkWell(child: contents) : contents,
level: level, level: level,
color: getColor(context) color: getColor(context),
child: new InkWell(
onTap: config.enabled ? config.onPressed : null,
child: contents
) )
) )
); );
......
...@@ -22,7 +22,7 @@ class MixedViewport extends RenderObjectWidget { ...@@ -22,7 +22,7 @@ class MixedViewport extends RenderObjectWidget {
this.token, this.token,
this.onExtentsUpdate, this.onExtentsUpdate,
this.onInvalidatorAvailable this.onInvalidatorAvailable
}): super(key: key); }) : super(key: key);
final double startOffset; final double startOffset;
final ScrollDirection direction; final ScrollDirection direction;
......
...@@ -8,7 +8,14 @@ import 'package:sky/src/widgets/focus.dart'; ...@@ -8,7 +8,14 @@ import 'package:sky/src/widgets/focus.dart';
import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/framework.dart';
import 'package:sky/src/widgets/transitions.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 RouteBuilder RouteGenerator(String name);
typedef void StateRouteCallback(StateRoute route); typedef void StateRouteCallback(StateRoute route);
typedef void NotificationCallback(); typedef void NotificationCallback();
...@@ -118,7 +125,7 @@ class NavigatorState extends State<Navigator> { ...@@ -118,7 +125,7 @@ class NavigatorState extends State<Navigator> {
Widget build(BuildContext context) { Widget build(BuildContext context) {
List<Widget> visibleRoutes = new List<Widget>(); List<Widget> visibleRoutes = new List<Widget>();
bool alreadyInsertModalBarrier = false; bool alreadyInsertModalBarrier = false;
WatchableAnimationPerformance nextPerformance; PerformanceView nextPerformance;
for (int i = _history.length-1; i >= 0; i -= 1) { for (int i = _history.length-1; i >= 0; i -= 1) {
Route route = _history[i]; Route route = _history[i];
if (!route.hasContent) { if (!route.hasContent) {
...@@ -126,11 +133,16 @@ class NavigatorState extends State<Navigator> { ...@@ -126,11 +133,16 @@ class NavigatorState extends State<Navigator> {
continue; continue;
} }
route.ensurePerformance( route.ensurePerformance(
direction: (i <= _currentPosition) ? Direction.forward : Direction.reverse direction: (i <= _currentPosition) ? AnimationDirection.forward : AnimationDirection.reverse
); );
route._onDismissed = () { route._onDismissed = () {
setState(() {
assert(_history.contains(route)); assert(_history.contains(route));
if (_history.lastIndexOf(route) <= _currentPosition)
popRoute(route);
};
route._onRemoveRoute = () {
assert(_history.contains(route));
setState(() {
_history.remove(route); _history.remove(route);
}); });
}; };
...@@ -154,33 +166,39 @@ class NavigatorState extends State<Navigator> { ...@@ -154,33 +166,39 @@ class NavigatorState extends State<Navigator> {
} }
return new Focus(child: new Stack(visibleRoutes.reversed.toList())); return new Focus(child: new Stack(visibleRoutes.reversed.toList()));
} }
} }
abstract class Route { abstract class Route {
WatchableAnimationPerformance get performance => _performance?.view; PerformanceView get performance => _performance?.view;
AnimationPerformance _performance; Performance _performance;
NotificationCallback _onDismissed; NotificationCallback _onDismissed;
NotificationCallback _onRemoveRoute;
AnimationPerformance createPerformance() { Performance createPerformance() {
Duration duration = transitionDuration; Duration duration = transitionDuration;
if (duration > Duration.ZERO) { if (duration > Duration.ZERO) {
return new AnimationPerformance(duration: duration) return new Performance(duration: duration)
..addStatusListener((AnimationStatus status) { ..addStatusListener((PerformanceStatus status) {
if (status == AnimationStatus.dismissed && _onDismissed != null) if (status == PerformanceStatus.dismissed) {
if (_onDismissed != null)
_onDismissed(); _onDismissed();
if (_onRemoveRoute != null)
_onRemoveRoute();
}
}); });
} }
return null; return null;
} }
void ensurePerformance({ Direction direction }) { void ensurePerformance({ AnimationDirection direction }) {
assert(direction != null); assert(direction != null);
if (_performance == null) if (_performance == null)
_performance = createPerformance(); _performance = createPerformance();
if (_performance != null) { 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) if (_performance.status != desiredStatus)
_performance.play(direction); _performance.play(direction);
} }
...@@ -236,17 +254,17 @@ abstract class Route { ...@@ -236,17 +254,17 @@ abstract class Route {
/// cover the entire application surface or are in any way semi-transparent. /// cover the entire application surface or are in any way semi-transparent.
bool get opaque => false; 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 /// object, available via the performance field, will be created when the
/// route is first built, using the duration described here. /// route is first built, using the duration described here.
Duration get transitionDuration => Duration.ZERO; Duration get transitionDuration => Duration.ZERO;
bool get isActuallyOpaque => (performance == null || _performance.isCompleted) && opaque; bool get isActuallyOpaque => (performance == null || _performance.isCompleted) && opaque;
Widget build(NavigatorState navigator, WatchableAnimationPerformance nextRoutePerformance); Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance);
void didPop([dynamic result]) { void didPop([dynamic result]) {
if (performance == null && _onDismissed != null) if (performance == null && _onRemoveRoute != null)
_onDismissed(); _onRemoveRoute();
} }
String toString() => '$runtimeType()'; String toString() => '$runtimeType()';
...@@ -263,7 +281,7 @@ class PageRoute extends Route { ...@@ -263,7 +281,7 @@ class PageRoute extends Route {
bool get opaque => true; bool get opaque => true;
Duration get transitionDuration => _kTransitionDuration; 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): Hit testing should ignore transform
// TODO(jackson): Block input unless content is interactive // TODO(jackson): Block input unless content is interactive
return new SlideTransition( return new SlideTransition(
...@@ -272,7 +290,7 @@ class PageRoute extends Route { ...@@ -272,7 +290,7 @@ class PageRoute extends Route {
child: new FadeTransition( child: new FadeTransition(
performance: performance, performance: performance,
opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: easeOut), opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: easeOut),
child: builder(navigator, this) child: builder(new RouteArguments(navigator: navigator, previousPerformance: this.performance, nextPerformance: nextRoutePerformance))
) )
); );
} }
...@@ -296,5 +314,5 @@ class StateRoute extends Route { ...@@ -296,5 +314,5 @@ class StateRoute extends Route {
super.didPop(result); super.didPop(result);
} }
Widget build(NavigatorState navigator, WatchableAnimationPerformance nextRoutePerformance) => null; Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance) => null;
} }
...@@ -11,7 +11,7 @@ import 'package:sky/painting.dart'; ...@@ -11,7 +11,7 @@ import 'package:sky/painting.dart';
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/focus.dart'; import 'package:sky/src/widgets/focus.dart';
import 'package:sky/src/widgets/framework.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/navigator.dart';
import 'package:sky/src/widgets/popup_menu_item.dart'; import 'package:sky/src/widgets/popup_menu_item.dart';
import 'package:sky/src/widgets/scrollable.dart'; import 'package:sky/src/widgets/scrollable.dart';
...@@ -29,7 +29,7 @@ const double _kMenuVerticalPadding = 8.0; ...@@ -29,7 +29,7 @@ const double _kMenuVerticalPadding = 8.0;
typedef List<PopupMenuItem> PopupMenuItemsBuilder(NavigatorState navigator); typedef List<PopupMenuItem> PopupMenuItemsBuilder(NavigatorState navigator);
class PopupMenu extends StatefulComponent { class PopupMenu extends StatelessComponent {
PopupMenu({ PopupMenu({
Key key, Key key,
this.items, this.items,
...@@ -44,78 +44,48 @@ class PopupMenu extends StatefulComponent { ...@@ -44,78 +44,48 @@ class PopupMenu extends StatefulComponent {
final List<PopupMenuItem> items; final List<PopupMenuItem> items;
final int level; final int level;
final NavigatorState navigator; final NavigatorState navigator;
final WatchableAnimationPerformance performance; final PerformanceView performance;
PopupMenuState createState() => new PopupMenuState();
}
class PopupMenuState extends State<PopupMenu> {
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) { Widget build(BuildContext context) {
_updateBoxPainter(new BoxDecoration( final BoxPainter painter = new BoxPainter(new BoxDecoration(
backgroundColor: Theme.of(context).canvasColor, backgroundColor: Theme.of(context).canvasColor,
borderRadius: 2.0, 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<Widget> children = []; List<Widget> children = [];
for (int i = 0; i < config.items.length; ++i) {
for (int i = 0; i < items.length; ++i) {
double start = (i + 1) * unit; double start = (i + 1) * unit;
double end = (start + 1.5 * unit).clamp(0.0, 1.0); double end = (start + 1.5 * unit).clamp(0.0, 1.0);
children.add(new FadeTransition( children.add(new FadeTransition(
performance: config.performance, performance: performance,
opacity: new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(start, end)), opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(start, end)),
child: new GestureDetector( child: new InkWell(
onTap: () { config.navigator.pop(config.items[i].value); }, onTap: () { navigator.pop(items[i].value); },
child: config.items[i] child: items[i]
)) ))
); );
} }
final width = new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(0.0, unit));
final height = new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(0.0, unit * config.items.length)); final width = new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(0.0, unit));
final height = new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(0.0, unit * items.length));
return new FadeTransition( return new FadeTransition(
performance: config.performance, performance: performance,
opacity: new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(0.0, 1.0 / 3.0)), opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(0.0, 1.0 / 3.0)),
child: new Container( child: new Container(
margin: new EdgeDims.all(_kMenuMargin), margin: new EdgeDims.all(_kMenuMargin),
child: new BuilderTransition( child: new BuilderTransition(
performance: config.performance, performance: performance,
variables: [width, height], variables: [width, height],
builder: (BuildContext context) { builder: (BuildContext context) {
return new CustomPaint( return new CustomPaint(
callback: (sky.Canvas canvas, Size size) { callback: (sky.Canvas canvas, Size size) {
double widthValue = width.value * size.width; double widthValue = width.value * size.width;
double heightValue = height.value * size.height; 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( child: new ConstrainedBox(
constraints: new BoxConstraints( constraints: new BoxConstraints(
...@@ -159,10 +129,10 @@ class MenuRoute extends Route { ...@@ -159,10 +129,10 @@ class MenuRoute extends Route {
final PopupMenuItemsBuilder builder; final PopupMenuItemsBuilder builder;
final int level; final int level;
AnimationPerformance createPerformance() { Performance createPerformance() {
AnimationPerformance result = super.createPerformance(); Performance result = super.createPerformance();
AnimationTiming timing = new AnimationTiming(); AnimationTiming timing = new AnimationTiming();
timing.reverseInterval = new Interval(0.0, _kMenuCloseIntervalEnd); timing.reverseCurve = new Interval(0.0, _kMenuCloseIntervalEnd);
result.timing = timing; result.timing = timing;
return result; return result;
} }
...@@ -172,7 +142,7 @@ class MenuRoute extends Route { ...@@ -172,7 +142,7 @@ class MenuRoute extends Route {
bool get opaque => false; bool get opaque => false;
Duration get transitionDuration => _kMenuDuration; Duration get transitionDuration => _kMenuDuration;
Widget build(NavigatorState navigator, WatchableAnimationPerformance nextRoutePerformance) { Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance) {
return new Positioned( return new Positioned(
top: position?.top, top: position?.top,
right: position?.right, right: position?.right,
......
...@@ -4,7 +4,6 @@ ...@@ -4,7 +4,6 @@
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/framework.dart';
import 'package:sky/src/widgets/ink_well.dart';
import 'package:sky/src/widgets/theme.dart'; import 'package:sky/src/widgets/theme.dart';
const double _kMenuItemHeight = 48.0; const double _kMenuItemHeight = 48.0;
...@@ -21,8 +20,7 @@ class PopupMenuItem extends StatelessComponent { ...@@ -21,8 +20,7 @@ class PopupMenuItem extends StatelessComponent {
final dynamic value; final dynamic value;
Widget build(BuildContext context) { Widget build(BuildContext context) {
return new InkWell( return new Container(
child: new Container(
height: _kMenuItemHeight, height: _kMenuItemHeight,
child: new DefaultTextStyle( child: new DefaultTextStyle(
style: Theme.of(context).text.subhead, style: Theme.of(context).text.subhead,
...@@ -31,7 +29,6 @@ class PopupMenuItem extends StatelessComponent { ...@@ -31,7 +29,6 @@ class PopupMenuItem extends StatelessComponent {
child: child child: child
) )
) )
)
); );
} }
} }
...@@ -36,16 +36,16 @@ abstract class ProgressIndicator extends StatefulComponent { ...@@ -36,16 +36,16 @@ abstract class ProgressIndicator extends StatefulComponent {
class ProgressIndicatorState extends State<ProgressIndicator> { class ProgressIndicatorState extends State<ProgressIndicator> {
ValueAnimation<double> _performance; ValuePerformance<double> _performance;
void initState() { void initState() {
super.initState(); super.initState();
_performance = new ValueAnimation<double>( _performance = new ValuePerformance<double>(
variable: new AnimatedValue<double>(0.0, end: 1.0, curve: ease), variable: new AnimatedValue<double>(0.0, end: 1.0, curve: ease),
duration: const Duration(milliseconds: 1500) duration: const Duration(milliseconds: 1500)
); );
_performance.addStatusListener((AnimationStatus status) { _performance.addStatusListener((PerformanceStatus status) {
if (status == AnimationStatus.completed) if (status == PerformanceStatus.completed)
_restartAnimation(); _restartAnimation();
}); });
_performance.play(); _performance.play();
......
...@@ -2,6 +2,7 @@ ...@@ -2,6 +2,7 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:sky/gestures.dart';
import 'package:sky/material.dart'; import 'package:sky/material.dart';
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/framework.dart';
...@@ -13,7 +14,7 @@ class RaisedButton extends MaterialButton { ...@@ -13,7 +14,7 @@ class RaisedButton extends MaterialButton {
Key key, Key key,
Widget child, Widget child,
bool enabled: true, bool enabled: true,
Function onPressed GestureTapCallback onPressed
}) : super(key: key, }) : super(key: key,
child: child, child: child,
enabled: enabled, enabled: enabled,
......
...@@ -51,16 +51,10 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> { ...@@ -51,16 +51,10 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
super.initState(); super.initState();
if (config.initialScrollOffset is double) if (config.initialScrollOffset is double)
_scrollOffset = config.initialScrollOffset; _scrollOffset = config.initialScrollOffset;
_toEndAnimation = new AnimatedSimulation(_setScrollOffset); _animation = new SimulationStepper(_setScrollOffset);
_toOffsetAnimation = new ValueAnimation<double>()
..addListener(() {
AnimatedValue<double> offset = _toOffsetAnimation.variable;
_setScrollOffset(offset.value);
});
} }
AnimatedSimulation _toEndAnimation; // See _startToEndAnimation() SimulationStepper _animation;
ValueAnimation<double> _toOffsetAnimation; // Started by scrollTo()
double _scrollOffset = 0.0; double _scrollOffset = 0.0;
double get scrollOffset => _scrollOffset; double get scrollOffset => _scrollOffset;
...@@ -106,23 +100,10 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> { ...@@ -106,23 +100,10 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
Widget buildContent(BuildContext context); Widget buildContent(BuildContext context);
Future _startToOffsetAnimation(double newScrollOffset, Duration duration, Curve curve) { Future _animateTo(double newScrollOffset, Duration duration, Curve curve) {
_stopAnimations(); _animation.stop();
_toOffsetAnimation _animation.value = scrollOffset;
..variable = new AnimatedValue<double>(scrollOffset, return _animation.animateTo(newScrollOffset, duration: duration, curve: curve);
end: newScrollOffset,
curve: curve
)
..progress = 0.0
..duration = duration;
return _toOffsetAnimation.play();
}
void _stopAnimations() {
if (_toOffsetAnimation.isAnimating)
_toOffsetAnimation.stop();
if (_toEndAnimation.isAnimating)
_toEndAnimation.stop();
} }
bool _scrollOffsetIsInBounds(double offset) { bool _scrollOffsetIsInBounds(double offset) {
...@@ -165,16 +146,16 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> { ...@@ -165,16 +146,16 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
} }
Future _startToEndAnimation({ double velocity }) { Future _startToEndAnimation({ double velocity }) {
_stopAnimations(); _animation.stop();
Simulation simulation = Simulation simulation =
_createSnapSimulation(velocity) ?? _createFlingSimulation(velocity ?? 0.0); _createSnapSimulation(velocity) ?? _createFlingSimulation(velocity ?? 0.0);
if (simulation == null) if (simulation == null)
return new Future.value(); return new Future.value();
return _toEndAnimation.start(simulation); return _animation.animateWith(simulation);
} }
void dispose() { void dispose() {
_stopAnimations(); _animation.stop();
super.dispose(); super.dispose();
} }
...@@ -193,12 +174,12 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> { ...@@ -193,12 +174,12 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
return new Future.value(); return new Future.value();
if (duration == null) { if (duration == null) {
_stopAnimations(); _animation.stop();
_setScrollOffset(newScrollOffset); _setScrollOffset(newScrollOffset);
return new Future.value(); return new Future.value();
} }
return _startToOffsetAnimation(newScrollOffset, duration, curve); return _animateTo(newScrollOffset, duration, curve);
} }
Future scrollBy(double scrollDelta, { Duration duration, Curve curve }) { Future scrollBy(double scrollDelta, { Duration duration, Curve curve }) {
...@@ -209,7 +190,7 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> { ...@@ -209,7 +190,7 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
Future fling(Offset velocity) { Future fling(Offset velocity) {
if (velocity != Offset.zero) if (velocity != Offset.zero)
return _startToEndAnimation(velocity: _scrollVelocity(velocity)); return _startToEndAnimation(velocity: _scrollVelocity(velocity));
if (!_toEndAnimation.isAnimating && (_toOffsetAnimation == null || !_toOffsetAnimation.isAnimating)) if (!_animation.isAnimating)
return settleScrollOffset(); return settleScrollOffset();
return new Future.value(); return new Future.value();
} }
...@@ -226,7 +207,7 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> { ...@@ -226,7 +207,7 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
} }
void _handlePointerDown(_) { void _handlePointerDown(_) {
_stopAnimations(); _animation.stop();
} }
void _handleDragUpdate(double delta) { void _handleDragUpdate(double delta) {
...@@ -337,7 +318,7 @@ class ScrollableViewportState extends ScrollableState<ScrollableViewport> { ...@@ -337,7 +318,7 @@ class ScrollableViewportState extends ScrollableState<ScrollableViewport> {
}); });
} }
void _updateScrollBehaviour() { 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( scrollTo(scrollBehavior.updateExtents(
contentExtent: _childSize, contentExtent: _childSize,
containerExtent: _viewportSize, containerExtent: _viewportSize,
......
...@@ -2,10 +2,10 @@ ...@@ -2,10 +2,10 @@
// Use of this source code is governed by a BSD-style license that can be // Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file. // found in the LICENSE file.
import 'package:sky/animation.dart'; import 'package:sky/animation.dart';
import 'package:sky/painting.dart'; import 'package:sky/gestures.dart';
import 'package:sky/material.dart'; import 'package:sky/material.dart';
import 'package:sky/painting.dart';
import 'package:sky/src/widgets/animated_component.dart'; import 'package:sky/src/widgets/animated_component.dart';
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/framework.dart';
...@@ -28,7 +28,7 @@ class SnackBarAction extends StatelessComponent { ...@@ -28,7 +28,7 @@ class SnackBarAction extends StatelessComponent {
} }
final String label; final String label;
final Function onPressed; final GestureTapCallback onPressed;
Widget build(BuildContext) { Widget build(BuildContext) {
return new GestureDetector( return new GestureDetector(
...@@ -49,7 +49,7 @@ class SnackBar extends AnimatedComponent { ...@@ -49,7 +49,7 @@ class SnackBar extends AnimatedComponent {
this.actions, this.actions,
bool showing, bool showing,
this.onDismissed 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); assert(content != null);
} }
......
// 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;
}
}
...@@ -7,12 +7,12 @@ import 'dart:sky' as sky; ...@@ -7,12 +7,12 @@ import 'dart:sky' as sky;
import 'package:newton/newton.dart'; import 'package:newton/newton.dart';
import 'package:sky/animation.dart'; import 'package:sky/animation.dart';
import 'package:sky/gestures.dart';
import 'package:sky/material.dart';
import 'package:sky/painting.dart'; import 'package:sky/painting.dart';
import 'package:sky/rendering.dart'; import 'package:sky/rendering.dart';
import 'package:sky/material.dart';
import 'package:sky/src/widgets/basic.dart'; import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/framework.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/icon.dart';
import 'package:sky/src/widgets/ink_well.dart'; import 'package:sky/src/widgets/ink_well.dart';
import 'package:sky/src/widgets/scrollable.dart'; import 'package:sky/src/widgets/scrollable.dart';
...@@ -307,6 +307,7 @@ class TabLabel { ...@@ -307,6 +307,7 @@ class TabLabel {
class Tab extends StatelessComponent { class Tab extends StatelessComponent {
Tab({ Tab({
Key key, Key key,
this.onSelected,
this.label, this.label,
this.color, this.color,
this.selected: false, this.selected: false,
...@@ -315,6 +316,7 @@ class Tab extends StatelessComponent { ...@@ -315,6 +316,7 @@ class Tab extends StatelessComponent {
assert(label.text != null || label.icon != null); assert(label.text != null || label.icon != null);
} }
final GestureTapCallback onSelected;
final TabLabel label; final TabLabel label;
final Color color; final Color color;
final bool selected; final bool selected;
...@@ -359,7 +361,10 @@ class Tab extends StatelessComponent { ...@@ -359,7 +361,10 @@ class Tab extends StatelessComponent {
padding: _kTabLabelPadding padding: _kTabLabelPadding
); );
return new InkWell(child: centeredLabel); return new InkWell(
onTap: onSelected,
child: centeredLabel
);
} }
} }
...@@ -403,16 +408,16 @@ class TabBar extends Scrollable { ...@@ -403,16 +408,16 @@ class TabBar extends Scrollable {
class TabBarState extends ScrollableState<TabBar> { class TabBarState extends ScrollableState<TabBar> {
void initState() { void initState() {
super.initState(); super.initState();
_indicatorAnimation = new ValueAnimation<Rect>() _indicatorAnimation = new ValuePerformance<Rect>()
..duration = _kTabBarScroll ..duration = _kTabBarScroll
..variable = new AnimatedRect(null, curve: ease); ..variable = new AnimatedRectValue(null, curve: ease);
scrollBehavior.isScrollable = config.isScrollable; scrollBehavior.isScrollable = config.isScrollable;
} }
Size _tabBarSize; Size _tabBarSize;
Size _viewportSize = Size.zero; Size _viewportSize = Size.zero;
List<double> _tabWidths; List<double> _tabWidths;
ValueAnimation<Rect> _indicatorAnimation; ValuePerformance<Rect> _indicatorAnimation;
void didUpdateConfig(TabBar oldConfig) { void didUpdateConfig(TabBar oldConfig) {
super.didUpdateConfig(oldConfig); super.didUpdateConfig(oldConfig);
...@@ -420,7 +425,7 @@ class TabBarState extends ScrollableState<TabBar> { ...@@ -420,7 +425,7 @@ class TabBarState extends ScrollableState<TabBar> {
scrollTo(0.0); scrollTo(0.0);
} }
AnimatedRect get _indicatorRect => _indicatorAnimation.variable; AnimatedRectValue get _indicatorRect => _indicatorAnimation.variable;
void _startIndicatorAnimation(int fromTabIndex, int toTabIndex) { void _startIndicatorAnimation(int fromTabIndex, int toTabIndex) {
_indicatorRect _indicatorRect
...@@ -458,7 +463,7 @@ class TabBarState extends ScrollableState<TabBar> { ...@@ -458,7 +463,7 @@ class TabBarState extends ScrollableState<TabBar> {
.clamp(scrollBehavior.minScrollOffset, scrollBehavior.maxScrollOffset); .clamp(scrollBehavior.minScrollOffset, scrollBehavior.maxScrollOffset);
} }
void _handleTap(int tabIndex) { void _handleTabSelected(int tabIndex) {
if (tabIndex != config.selectedIndex) { if (tabIndex != config.selectedIndex) {
if (_tabWidths != null) { if (_tabWidths != null) {
if (config.isScrollable) if (config.isScrollable)
...@@ -471,14 +476,12 @@ class TabBarState extends ScrollableState<TabBar> { ...@@ -471,14 +476,12 @@ class TabBarState extends ScrollableState<TabBar> {
} }
Widget _toTab(TabLabel label, int tabIndex, Color color, Color selectedColor) { Widget _toTab(TabLabel label, int tabIndex, Color color, Color selectedColor) {
return new GestureDetector( return new Tab(
onTap: () => _handleTap(tabIndex), onSelected: () => _handleTabSelected(tabIndex),
child: new Tab(
label: label, label: label,
color: color, color: color,
selected: tabIndex == config.selectedIndex, selected: tabIndex == config.selectedIndex,
selectedColor: selectedColor selectedColor: selectedColor
)
); );
} }
......
...@@ -7,7 +7,7 @@ import 'package:sky/src/widgets/basic.dart'; ...@@ -7,7 +7,7 @@ import 'package:sky/src/widgets/basic.dart';
import 'package:sky/src/widgets/framework.dart'; import 'package:sky/src/widgets/framework.dart';
import 'package:vector_math/vector_math_64.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 { abstract class TransitionComponent extends StatefulComponent {
TransitionComponent({ TransitionComponent({
...@@ -17,7 +17,7 @@ abstract class TransitionComponent extends StatefulComponent { ...@@ -17,7 +17,7 @@ abstract class TransitionComponent extends StatefulComponent {
assert(performance != null); assert(performance != null);
} }
final WatchableAnimationPerformance performance; final PerformanceView performance;
Widget build(BuildContext context); Widget build(BuildContext context);
...@@ -57,7 +57,7 @@ abstract class TransitionWithChild extends TransitionComponent { ...@@ -57,7 +57,7 @@ abstract class TransitionWithChild extends TransitionComponent {
TransitionWithChild({ TransitionWithChild({
Key key, Key key,
this.child, this.child,
WatchableAnimationPerformance performance PerformanceView performance
}) : super(key: key, performance: performance); }) : super(key: key, performance: performance);
final Widget child; final Widget child;
...@@ -71,7 +71,7 @@ class SlideTransition extends TransitionWithChild { ...@@ -71,7 +71,7 @@ class SlideTransition extends TransitionWithChild {
SlideTransition({ SlideTransition({
Key key, Key key,
this.position, this.position,
WatchableAnimationPerformance performance, PerformanceView performance,
Widget child Widget child
}) : super(key: key, }) : super(key: key,
performance: performance, performance: performance,
...@@ -91,7 +91,7 @@ class FadeTransition extends TransitionWithChild { ...@@ -91,7 +91,7 @@ class FadeTransition extends TransitionWithChild {
FadeTransition({ FadeTransition({
Key key, Key key,
this.opacity, this.opacity,
WatchableAnimationPerformance performance, PerformanceView performance,
Widget child Widget child
}) : super(key: key, }) : super(key: key,
performance: performance, performance: performance,
...@@ -109,7 +109,7 @@ class ColorTransition extends TransitionWithChild { ...@@ -109,7 +109,7 @@ class ColorTransition extends TransitionWithChild {
ColorTransition({ ColorTransition({
Key key, Key key,
this.color, this.color,
WatchableAnimationPerformance performance, PerformanceView performance,
Widget child Widget child
}) : super(key: key, }) : super(key: key,
performance: performance, performance: performance,
...@@ -131,7 +131,7 @@ class SquashTransition extends TransitionWithChild { ...@@ -131,7 +131,7 @@ class SquashTransition extends TransitionWithChild {
Key key, Key key,
this.width, this.width,
this.height, this.height,
WatchableAnimationPerformance performance, PerformanceView performance,
Widget child Widget child
}) : super(key: key, }) : super(key: key,
performance: performance, performance: performance,
...@@ -156,7 +156,7 @@ class BuilderTransition extends TransitionComponent { ...@@ -156,7 +156,7 @@ class BuilderTransition extends TransitionComponent {
Key key, Key key,
this.variables, this.variables,
this.builder, this.builder,
WatchableAnimationPerformance performance PerformanceView performance
}) : super(key: key, }) : super(key: key,
performance: performance); performance: performance);
......
...@@ -6,6 +6,7 @@ ...@@ -6,6 +6,7 @@
library widgets; library widgets;
export 'src/widgets/animated_component.dart'; export 'src/widgets/animated_component.dart';
export 'src/widgets/animated_container.dart';
export 'src/widgets/app.dart'; export 'src/widgets/app.dart';
export 'src/widgets/basic.dart'; export 'src/widgets/basic.dart';
export 'src/widgets/binding.dart'; export 'src/widgets/binding.dart';
...@@ -43,6 +44,7 @@ export 'src/widgets/radio.dart'; ...@@ -43,6 +44,7 @@ export 'src/widgets/radio.dart';
export 'src/widgets/raised_button.dart'; export 'src/widgets/raised_button.dart';
export 'src/widgets/scaffold.dart'; export 'src/widgets/scaffold.dart';
export 'src/widgets/scrollable.dart'; export 'src/widgets/scrollable.dart';
export 'src/widgets/statistics_overlay.dart';
export 'src/widgets/snack_bar.dart'; export 'src/widgets/snack_bar.dart';
export 'src/widgets/switch.dart'; export 'src/widgets/switch.dart';
export 'src/widgets/tabs.dart'; export 'src/widgets/tabs.dart';
......
...@@ -131,6 +131,19 @@ class Node { ...@@ -131,6 +131,19 @@ class Node {
void set rotation(double rotation) { void set rotation(double rotation) {
assert(rotation != null); 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; _rotation = rotation;
invalidateTransformMatrix(); invalidateTransformMatrix();
} }
...@@ -142,6 +155,19 @@ class Node { ...@@ -142,6 +155,19 @@ class Node {
void set position(Point position) { void set position(Point position) {
assert(position != null); 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; _position = position;
invalidateTransformMatrix(); invalidateTransformMatrix();
} }
...@@ -609,4 +635,24 @@ class Node { ...@@ -609,4 +635,24 @@ class Node {
bool handleEvent(SpriteBoxEvent event) { bool handleEvent(SpriteBoxEvent event) {
return false; 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;
}
} }
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<box2d.Shape> b2Shapes = [];
List<PhysicsShape> 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._bodiesScheduledForDestruction.add(_body);
_attached = false;
}
}
void _addB2Shapes(PhysicsNode physicsNode, PhysicsShape shape, List<box2d.Shape> b2Shapes, List<PhysicsShape> 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);
}
}
}
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;
List<box2d.Body> _bodiesScheduledForDestruction = [];
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) {
// Remove bodies that were marked for destruction during the update phase
_removeBodiesScheduledForDestruction();
// 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()));
}
// 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) {
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<Point> 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()
);
if (type == PhysicsContactType.postSolve) {
}
// 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);
}
}
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<Point> points;
box2d.Shape _createB2Shape(PhysicsNode node) {
List<Vector2> 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<PhysicsShape> shapes;
box2d.Shape _createB2Shape(PhysicsNode node) {
return null;
}
}
...@@ -10,6 +10,7 @@ import 'dart:math' as math; ...@@ -10,6 +10,7 @@ import 'dart:math' as math;
import 'dart:typed_data'; import 'dart:typed_data';
import 'dart:sky' as sky; import 'dart:sky' as sky;
import 'package:box2d/box2d.dart' as box2d;
import 'package:mojo/core.dart'; import 'package:mojo/core.dart';
import 'package:sky_services/media/media.mojom.dart'; import 'package:sky_services/media/media.mojom.dart';
import 'package:sky/animation.dart'; import 'package:sky/animation.dart';
...@@ -31,6 +32,9 @@ part 'node.dart'; ...@@ -31,6 +32,9 @@ part 'node.dart';
part 'node3d.dart'; part 'node3d.dart';
part 'node_with_size.dart'; part 'node_with_size.dart';
part 'particle_system.dart'; part 'particle_system.dart';
part 'physics_body.dart';
part 'physics_node.dart';
part 'physics_shape.dart';
part 'sound.dart'; part 'sound.dart';
part 'sound_manager.dart'; part 'sound_manager.dart';
part 'sprite.dart'; part 'sprite.dart';
......
...@@ -47,7 +47,7 @@ class SpriteBox extends RenderBox { ...@@ -47,7 +47,7 @@ class SpriteBox extends RenderBox {
} }
// Tracking of frame rate and updates // Tracking of frame rate and updates
double _lastTimeStamp; Duration _lastTimeStamp;
double _frameRate = 0.0; double _frameRate = 0.0;
double get frameRate => _frameRate; double get frameRate => _frameRate;
...@@ -76,6 +76,8 @@ class SpriteBox extends RenderBox { ...@@ -76,6 +76,8 @@ class SpriteBox extends RenderBox {
List<Node> _constrainedNodes; List<Node> _constrainedNodes;
List<PhysicsNode> _physicsNodes;
Rect _visibleArea; Rect _visibleArea;
Rect get visibleArea { Rect get visibleArea {
...@@ -84,6 +86,8 @@ class SpriteBox extends RenderBox { ...@@ -84,6 +86,8 @@ class SpriteBox extends RenderBox {
return _visibleArea; return _visibleArea;
} }
bool _initialized = false;
// Setup // Setup
/// Creates a new SpriteBox with a node as its content, by default uses letterboxing. /// Creates a new SpriteBox with a node as its content, by default uses letterboxing.
...@@ -134,19 +138,22 @@ class SpriteBox extends RenderBox { ...@@ -134,19 +138,22 @@ class SpriteBox extends RenderBox {
size = constraints.biggest; size = constraints.biggest;
_invalidateTransformMatrix(); _invalidateTransformMatrix();
_callSpriteBoxPerformedLayout(_rootNode); _callSpriteBoxPerformedLayout(_rootNode);
_initialized = true;
} }
// Adding and removing nodes // Adding and removing nodes
_registerNode(Node node) { void _registerNode(Node node) {
_actionControllers = null; _actionControllers = null;
_eventTargets = null; _eventTargets = null;
_physicsNodes = null;
if (node == null || node.constraints != null) _constrainedNodes = null; if (node == null || node.constraints != null) _constrainedNodes = null;
} }
_deregisterNode(Node node) { void _deregisterNode(Node node) {
_actionControllers = null; _actionControllers = null;
_eventTargets = null; _eventTargets = null;
_physicsNodes = null;
if (node == null || node.constraints != null) _constrainedNodes = null; if (node == null || node.constraints != null) _constrainedNodes = null;
} }
...@@ -345,21 +352,25 @@ class SpriteBox extends RenderBox { ...@@ -345,21 +352,25 @@ class SpriteBox extends RenderBox {
scheduler.requestAnimationFrame(_tick); scheduler.requestAnimationFrame(_tick);
} }
void _tick(double timeStamp) { void _tick(Duration timeStamp) {
if (!attached) if (!attached)
return; return;
// Calculate delta and frame rate // Calculate delta and frame rate
if (_lastTimeStamp == null) _lastTimeStamp = timeStamp; if (_lastTimeStamp == null)
double delta = (timeStamp - _lastTimeStamp) / 1000; _lastTimeStamp = timeStamp;
double delta = (timeStamp - _lastTimeStamp).inMicroseconds.toDouble() / Duration.MICROSECONDS_PER_SECOND;
_lastTimeStamp = timeStamp; _lastTimeStamp = timeStamp;
_frameRate = 1.0/delta; _frameRate = 1.0/delta;
if (_initialized) {
_callConstraintsPreUpdate(delta); _callConstraintsPreUpdate(delta);
_runActions(delta); _runActions(delta);
_callUpdate(_rootNode, delta); _callUpdate(_rootNode, delta);
_callStepPhysics(delta);
_callConstraintsConstrain(delta); _callConstraintsConstrain(delta);
}
// Schedule next update // Schedule next update
_scheduleTick(); _scheduleTick();
...@@ -370,20 +381,26 @@ class SpriteBox extends RenderBox { ...@@ -370,20 +381,26 @@ class SpriteBox extends RenderBox {
void _runActions(double dt) { void _runActions(double dt) {
if (_actionControllers == null) { if (_actionControllers == null) {
_actionControllers = []; _rebuildActionControllersAndPhysicsNodes();
_addActionControllers(_rootNode, _actionControllers);
} }
for (ActionController actions in _actionControllers) { for (ActionController actions in _actionControllers) {
actions.step(dt); actions.step(dt);
} }
} }
void _addActionControllers(Node node, List<ActionController> controllers) { void _rebuildActionControllersAndPhysicsNodes() {
if (node._actions != null) controllers.add(node._actions); _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--) { for (int i = node.children.length - 1; i >= 0; i--) {
Node child = node.children[i]; Node child = node.children[i];
_addActionControllers(child, controllers); _addActionControllersAndPhysicsNodes(child);
} }
} }
...@@ -397,6 +414,15 @@ class SpriteBox extends RenderBox { ...@@ -397,6 +414,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) { void _callConstraintsPreUpdate(double dt) {
if (_constrainedNodes == null) { if (_constrainedNodes == null) {
_constrainedNodes = []; _constrainedNodes = [];
......
...@@ -6,6 +6,7 @@ homepage: http://flutter.io ...@@ -6,6 +6,7 @@ homepage: http://flutter.io
dependencies: dependencies:
sky: ">=0.0.36 < 0.1.0" sky: ">=0.0.36 < 0.1.0"
sky_tools: ">=0.0.10 < 0.1.0" sky_tools: ">=0.0.10 < 0.1.0"
box2d: any
dependency_overrides: dependency_overrides:
sky: sky:
path: ../sky/packages/sky path: ../sky/packages/sky
...@@ -8,18 +8,18 @@ void main() { ...@@ -8,18 +8,18 @@ void main() {
bool firstCallbackRan = false; bool firstCallbackRan = false;
bool secondCallbackRan = false; bool secondCallbackRan = false;
void firstCallback(double timeStamp) { void firstCallback(Duration timeStamp) {
expect(firstCallbackRan, isFalse); expect(firstCallbackRan, isFalse);
expect(secondCallbackRan, isFalse); expect(secondCallbackRan, isFalse);
expect(timeStamp, equals(16.0)); expect(timeStamp.inMilliseconds, equals(16));
firstCallbackRan = true; firstCallbackRan = true;
scheduler.cancelAnimationFrame(secondId); scheduler.cancelAnimationFrame(secondId);
} }
void secondCallback(double timeStamp) { void secondCallback(Duration timeStamp) {
expect(firstCallbackRan, isTrue); expect(firstCallbackRan, isTrue);
expect(secondCallbackRan, isFalse); expect(secondCallbackRan, isFalse);
expect(timeStamp, equals(16.0)); expect(timeStamp.inMilliseconds, equals(16));
secondCallbackRan = true; secondCallbackRan = true;
} }
......
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));
});
});
}
import 'package:sky/rendering.dart';
import 'package:sky/widgets.dart'; import 'package:sky/widgets.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
...@@ -44,13 +45,11 @@ Widget widgetBuilder() { ...@@ -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.horizontal);
assert(gestureDirection != DismissDirection.vertical); assert(gestureDirection != DismissDirection.vertical);
Element itemElement = tester.findText(item.toString());
expect(itemElement, isNotNull);
Point downLocation; Point downLocation;
Point upLocation; Point upLocation;
switch(gestureDirection) { switch(gestureDirection) {
...@@ -84,12 +83,35 @@ void dismissItem(WidgetTester tester, int item, { DismissDirection gestureDirect ...@@ -84,12 +83,35 @@ void dismissItem(WidgetTester tester, int item, { DismissDirection gestureDirect
tester.dispatchEvent(pointer.down(downLocation), downLocation); tester.dispatchEvent(pointer.down(downLocation), downLocation);
tester.dispatchEvent(pointer.move(upLocation), downLocation); tester.dispatchEvent(pointer.move(upLocation), downLocation);
tester.dispatchEvent(pointer.up(), 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()); // start the resize animation
tester.pumpWidget(widgetBuilder(), const Duration(seconds: 1)); // finish the resize animation tester.pumpWidget(widgetBuilder(), const Duration(seconds: 1)); // finish the resize animation
tester.pumpWidget(widgetBuilder(), const Duration(seconds: 1)); // dismiss 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() { void main() {
test('Horizontal drag triggers dismiss scrollDirection=vertical', () { test('Horizontal drag triggers dismiss scrollDirection=vertical', () {
testWidgets((WidgetTester tester) { testWidgets((WidgetTester tester) {
...@@ -230,4 +252,31 @@ void main() { ...@@ -230,4 +252,31 @@ void main() {
tester.pumpWidget(widgetBuilder()); 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);
});
});
} }
...@@ -13,9 +13,9 @@ void main() { ...@@ -13,9 +13,9 @@ void main() {
tester.pumpWidget(new Navigator( tester.pumpWidget(new Navigator(
routes: { routes: {
'/': (NavigatorState navigator, Route route) { return new Column([ '/': (RouteArguments args) { return new Column([
new Draggable( new Draggable(
navigator: navigator, navigator: args.navigator,
data: 1, data: 1,
child: new Text('Source'), child: new Text('Source'),
feedback: new Text('Dragging') feedback: new Text('Dragging')
......
import 'package:sky/rendering.dart';
import 'package:sky/widgets.dart'; import 'package:sky/widgets.dart';
import 'package:test/test.dart'; import 'package:test/test.dart';
...@@ -29,7 +30,8 @@ void main() { ...@@ -29,7 +30,8 @@ void main() {
) )
)); ));
expect(detectedSize, equals(const Size(50.0, 25.0))); 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)));
}); });
}); });
} }
...@@ -88,4 +88,38 @@ void main() { ...@@ -88,4 +88,38 @@ void main() {
tester.pumpWidget(new Container()); 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);
});
});
} }
...@@ -49,8 +49,8 @@ void main() { ...@@ -49,8 +49,8 @@ void main() {
test('Can navigator navigate to and from a stateful component', () { test('Can navigator navigate to and from a stateful component', () {
testWidgets((WidgetTester tester) { testWidgets((WidgetTester tester) {
final Map<String, RouteBuilder> routes = <String, RouteBuilder>{ final Map<String, RouteBuilder> routes = <String, RouteBuilder>{
'/': (navigator, route) => new FirstComponent(navigator), '/': (RouteArguments args) => new FirstComponent(args.navigator),
'/second': (navigator, route) => new SecondComponent(navigator), '/second': (RouteArguments args) => new SecondComponent(args.navigator),
}; };
tester.pumpWidget(new Navigator(routes: routes)); tester.pumpWidget(new Navigator(routes: routes));
......
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));
});
});
}
...@@ -145,7 +145,10 @@ class WidgetTester { ...@@ -145,7 +145,10 @@ class WidgetTester {
} }
void scroll(Element element, Offset offset, { int pointer: 1 }) { 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; Point endLocation = startLocation + offset;
TestPointer p = new TestPointer(pointer); TestPointer p = new TestPointer(pointer);
// Events for the entire press-drag-release gesture are dispatched // Events for the entire press-drag-release gesture are dispatched
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment