Merge remote-tracking branch 'upstream/master'
This commit is contained in:
commit
43a5dff843
@ -11,7 +11,7 @@ class Field extends StatelessComponent {
|
||||
this.inputKey,
|
||||
this.icon,
|
||||
this.placeholder
|
||||
}): super(key: key);
|
||||
}) : super(key: key);
|
||||
|
||||
final GlobalKey inputKey;
|
||||
final String icon;
|
||||
@ -101,7 +101,7 @@ void main() {
|
||||
title: 'Address Book',
|
||||
theme: theme,
|
||||
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) {
|
||||
activity.startActivity(intent);
|
||||
}
|
||||
|
||||
class SkyDemo {
|
||||
SkyDemo({
|
||||
class FlutterDemo {
|
||||
FlutterDemo({
|
||||
name,
|
||||
this.href,
|
||||
this.bundle,
|
||||
@ -60,8 +60,8 @@ class SkyDemo {
|
||||
final BoxDecoration decoration;
|
||||
}
|
||||
|
||||
List<SkyDemo> demos = [
|
||||
new SkyDemo(
|
||||
List<FlutterDemo> demos = [
|
||||
new FlutterDemo(
|
||||
name: 'Stocks',
|
||||
href: '../../stocks/lib/main.dart',
|
||||
bundle: 'stocks.skyx',
|
||||
@ -74,7 +74,7 @@ List<SkyDemo> demos = [
|
||||
)
|
||||
)
|
||||
),
|
||||
new SkyDemo(
|
||||
new FlutterDemo(
|
||||
name: 'Asteroids',
|
||||
href: '../../game/lib/main.dart',
|
||||
bundle: 'game.skyx',
|
||||
@ -87,7 +87,7 @@ List<SkyDemo> demos = [
|
||||
)
|
||||
)
|
||||
),
|
||||
new SkyDemo(
|
||||
new FlutterDemo(
|
||||
name: 'Fitness',
|
||||
href: '../../fitness/lib/main.dart',
|
||||
bundle: 'fitness.skyx',
|
||||
@ -97,7 +97,7 @@ List<SkyDemo> demos = [
|
||||
backgroundColor: Colors.indigo[500]
|
||||
)
|
||||
),
|
||||
new SkyDemo(
|
||||
new FlutterDemo(
|
||||
name: 'Swipe Away',
|
||||
href: '../../widgets/card_collection.dart',
|
||||
bundle: 'cards.skyx',
|
||||
@ -107,7 +107,7 @@ List<SkyDemo> demos = [
|
||||
backgroundColor: Colors.redAccent[200]
|
||||
)
|
||||
),
|
||||
new SkyDemo(
|
||||
new FlutterDemo(
|
||||
name: 'Interactive Text',
|
||||
href: '../../rendering/interactive_flex.dart',
|
||||
bundle: 'interactive_flex.skyx',
|
||||
@ -120,7 +120,7 @@ List<SkyDemo> demos = [
|
||||
// new SkyDemo(
|
||||
|
||||
// 'Touch Demo', '../../rendering/touch_demo.dart', 'Simple example showing handling of touch events at a low level'),
|
||||
new SkyDemo(
|
||||
new FlutterDemo(
|
||||
name: 'Minedigger Game',
|
||||
href: '../../mine_digger/lib/main.dart',
|
||||
bundle: 'mine_digger.skyx',
|
||||
@ -138,44 +138,47 @@ List<SkyDemo> demos = [
|
||||
const double kCardHeight = 120.0;
|
||||
const EdgeDims kListPadding = const EdgeDims.all(4.0);
|
||||
|
||||
class DemoList extends StatelessComponent {
|
||||
Widget buildCardContents(SkyDemo demo) {
|
||||
return new Container(
|
||||
decoration: demo.decoration,
|
||||
child: new InkWell(
|
||||
child: new Container(
|
||||
margin: const EdgeDims.only(top: 24.0, left: 24.0),
|
||||
child: new Column([
|
||||
new Text(demo.name, style: demo.textTheme.title),
|
||||
new Flexible(
|
||||
child: new Text(demo.description, style: demo.textTheme.subhead)
|
||||
)
|
||||
],
|
||||
alignItems: FlexAlignItems.start
|
||||
class DemoCard extends StatelessComponent {
|
||||
DemoCard({ Key key, this.demo }) : super(key: key);
|
||||
|
||||
final FlutterDemo demo;
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return new Container(
|
||||
height: kCardHeight,
|
||||
child: new Card(
|
||||
child: new Container(
|
||||
decoration: demo.decoration,
|
||||
child: new InkWell(
|
||||
onTap: () => launch(demo.href, demo.bundle),
|
||||
child: new Container(
|
||||
margin: const EdgeDims.only(top: 24.0, left: 24.0),
|
||||
child: new Column([
|
||||
new Text(demo.name, style: demo.textTheme.title),
|
||||
new Flexible(
|
||||
child: new Text(demo.description, style: demo.textTheme.subhead)
|
||||
)
|
||||
],
|
||||
alignItems: FlexAlignItems.start
|
||||
)
|
||||
)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildDemo(BuildContext context, SkyDemo demo) {
|
||||
return new GestureDetector(
|
||||
key: demo.key,
|
||||
onTap: () => launch(demo.href, demo.bundle),
|
||||
child: new Container(
|
||||
height: kCardHeight,
|
||||
child: new Card(
|
||||
child: buildCardContents(demo)
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class DemoList extends StatelessComponent {
|
||||
Widget _buildDemoCard(BuildContext context, FlutterDemo demo) {
|
||||
return new DemoCard(key: demo.key, demo: demo);
|
||||
}
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return new ScrollableList<SkyDemo>(
|
||||
return new ScrollableList<FlutterDemo>(
|
||||
items: demos,
|
||||
itemExtent: kCardHeight,
|
||||
itemBuilder: buildDemo,
|
||||
itemBuilder: _buildDemoCard,
|
||||
padding: kListPadding
|
||||
);
|
||||
}
|
||||
@ -200,10 +203,10 @@ class DemoHome extends StatelessComponent {
|
||||
|
||||
void main() {
|
||||
runApp(new App(
|
||||
title: 'Sky Demos',
|
||||
title: 'Flutter Demos',
|
||||
theme: _theme,
|
||||
routes: {
|
||||
'/': (NavigatorState navigator, Route route) => new DemoHome()
|
||||
'/': (RouteArguments args) => new DemoHome()
|
||||
}
|
||||
));
|
||||
}
|
||||
|
@ -33,15 +33,13 @@ class DialogMenuItem extends StatelessComponent {
|
||||
Function onPressed;
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return new GestureDetector(
|
||||
onTap: onPressed,
|
||||
child: new Container(
|
||||
height: 48.0,
|
||||
child: new InkWell(
|
||||
child: new Padding(
|
||||
padding: const EdgeDims.symmetric(horizontal: 16.0),
|
||||
child: new Row(children)
|
||||
)
|
||||
return new Container(
|
||||
height: 48.0,
|
||||
child: new InkWell(
|
||||
onTap: onPressed,
|
||||
child: new Padding(
|
||||
padding: const EdgeDims.symmetric(horizontal: 16.0),
|
||||
child: new Row(children)
|
||||
)
|
||||
)
|
||||
);
|
||||
@ -62,25 +60,20 @@ class FeedFragment extends StatefulComponent {
|
||||
class FeedFragmentState extends State<FeedFragment> {
|
||||
FitnessMode _fitnessMode = FitnessMode.feed;
|
||||
|
||||
AnimationStatus _snackBarStatus = AnimationStatus.dismissed;
|
||||
PerformanceStatus _snackBarStatus = PerformanceStatus.dismissed;
|
||||
bool _isShowingSnackBar = false;
|
||||
|
||||
void _handleFitnessModeChange(FitnessMode value) {
|
||||
setState(() {
|
||||
_fitnessMode = value;
|
||||
_drawerShowing = false;
|
||||
});
|
||||
config.navigator.pop();
|
||||
}
|
||||
|
||||
Drawer buildDrawer() {
|
||||
if (_drawerStatus == AnimationStatus.dismissed)
|
||||
return null;
|
||||
return new Drawer(
|
||||
showing: _drawerShowing,
|
||||
level: 3,
|
||||
onDismissed: _handleDrawerDismissed,
|
||||
void _showDrawer() {
|
||||
showDrawer(
|
||||
navigator: config.navigator,
|
||||
children: [
|
||||
child: new Block([
|
||||
new DrawerHeader(child: new Text('Fitness')),
|
||||
new DrawerItem(
|
||||
icon: 'action/view_list',
|
||||
@ -100,26 +93,10 @@ class FeedFragmentState extends State<FeedFragment> {
|
||||
new DrawerItem(
|
||||
icon: 'action/help',
|
||||
child: new Text('Help & Feedback'))
|
||||
]
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
bool _drawerShowing = false;
|
||||
AnimationStatus _drawerStatus = AnimationStatus.dismissed;
|
||||
|
||||
void _handleOpenDrawer() {
|
||||
setState(() {
|
||||
_drawerShowing = true;
|
||||
_drawerStatus = AnimationStatus.forward;
|
||||
});
|
||||
}
|
||||
|
||||
void _handleDrawerDismissed() {
|
||||
setState(() {
|
||||
_drawerStatus = AnimationStatus.dismissed;
|
||||
});
|
||||
}
|
||||
|
||||
void _handleShowSettings() {
|
||||
config.navigator.pop();
|
||||
config.navigator.pushNamed('/settings');
|
||||
@ -137,7 +114,7 @@ class FeedFragmentState extends State<FeedFragment> {
|
||||
return new ToolBar(
|
||||
left: new IconButton(
|
||||
icon: "navigation/menu",
|
||||
onPressed: _handleOpenDrawer),
|
||||
onPressed: _showDrawer),
|
||||
center: new Text(fitnessModeTitle)
|
||||
);
|
||||
}
|
||||
@ -149,7 +126,7 @@ class FeedFragmentState extends State<FeedFragment> {
|
||||
setState(() {
|
||||
_undoItem = item;
|
||||
_isShowingSnackBar = true;
|
||||
_snackBarStatus = AnimationStatus.forward;
|
||||
_snackBarStatus = PerformanceStatus.forward;
|
||||
});
|
||||
}
|
||||
|
||||
@ -230,13 +207,13 @@ class FeedFragmentState extends State<FeedFragment> {
|
||||
}
|
||||
|
||||
Widget buildSnackBar() {
|
||||
if (_snackBarStatus == AnimationStatus.dismissed)
|
||||
if (_snackBarStatus == PerformanceStatus.dismissed)
|
||||
return null;
|
||||
return new SnackBar(
|
||||
showing: _isShowingSnackBar,
|
||||
content: new Text("Item deleted."),
|
||||
actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)],
|
||||
onDismissed: () { setState(() { _snackBarStatus = AnimationStatus.dismissed; }); }
|
||||
onDismissed: () { setState(() { _snackBarStatus = PerformanceStatus.dismissed; }); }
|
||||
);
|
||||
}
|
||||
|
||||
@ -264,8 +241,7 @@ class FeedFragmentState extends State<FeedFragment> {
|
||||
toolbar: buildToolBar(),
|
||||
body: buildBody(),
|
||||
snackBar: buildSnackBar(),
|
||||
floatingActionButton: buildFloatingActionButton(),
|
||||
drawer: buildDrawer()
|
||||
floatingActionButton: buildFloatingActionButton()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -92,8 +92,6 @@ class FitnessApp extends StatefulComponent {
|
||||
class FitnessAppState extends State<FitnessApp> {
|
||||
UserDataImpl _userData;
|
||||
|
||||
Map<String, RouteBuilder> _routes;
|
||||
|
||||
void initState() {
|
||||
super.initState();
|
||||
loadFitnessData().then((UserData data) {
|
||||
@ -102,36 +100,6 @@ class FitnessAppState extends State<FitnessApp> {
|
||||
print("Failed to load data: $e");
|
||||
setState(() => _userData = new UserDataImpl());
|
||||
});
|
||||
|
||||
_routes = {
|
||||
'/': (NavigatorState navigator, Route route) {
|
||||
return new FeedFragment(
|
||||
navigator: navigator,
|
||||
userData: _userData,
|
||||
onItemCreated: _handleItemCreated,
|
||||
onItemDeleted: _handleItemDeleted
|
||||
);
|
||||
},
|
||||
'/meals/new': (navigator, route) {
|
||||
return new MealFragment(
|
||||
navigator: navigator,
|
||||
onCreated: _handleItemCreated
|
||||
);
|
||||
},
|
||||
'/measurements/new': (NavigatorState navigator, Route route) {
|
||||
return new MeasurementFragment(
|
||||
navigator: navigator,
|
||||
onCreated: _handleItemCreated
|
||||
);
|
||||
},
|
||||
'/settings': (navigator, route) {
|
||||
return new SettingsFragment(
|
||||
navigator: navigator,
|
||||
userData: _userData,
|
||||
updater: settingsUpdater
|
||||
);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
void _handleItemCreated(FitnessItem item) {
|
||||
@ -158,17 +126,43 @@ class FitnessAppState extends State<FitnessApp> {
|
||||
});
|
||||
}
|
||||
|
||||
final ThemeData _theme = new ThemeData(
|
||||
brightness: ThemeBrightness.light,
|
||||
primarySwatch: Colors.indigo,
|
||||
accentColor: Colors.pinkAccent[200]
|
||||
);
|
||||
|
||||
Widget build(BuildContext) {
|
||||
return new App(
|
||||
theme: _theme,
|
||||
theme: new ThemeData(
|
||||
brightness: ThemeBrightness.light,
|
||||
primarySwatch: Colors.indigo,
|
||||
accentColor: Colors.pinkAccent[200]
|
||||
),
|
||||
title: 'Fitness',
|
||||
routes: _routes
|
||||
routes: {
|
||||
'/': (RouteArguments args) {
|
||||
return new FeedFragment(
|
||||
navigator: args.navigator,
|
||||
userData: _userData,
|
||||
onItemCreated: _handleItemCreated,
|
||||
onItemDeleted: _handleItemDeleted
|
||||
);
|
||||
},
|
||||
'/meals/new': (RouteArguments args) {
|
||||
return new MealFragment(
|
||||
navigator: args.navigator,
|
||||
onCreated: _handleItemCreated
|
||||
);
|
||||
},
|
||||
'/measurements/new': (RouteArguments args) {
|
||||
return new MeasurementFragment(
|
||||
navigator: args.navigator,
|
||||
onCreated: _handleItemCreated
|
||||
);
|
||||
},
|
||||
'/settings': (RouteArguments args) {
|
||||
return new SettingsFragment(
|
||||
navigator: args.navigator,
|
||||
userData: _userData,
|
||||
updater: settingsUpdater
|
||||
);
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -65,12 +65,13 @@ class MealFragmentState extends State<MealFragment> {
|
||||
icon: "navigation/close",
|
||||
onPressed: config.navigator.pop),
|
||||
center: new Text('New Meal'),
|
||||
right: [new InkWell(
|
||||
child: new GestureDetector(
|
||||
right: [
|
||||
// TODO(abarth): Should this be a FlatButton?
|
||||
new InkWell(
|
||||
onTap: _handleSave,
|
||||
child: new Text('SAVE')
|
||||
)
|
||||
)]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -136,12 +136,13 @@ class MeasurementFragmentState extends State<MeasurementFragment> {
|
||||
icon: "navigation/close",
|
||||
onPressed: config.navigator.pop),
|
||||
center: new Text('New Measurement'),
|
||||
right: [new InkWell(
|
||||
child: new GestureDetector(
|
||||
right: [
|
||||
// TODO(abarth): Should this be a FlatButton?
|
||||
new InkWell(
|
||||
onTap: _handleSave,
|
||||
child: new Text('SAVE')
|
||||
)
|
||||
)]
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -62,7 +62,7 @@ class TestAppState extends State<TestApp> {
|
||||
);
|
||||
}
|
||||
|
||||
Column _buildColumn(NavigatorState navigator, Route route) {
|
||||
Column _buildColumn(RouteArguments args) {
|
||||
return new Column([
|
||||
new Flexible(child: _buildSpriteWidget()),
|
||||
_buildTabBar()
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
Widget _buildMainScene(navigator, route) {
|
||||
Widget _buildMainScene(RouteArguments args) {
|
||||
return new Stack([
|
||||
new SpriteWidget(new MainScreenBackground(), SpriteBoxTransformMode.fixedWidth),
|
||||
new Column([
|
||||
@ -109,10 +109,10 @@ class GameDemoState extends State<GameDemo> {
|
||||
_sounds,
|
||||
(lastScore) {
|
||||
setState(() {_lastScore = lastScore;});
|
||||
navigator.pop();
|
||||
args.navigator.pop();
|
||||
}
|
||||
);
|
||||
navigator.pushNamed('/game');
|
||||
args.navigator.pushNamed('/game');
|
||||
},
|
||||
texture: _spriteSheetUI['btn_play_up.png'],
|
||||
textureDown: _spriteSheetUI['btn_play_down.png'],
|
||||
|
@ -3,6 +3,7 @@ dependencies:
|
||||
sky: any
|
||||
sky_tools: any
|
||||
skysprites: any
|
||||
box2d: any
|
||||
dependency_overrides:
|
||||
material_design_icons:
|
||||
path: ../../sky/packages/material_design_icons
|
||||
|
59
examples/game/test_bed.dart
Normal file
59
examples/game/test_bed.dart
Normal file
@ -0,0 +1,59 @@
|
||||
import 'dart:sky';
|
||||
|
||||
import 'package:sky/material.dart';
|
||||
import 'package:sky/rendering.dart';
|
||||
import 'package:sky/services.dart';
|
||||
import 'package:sky/widgets.dart';
|
||||
import 'package:skysprites/skysprites.dart';
|
||||
|
||||
AssetBundle _initBundle() {
|
||||
if (rootBundle != null)
|
||||
return rootBundle;
|
||||
return new NetworkAssetBundle(Uri.base);
|
||||
}
|
||||
|
||||
final AssetBundle _bundle = _initBundle();
|
||||
|
||||
ImageMap _images;
|
||||
SpriteSheet _spriteSheet;
|
||||
TestBedApp _app;
|
||||
|
||||
main() async {
|
||||
_images = new ImageMap(_bundle);
|
||||
|
||||
await _images.load([
|
||||
'assets/sprites.png'
|
||||
]);
|
||||
|
||||
String json = await _bundle.loadString('assets/sprites.json');
|
||||
_spriteSheet = new SpriteSheet(_images['assets/sprites.png'], json);
|
||||
|
||||
_app = new TestBedApp();
|
||||
runApp(_app);
|
||||
}
|
||||
|
||||
class TestBedApp extends App {
|
||||
|
||||
Widget build() {
|
||||
ThemeData theme = new ThemeData(
|
||||
brightness: ThemeBrightness.light,
|
||||
primarySwatch: Colors.purple
|
||||
);
|
||||
|
||||
return new Theme(
|
||||
data: theme,
|
||||
child: new Title(
|
||||
title: 'Test Bed',
|
||||
child: new SpriteWidget(
|
||||
new TestBed(),
|
||||
SpriteBoxTransformMode.letterbox
|
||||
)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class TestBed extends NodeWithSize {
|
||||
TestBed() : super(new Size(1024.0, 1024.0)) {
|
||||
}
|
||||
}
|
@ -36,7 +36,7 @@ main() async {
|
||||
title: 'Test drawAtlas',
|
||||
theme: _theme,
|
||||
routes: {
|
||||
'/': (NavigatorState navigator, Route route) {
|
||||
'/': (RouteArguments args) {
|
||||
return new SpriteWidget(
|
||||
new TestDrawAtlas(),
|
||||
SpriteBoxTransformMode.fixedWidth
|
||||
|
96
examples/game/test_physics.dart
Normal file
96
examples/game/test_physics.dart
Normal file
@ -0,0 +1,96 @@
|
||||
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> {
|
||||
alive = true;
|
||||
hasWon = false;
|
||||
detectedCount = 0;
|
||||
// Build the arrays.
|
||||
cells = new List<List<bool>>();
|
||||
uiState = new List<List<CellState>>();
|
||||
for (int iy = 0; iy != rows; iy++) {
|
||||
cells.add(new List<bool>());
|
||||
uiState.add(new List<CellState>());
|
||||
for (int ix = 0; ix != cols; ix++) {
|
||||
cells[iy].add(false);
|
||||
uiState[iy].add(CellState.covered);
|
||||
}
|
||||
}
|
||||
// Initialize matrices.
|
||||
cells = new List<List>.generate(rows, (int row) {
|
||||
return new List<bool>.filled(cols, false);
|
||||
});
|
||||
uiState = new List<List>.generate(rows, (int row) {
|
||||
return new List<CellState>.filled(cols, CellState.covered);
|
||||
});
|
||||
// Place the mines.
|
||||
Random random = new Random();
|
||||
int cellsRemaining = rows * cols;
|
||||
int minesRemaining = totalMineCount;
|
||||
for (int x = 0; x < cols; x += 1) {
|
||||
for (int y = 0; y < rows; y += 1) {
|
||||
if (random.nextInt(cellsRemaining) < minesRemaining) {
|
||||
cells[y][x] = true;
|
||||
minesRemaining -= 1;
|
||||
if (minesRemaining <= 0)
|
||||
return;
|
||||
}
|
||||
cellsRemaining -= 1;
|
||||
while (minesRemaining > 0) {
|
||||
int pos = random.nextInt(rows * cols);
|
||||
int row = pos ~/ rows;
|
||||
int col = pos % cols;
|
||||
if (!cells[row][col]) {
|
||||
cells[row][col] = true;
|
||||
minesRemaining--;
|
||||
}
|
||||
}
|
||||
assert(false);
|
||||
}
|
||||
|
||||
PointerEventListener _pointerDownHandlerFor(int posX, int posY) {
|
||||
@ -106,9 +98,9 @@ class MineDiggerState extends State<MineDigger> {
|
||||
Widget buildBoard() {
|
||||
bool hasCoveredCell = false;
|
||||
List<Row> flexRows = <Row>[];
|
||||
for (int iy = 0; iy != 9; iy++) {
|
||||
for (int iy = 0; iy < rows; iy++) {
|
||||
List<Widget> row = <Widget>[];
|
||||
for (int ix = 0; ix != 9; ix++) {
|
||||
for (int ix = 0; ix < cols; ix++) {
|
||||
CellState state = uiState[iy][ix];
|
||||
int count = mineCount(ix, iy);
|
||||
if (!alive) {
|
||||
|
@ -9,7 +9,7 @@ import 'package:sky/rendering.dart';
|
||||
|
||||
import 'solid_color_box.dart';
|
||||
|
||||
double timeBase;
|
||||
Duration timeBase;
|
||||
RenderTransform transformBox;
|
||||
|
||||
void main() {
|
||||
@ -34,10 +34,10 @@ void main() {
|
||||
scheduler.addPersistentFrameCallback(rotate);
|
||||
}
|
||||
|
||||
void rotate(double timeStamp) {
|
||||
void rotate(Duration timeStamp) {
|
||||
if (timeBase == null)
|
||||
timeBase = timeStamp;
|
||||
double delta = (timeStamp - timeBase) / 1000; // radians
|
||||
double delta = (timeStamp - timeBase).inMicroseconds.toDouble() / Duration.MICROSECONDS_PER_SECOND; // radians
|
||||
|
||||
transformBox.setIdentity();
|
||||
transformBox.translate(transformBox.size.width / 2.0, transformBox.size.height / 2.0);
|
||||
|
@ -82,7 +82,7 @@ class StocksAppState extends State<StocksApp> {
|
||||
if (path.length != 3)
|
||||
return null;
|
||||
if (_stocks.containsKey(path[2]))
|
||||
return (navigator, route) => new StockSymbolViewer(navigator, _stocks[path[2]]);
|
||||
return (RouteArguments args) => new StockSymbolViewer(args.navigator, _stocks[path[2]]);
|
||||
return null;
|
||||
}
|
||||
return null;
|
||||
@ -93,8 +93,8 @@ class StocksAppState extends State<StocksApp> {
|
||||
title: 'Stocks',
|
||||
theme: theme,
|
||||
routes: <String, RouteBuilder>{
|
||||
'/': (navigator, route) => new StockHome(navigator, _stocks, _symbols, _optimismSetting, modeUpdater),
|
||||
'/settings': (navigator, route) => new StockSettings(navigator, _optimismSetting, _backupSetting, settingsUpdater)
|
||||
'/': (RouteArguments args) => new StockHome(args.navigator, _stocks, _symbols, _optimismSetting, modeUpdater),
|
||||
'/settings': (RouteArguments args) => new StockSettings(args.navigator, _optimismSetting, _backupSetting, settingsUpdater)
|
||||
},
|
||||
onGenerateRoute: _getRoute
|
||||
);
|
||||
|
@ -25,7 +25,7 @@ class StockHomeState extends State<StockHome> {
|
||||
bool _isSearching = false;
|
||||
String _searchQuery;
|
||||
|
||||
AnimationStatus _snackBarStatus = AnimationStatus.dismissed;
|
||||
PerformanceStatus _snackBarStatus = PerformanceStatus.dismissed;
|
||||
bool _isSnackBarShowing = false;
|
||||
|
||||
void _handleSearchBegin() {
|
||||
@ -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;
|
||||
void _handleAutorefreshChanged(bool value) {
|
||||
setState(() {
|
||||
@ -91,16 +75,10 @@ class StockHomeState extends State<StockHome> {
|
||||
);
|
||||
}
|
||||
|
||||
Drawer buildDrawer() {
|
||||
if (_drawerStatus == AnimationStatus.dismissed)
|
||||
return null;
|
||||
assert(_drawerShowing); // TODO(mpcomplete): this is always true
|
||||
return new Drawer(
|
||||
level: 3,
|
||||
showing: _drawerShowing,
|
||||
onDismissed: _handleDrawerDismissed,
|
||||
void _showDrawer() {
|
||||
showDrawer(
|
||||
navigator: config.navigator,
|
||||
children: [
|
||||
child: new Block([
|
||||
new DrawerHeader(child: new Text('Stocks')),
|
||||
new DrawerItem(
|
||||
icon: 'action/assessment',
|
||||
@ -141,7 +119,7 @@ class StockHomeState extends State<StockHome> {
|
||||
new DrawerItem(
|
||||
icon: 'action/help',
|
||||
child: new Text('Help & Feedback'))
|
||||
]
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
@ -154,7 +132,7 @@ class StockHomeState extends State<StockHome> {
|
||||
return new ToolBar(
|
||||
left: new IconButton(
|
||||
icon: "navigation/menu",
|
||||
onPressed: _handleOpenDrawer
|
||||
onPressed: _showDrawer
|
||||
),
|
||||
center: new Text('Stocks'),
|
||||
right: [
|
||||
@ -246,20 +224,20 @@ class StockHomeState extends State<StockHome> {
|
||||
|
||||
GlobalKey snackBarKey = new GlobalKey(label: 'snackbar');
|
||||
Widget buildSnackBar() {
|
||||
if (_snackBarStatus == AnimationStatus.dismissed)
|
||||
if (_snackBarStatus == PerformanceStatus.dismissed)
|
||||
return null;
|
||||
return new SnackBar(
|
||||
showing: _isSnackBarShowing,
|
||||
content: new Text("Stock purchased!"),
|
||||
actions: [new SnackBarAction(label: "UNDO", onPressed: _handleUndo)],
|
||||
onDismissed: () { setState(() { _snackBarStatus = AnimationStatus.dismissed; }); }
|
||||
onDismissed: () { setState(() { _snackBarStatus = PerformanceStatus.dismissed; }); }
|
||||
);
|
||||
}
|
||||
|
||||
void _handleStockPurchased() {
|
||||
setState(() {
|
||||
_isSnackBarShowing = true;
|
||||
_snackBarStatus = AnimationStatus.forward;
|
||||
_snackBarStatus = PerformanceStatus.forward;
|
||||
});
|
||||
}
|
||||
|
||||
@ -276,8 +254,7 @@ class StockHomeState extends State<StockHome> {
|
||||
toolbar: _isSearching ? buildSearchBar() : buildToolBar(),
|
||||
body: buildTabNavigator(),
|
||||
snackBar: buildSnackBar(),
|
||||
floatingActionButton: buildFloatingActionButton(),
|
||||
drawer: buildDrawer()
|
||||
floatingActionButton: buildFloatingActionButton()
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -37,13 +37,13 @@ class StockRow extends StatelessComponent {
|
||||
|
||||
static const double kHeight = 79.0;
|
||||
|
||||
GestureTapListener _getTapHandler(StockRowActionCallback callback) {
|
||||
GestureTapCallback _getTapHandler(StockRowActionCallback callback) {
|
||||
if (callback == null)
|
||||
return null;
|
||||
return () => callback(stock, key, arrowKey, symbolKey, priceKey);
|
||||
}
|
||||
|
||||
GestureLongPressListener _getLongPressHandler(StockRowActionCallback callback) {
|
||||
GestureLongPressCallback _getLongPressHandler(StockRowActionCallback callback) {
|
||||
if (callback == null)
|
||||
return null;
|
||||
return () => callback(stock, key, arrowKey, symbolKey, priceKey);
|
||||
@ -55,52 +55,50 @@ class StockRow extends StatelessComponent {
|
||||
String changeInPrice = "${stock.percentChange.toStringAsFixed(2)}%";
|
||||
if (stock.percentChange > 0) changeInPrice = "+" + changeInPrice;
|
||||
|
||||
return new GestureDetector(
|
||||
return new InkWell(
|
||||
onTap: _getTapHandler(onPressed),
|
||||
onLongPress: _getLongPressHandler(onLongPressed),
|
||||
child: new InkWell(
|
||||
child: new Container(
|
||||
padding: const EdgeDims.TRBL(16.0, 16.0, 20.0, 16.0),
|
||||
decoration: new BoxDecoration(
|
||||
border: new Border(
|
||||
bottom: new BorderSide(color: Theme.of(context).dividerColor)
|
||||
)
|
||||
child: new Container(
|
||||
padding: const EdgeDims.TRBL(16.0, 16.0, 20.0, 16.0),
|
||||
decoration: new BoxDecoration(
|
||||
border: new Border(
|
||||
bottom: new BorderSide(color: Theme.of(context).dividerColor)
|
||||
)
|
||||
),
|
||||
child: new Row([
|
||||
new Container(
|
||||
key: arrowKey,
|
||||
child: new StockArrow(percentChange: stock.percentChange),
|
||||
margin: const EdgeDims.only(right: 5.0)
|
||||
),
|
||||
child: new Row([
|
||||
new Container(
|
||||
key: arrowKey,
|
||||
child: new StockArrow(percentChange: stock.percentChange),
|
||||
margin: const EdgeDims.only(right: 5.0)
|
||||
),
|
||||
new Flexible(
|
||||
child: new Row([
|
||||
new Flexible(
|
||||
flex: 2,
|
||||
child: new Text(
|
||||
stock.symbol,
|
||||
key: symbolKey
|
||||
)
|
||||
),
|
||||
new Flexible(
|
||||
child: new Text(
|
||||
lastSale,
|
||||
style: const TextStyle(textAlign: TextAlign.right),
|
||||
key: priceKey
|
||||
)
|
||||
),
|
||||
new Flexible(
|
||||
child: new Text(
|
||||
changeInPrice,
|
||||
style: Theme.of(context).text.caption.copyWith(textAlign: TextAlign.right)
|
||||
)
|
||||
),
|
||||
],
|
||||
alignItems: FlexAlignItems.baseline,
|
||||
textBaseline: DefaultTextStyle.of(context).textBaseline
|
||||
)
|
||||
new Flexible(
|
||||
child: new Row([
|
||||
new Flexible(
|
||||
flex: 2,
|
||||
child: new Text(
|
||||
stock.symbol,
|
||||
key: symbolKey
|
||||
)
|
||||
),
|
||||
new Flexible(
|
||||
child: new Text(
|
||||
lastSale,
|
||||
style: const TextStyle(textAlign: TextAlign.right),
|
||||
key: priceKey
|
||||
)
|
||||
),
|
||||
new Flexible(
|
||||
child: new Text(
|
||||
changeInPrice,
|
||||
style: Theme.of(context).text.caption.copyWith(textAlign: TextAlign.right)
|
||||
)
|
||||
),
|
||||
],
|
||||
alignItems: FlexAlignItems.baseline,
|
||||
textBaseline: DefaultTextStyle.of(context).textBaseline
|
||||
)
|
||||
])
|
||||
)
|
||||
)
|
||||
])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -2,7 +2,8 @@
|
||||
// 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 'dart:sky' as sky;
|
||||
|
||||
import 'package:sky/material.dart';
|
||||
import 'package:sky/painting.dart';
|
||||
import 'package:sky/widgets.dart';
|
||||
@ -16,15 +17,22 @@ class CardModel {
|
||||
Key get key => new ObjectKey(this);
|
||||
}
|
||||
|
||||
class CardCollectionApp extends StatefulComponent {
|
||||
CardCollectionAppState createState() => new CardCollectionAppState();
|
||||
class CardCollection extends StatefulComponent {
|
||||
CardCollection({ this.navigator });
|
||||
|
||||
final NavigatorState navigator;
|
||||
|
||||
CardCollectionState createState() => new CardCollectionState();
|
||||
}
|
||||
|
||||
class CardCollectionAppState extends State<CardCollectionApp> {
|
||||
class CardCollectionState extends State<CardCollection> {
|
||||
|
||||
static const TextStyle cardLabelStyle =
|
||||
const TextStyle(color: Colors.white, fontSize: 18.0, fontWeight: bold);
|
||||
|
||||
// TODO(hansmuller): need a local image asset
|
||||
static const _sunshineURL = "http://www.walltor.com/images/wallpaper/good-morning-sunshine-58540.jpg";
|
||||
|
||||
final TextStyle backgroundTextStyle =
|
||||
Typography.white.title.copyWith(textAlign: TextAlign.center);
|
||||
|
||||
@ -32,8 +40,7 @@ class CardCollectionAppState extends State<CardCollectionApp> {
|
||||
DismissDirection _dismissDirection = DismissDirection.horizontal;
|
||||
bool _snapToCenter = false;
|
||||
bool _fixedSizeCards = false;
|
||||
bool _drawerShowing = false;
|
||||
AnimationStatus _drawerStatus = AnimationStatus.dismissed;
|
||||
bool _sunshine = false;
|
||||
InvalidatorCallback _invalidator;
|
||||
Size _cardCollectionSize = new Size(200.0, 200.0);
|
||||
|
||||
@ -108,17 +115,23 @@ class CardCollectionAppState extends State<CardCollectionApp> {
|
||||
}
|
||||
}
|
||||
|
||||
void _handleOpenDrawer() {
|
||||
setState(() {
|
||||
_drawerShowing = true;
|
||||
_drawerStatus = AnimationStatus.forward;
|
||||
});
|
||||
}
|
||||
|
||||
void _handleDrawerDismissed() {
|
||||
setState(() {
|
||||
_drawerStatus = AnimationStatus.dismissed;
|
||||
});
|
||||
void _showDrawer() {
|
||||
showDrawer(
|
||||
navigator: config.navigator,
|
||||
child: new IconTheme(
|
||||
data: const IconThemeData(color: IconThemeColor.black),
|
||||
child: new Block([
|
||||
new DrawerHeader(child: new Text('Options')),
|
||||
buildDrawerCheckbox("Snap fling scrolls to center", _snapToCenter, _toggleSnapToCenter),
|
||||
buildDrawerCheckbox("Fixed size cards", _fixedSizeCards, _toggleFixedSizeCards),
|
||||
buildDrawerCheckbox("Let the sun shine", _sunshine, _toggleSunshine),
|
||||
new DrawerDivider(),
|
||||
buildDrawerRadioItem(DismissDirection.horizontal, 'action/code'),
|
||||
buildDrawerRadioItem(DismissDirection.left, 'navigation/arrow_back'),
|
||||
buildDrawerRadioItem(DismissDirection.right, 'navigation/arrow_forward'),
|
||||
])
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
String _dismissDirectionText(DismissDirection direction) {
|
||||
@ -139,64 +152,47 @@ class CardCollectionAppState extends State<CardCollectionApp> {
|
||||
});
|
||||
}
|
||||
|
||||
_changeDismissDirection(DismissDirection newDismissDirection) {
|
||||
void _toggleSunshine() {
|
||||
setState(() {
|
||||
_dismissDirection = newDismissDirection;
|
||||
_drawerStatus = AnimationStatus.dismissed;
|
||||
_sunshine = !_sunshine;
|
||||
});
|
||||
}
|
||||
|
||||
Widget buildDrawer() {
|
||||
if (_drawerStatus == AnimationStatus.dismissed)
|
||||
return null;
|
||||
void _changeDismissDirection(DismissDirection newDismissDirection) {
|
||||
setState(() {
|
||||
_dismissDirection = newDismissDirection;
|
||||
});
|
||||
config.navigator.pop();
|
||||
}
|
||||
|
||||
Widget buildDrawerCheckbox(String label, bool value, Function callback) {
|
||||
return new DrawerItem(
|
||||
onPressed: callback,
|
||||
child: new Row([
|
||||
new Flexible(child: new Text(label)),
|
||||
new Checkbox(value: value, onChanged: (_) { callback(); })
|
||||
])
|
||||
);
|
||||
}
|
||||
Widget buildDrawerCheckbox(String label, bool value, Function callback) {
|
||||
return new DrawerItem(
|
||||
onPressed: callback,
|
||||
child: new Row([
|
||||
new Flexible(child: new Text(label)),
|
||||
new Checkbox(value: value, onChanged: (_) { callback(); })
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildDrawerRadioItem(DismissDirection direction, String icon) {
|
||||
return new DrawerItem(
|
||||
icon: icon,
|
||||
onPressed: () { _changeDismissDirection(direction); },
|
||||
child: new Row([
|
||||
new Flexible(child: new Text(_dismissDirectionText(direction))),
|
||||
new Radio(
|
||||
value: direction,
|
||||
onChanged: _changeDismissDirection,
|
||||
groupValue: _dismissDirection
|
||||
)
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
return new IconTheme(
|
||||
data: const IconThemeData(color: IconThemeColor.black),
|
||||
child: new Drawer(
|
||||
level: 3,
|
||||
showing: _drawerShowing,
|
||||
onDismissed: _handleDrawerDismissed,
|
||||
children: [
|
||||
new DrawerHeader(child: new Text('Options')),
|
||||
buildDrawerCheckbox("Snap fling scrolls to center", _snapToCenter, _toggleSnapToCenter),
|
||||
buildDrawerCheckbox("Fixed size cards", _fixedSizeCards, _toggleFixedSizeCards),
|
||||
new DrawerDivider(),
|
||||
buildDrawerRadioItem(DismissDirection.horizontal, 'action/code'),
|
||||
buildDrawerRadioItem(DismissDirection.left, 'navigation/arrow_back'),
|
||||
buildDrawerRadioItem(DismissDirection.right, 'navigation/arrow_forward'),
|
||||
]
|
||||
)
|
||||
Widget buildDrawerRadioItem(DismissDirection direction, String icon) {
|
||||
return new DrawerItem(
|
||||
icon: icon,
|
||||
onPressed: () { _changeDismissDirection(direction); },
|
||||
child: new Row([
|
||||
new Flexible(child: new Text(_dismissDirectionText(direction))),
|
||||
new Radio(
|
||||
value: direction,
|
||||
onChanged: _changeDismissDirection,
|
||||
groupValue: _dismissDirection
|
||||
)
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
Widget buildToolBar() {
|
||||
return new ToolBar(
|
||||
left: new IconButton(icon: "navigation/menu", onPressed: _handleOpenDrawer),
|
||||
left: new IconButton(icon: "navigation/menu", onPressed: _showDrawer),
|
||||
center: new Text('Swipe Away'),
|
||||
right: [
|
||||
new Text(_dismissDirectionText(_dismissDirection))
|
||||
@ -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 cardCollection;
|
||||
@ -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(
|
||||
callback: _updateCardCollectionSize,
|
||||
child: new Container(
|
||||
@ -329,24 +341,23 @@ class CardCollectionAppState extends State<CardCollectionApp> {
|
||||
body = new Stack([body, indicator]);
|
||||
}
|
||||
|
||||
return new Theme(
|
||||
data: new ThemeData(
|
||||
brightness: ThemeBrightness.light,
|
||||
primarySwatch: Colors.blue,
|
||||
accentColor: Colors.redAccent[200]
|
||||
),
|
||||
child: new Title(
|
||||
title: 'Cards',
|
||||
child: new Scaffold(
|
||||
toolbar: buildToolBar(),
|
||||
drawer: buildDrawer(),
|
||||
body: body
|
||||
)
|
||||
)
|
||||
return new Scaffold(
|
||||
toolbar: buildToolBar(),
|
||||
body: body
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
runApp(new CardCollectionApp());
|
||||
runApp(new App(
|
||||
title: 'Cards',
|
||||
theme: new ThemeData(
|
||||
brightness: ThemeBrightness.light,
|
||||
primarySwatch: Colors.blue,
|
||||
accentColor: Colors.redAccent[200]
|
||||
),
|
||||
routes: {
|
||||
'/': (RouteArguments args) => new CardCollection(navigator: args.navigator),
|
||||
}
|
||||
));
|
||||
}
|
||||
|
@ -50,7 +50,7 @@ class ExampleDragTargetState extends State<ExampleDragTarget> {
|
||||
}
|
||||
|
||||
class Dot extends StatelessComponent {
|
||||
Dot({ Key key, this.color, this.size }): super(key: key);
|
||||
Dot({ Key key, this.color, this.size }) : super(key: key);
|
||||
final Color color;
|
||||
final double size;
|
||||
Widget build(BuildContext context) {
|
||||
@ -66,7 +66,7 @@ class Dot extends StatelessComponent {
|
||||
}
|
||||
|
||||
class ExampleDragSource extends StatelessComponent {
|
||||
ExampleDragSource({ Key key, this.navigator, this.name, this.color }): super(key: key);
|
||||
ExampleDragSource({ Key key, this.navigator, this.name, this.color }) : super(key: key);
|
||||
final NavigatorState navigator;
|
||||
final String name;
|
||||
final Color color;
|
||||
@ -133,7 +133,7 @@ void main() {
|
||||
runApp(new App(
|
||||
title: 'Drag and Drop Flutter Demo',
|
||||
routes: {
|
||||
'/': (NavigatorState navigator, Route route) => new DragAndDropApp(navigator: navigator)
|
||||
'/': (RouteArguments args) => new DragAndDropApp(navigator: args.navigator)
|
||||
}
|
||||
));
|
||||
}
|
||||
|
@ -6,46 +6,46 @@ import 'package:sky/material.dart';
|
||||
import 'package:sky/widgets.dart';
|
||||
|
||||
final Map<String, RouteBuilder> routes = <String, RouteBuilder>{
|
||||
'/': (NavigatorState navigator, Route route) => new Container(
|
||||
'/': (RouteArguments args) => new Container(
|
||||
padding: const EdgeDims.all(30.0),
|
||||
decoration: new BoxDecoration(backgroundColor: const Color(0xFFCCCCCC)),
|
||||
child: new Column([
|
||||
new Text("You are at home"),
|
||||
new RaisedButton(
|
||||
child: new Text('GO SHOPPING'),
|
||||
onPressed: () => navigator.pushNamed('/shopping')
|
||||
onPressed: () => args.navigator.pushNamed('/shopping')
|
||||
),
|
||||
new RaisedButton(
|
||||
child: new Text('START ADVENTURE'),
|
||||
onPressed: () => navigator.pushNamed('/adventure')
|
||||
onPressed: () => args.navigator.pushNamed('/adventure')
|
||||
)],
|
||||
justifyContent: FlexJustifyContent.center
|
||||
)
|
||||
),
|
||||
'/shopping': (NavigatorState navigator, Route route) => new Container(
|
||||
'/shopping': (RouteArguments args) => new Container(
|
||||
padding: const EdgeDims.all(20.0),
|
||||
decoration: new BoxDecoration(backgroundColor: const Color(0xFFBF5FFF)),
|
||||
child: new Column([
|
||||
new Text("Village Shop"),
|
||||
new RaisedButton(
|
||||
child: new Text('RETURN HOME'),
|
||||
onPressed: () => navigator.pop()
|
||||
onPressed: () => args.navigator.pop()
|
||||
),
|
||||
new RaisedButton(
|
||||
child: new Text('GO TO DUNGEON'),
|
||||
onPressed: () => navigator.pushNamed('/adventure')
|
||||
onPressed: () => args.navigator.pushNamed('/adventure')
|
||||
)],
|
||||
justifyContent: FlexJustifyContent.center
|
||||
)
|
||||
),
|
||||
'/adventure': (NavigatorState navigator, Route route) => new Container(
|
||||
'/adventure': (RouteArguments args) => new Container(
|
||||
padding: const EdgeDims.all(20.0),
|
||||
decoration: new BoxDecoration(backgroundColor: const Color(0xFFDC143C)),
|
||||
child: new Column([
|
||||
new Text("Monster's Lair"),
|
||||
new RaisedButton(
|
||||
child: new Text('RUN!!!'),
|
||||
onPressed: () => navigator.pop()
|
||||
onPressed: () => args.navigator.pop()
|
||||
)],
|
||||
justifyContent: FlexJustifyContent.center
|
||||
)
|
||||
|
@ -165,7 +165,7 @@ void main() {
|
||||
),
|
||||
title: 'Cards',
|
||||
routes: {
|
||||
'/': (navigator, route) => new OverlayGeometryApp()
|
||||
'/': (RouteArguments args) => new OverlayGeometryApp()
|
||||
}
|
||||
));
|
||||
}
|
||||
|
@ -2,7 +2,6 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:sky/animation.dart';
|
||||
import 'package:sky/material.dart';
|
||||
import 'package:sky/painting.dart';
|
||||
import 'package:sky/widgets.dart';
|
||||
@ -17,6 +16,10 @@ class CardModel {
|
||||
}
|
||||
|
||||
class PageableListApp extends StatefulComponent {
|
||||
PageableListApp({ this.navigator });
|
||||
|
||||
final NavigatorState navigator;
|
||||
|
||||
PageableListAppState createState() => new PageableListAppState();
|
||||
}
|
||||
|
||||
@ -85,31 +88,10 @@ class PageableListAppState extends State<PageableListApp> {
|
||||
});
|
||||
}
|
||||
|
||||
bool _drawerShowing = false;
|
||||
AnimationStatus _drawerStatus = AnimationStatus.dismissed;
|
||||
|
||||
void _handleOpenDrawer() {
|
||||
setState(() {
|
||||
_drawerShowing = true;
|
||||
_drawerStatus = AnimationStatus.forward;
|
||||
});
|
||||
}
|
||||
|
||||
void _handleDrawerDismissed() {
|
||||
setState(() {
|
||||
_drawerStatus = AnimationStatus.dismissed;
|
||||
});
|
||||
}
|
||||
|
||||
Drawer buildDrawer() {
|
||||
if (_drawerStatus == AnimationStatus.dismissed)
|
||||
return null;
|
||||
|
||||
return new Drawer(
|
||||
level: 3,
|
||||
showing: _drawerShowing,
|
||||
onDismissed: _handleDrawerDismissed,
|
||||
children: [
|
||||
void _showDrawer() {
|
||||
showDrawer(
|
||||
navigator: config.navigator,
|
||||
child: new Block([
|
||||
new DrawerHeader(child: new Text('Options')),
|
||||
new DrawerItem(
|
||||
icon: 'navigation/more_horiz',
|
||||
@ -130,14 +112,13 @@ class PageableListAppState extends State<PageableListApp> {
|
||||
new Checkbox(value: itemsWrap)
|
||||
])
|
||||
)
|
||||
]
|
||||
])
|
||||
);
|
||||
|
||||
}
|
||||
|
||||
Widget buildToolBar() {
|
||||
return new ToolBar(
|
||||
left: new IconButton(icon: "navigation/menu", onPressed: _handleOpenDrawer),
|
||||
left: new IconButton(icon: "navigation/menu", onPressed: _showDrawer),
|
||||
center: new Text('PageableList'),
|
||||
right: [
|
||||
new Text(scrollDirection == ScrollDirection.horizontal ? "horizontal" : "vertical")
|
||||
@ -167,25 +148,24 @@ class PageableListAppState extends State<PageableListApp> {
|
||||
Widget build(BuildContext context) {
|
||||
return new IconTheme(
|
||||
data: const IconThemeData(color: IconThemeColor.white),
|
||||
child: new Theme(
|
||||
data: new ThemeData(
|
||||
brightness: ThemeBrightness.light,
|
||||
primarySwatch: Colors.blue,
|
||||
accentColor: Colors.redAccent[200]
|
||||
),
|
||||
child: new Title(
|
||||
title: 'PageableList',
|
||||
child: new Scaffold(
|
||||
drawer: buildDrawer(),
|
||||
toolbar: buildToolBar(),
|
||||
body: buildBody(context)
|
||||
)
|
||||
)
|
||||
child: new Scaffold(
|
||||
toolbar: buildToolBar(),
|
||||
body: buildBody(context)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
runApp(new PageableListApp());
|
||||
runApp(new App(
|
||||
title: 'PageableList',
|
||||
theme: new ThemeData(
|
||||
brightness: ThemeBrightness.light,
|
||||
primarySwatch: Colors.blue,
|
||||
accentColor: Colors.redAccent[200]
|
||||
),
|
||||
routes: {
|
||||
'/': (RouteArguments args) => new PageableListApp(navigator: args.navigator),
|
||||
}
|
||||
));
|
||||
}
|
||||
|
@ -13,24 +13,23 @@ class ProgressIndicatorApp extends StatefulComponent {
|
||||
class ProgressIndicatorAppState extends State<ProgressIndicatorApp> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
valueAnimation = new ValueAnimation<double>()
|
||||
valueAnimation = new ValuePerformance<double>()
|
||||
..duration = const Duration(milliseconds: 1500)
|
||||
..variable = new AnimatedValue<double>(
|
||||
0.0,
|
||||
end: 1.0,
|
||||
curve: ease,
|
||||
reverseCurve: ease,
|
||||
interval: new Interval(0.0, 0.9)
|
||||
curve: new Interval(0.0, 0.9, curve: ease),
|
||||
reverseCurve: ease
|
||||
);
|
||||
valueAnimation.addStatusListener((AnimationStatus status) {
|
||||
if (status == AnimationStatus.dismissed || status == AnimationStatus.completed)
|
||||
valueAnimation.addStatusListener((PerformanceStatus status) {
|
||||
if (status == PerformanceStatus.dismissed || status == PerformanceStatus.completed)
|
||||
reverseValueAnimationDirection();
|
||||
});
|
||||
valueAnimation.play(valueAnimationDirection);
|
||||
}
|
||||
|
||||
ValueAnimation<double> valueAnimation;
|
||||
Direction valueAnimationDirection = Direction.forward;
|
||||
ValuePerformance<double> valueAnimation;
|
||||
AnimationDirection valueAnimationDirection = AnimationDirection.forward;
|
||||
|
||||
void handleTap() {
|
||||
setState(() {
|
||||
@ -43,9 +42,9 @@ class ProgressIndicatorAppState extends State<ProgressIndicatorApp> {
|
||||
}
|
||||
|
||||
void reverseValueAnimationDirection() {
|
||||
valueAnimationDirection = (valueAnimationDirection == Direction.forward)
|
||||
? Direction.reverse
|
||||
: Direction.forward;
|
||||
valueAnimationDirection = (valueAnimationDirection == AnimationDirection.forward)
|
||||
? AnimationDirection.reverse
|
||||
: AnimationDirection.forward;
|
||||
valueAnimation.play(valueAnimationDirection);
|
||||
}
|
||||
|
||||
|
@ -54,13 +54,13 @@ Widget builder() {
|
||||
);
|
||||
}
|
||||
|
||||
double timeBase;
|
||||
Duration timeBase;
|
||||
RenderTransform transformBox;
|
||||
|
||||
void rotate(double timeStamp) {
|
||||
void rotate(Duration timeStamp) {
|
||||
if (timeBase == null)
|
||||
timeBase = timeStamp;
|
||||
double delta = (timeStamp - timeBase) / 1000; // radians
|
||||
double delta = (timeStamp - timeBase).inMicroseconds.toDouble() / Duration.MICROSECONDS_PER_SECOND; // radians
|
||||
|
||||
transformBox.setIdentity();
|
||||
transformBox.translate(transformBox.size.width / 2.0, transformBox.size.height / 2.0);
|
||||
|
@ -7,12 +7,12 @@
|
||||
/// This library depends only on core Dart libraries and the `newton` package.
|
||||
library animation;
|
||||
|
||||
export 'src/animation/animated_simulation.dart';
|
||||
export 'src/animation/animated_value.dart';
|
||||
export 'src/animation/animation_performance.dart';
|
||||
export 'src/animation/performance.dart';
|
||||
export 'src/animation/clamped_simulation.dart';
|
||||
export 'src/animation/curves.dart';
|
||||
export 'src/animation/forces.dart';
|
||||
export 'src/animation/scheduler.dart';
|
||||
export 'src/animation/scroll_behavior.dart';
|
||||
export 'src/animation/timeline.dart';
|
||||
export 'src/animation/simulation_stepper.dart';
|
||||
export 'src/animation/ticker.dart';
|
||||
|
@ -2,12 +2,12 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import "dart:sky";
|
||||
import 'dart:sky' show Color, Rect;
|
||||
|
||||
import 'package:sky/src/animation/curves.dart';
|
||||
|
||||
/// The direction in which an animation is running
|
||||
enum Direction {
|
||||
enum AnimationDirection {
|
||||
/// The animation is running from beginning to end
|
||||
forward,
|
||||
|
||||
@ -16,72 +16,52 @@ enum Direction {
|
||||
}
|
||||
|
||||
/// An interface describing a variable that changes as an animation progresses.
|
||||
///
|
||||
/// AnimatedVariables, by convention, must be cheap to create. This allows them to be used in
|
||||
/// build functions in Widgets.
|
||||
abstract class AnimatedVariable {
|
||||
///
|
||||
/// Animatable objects, by convention, must be cheap to create. This allows them
|
||||
/// to be used in build functions in Widgets.
|
||||
abstract class Animatable {
|
||||
/// Update the variable to a given time in an animation that is running in the given direction
|
||||
void setProgress(double t, Direction direction);
|
||||
void setProgress(double t, AnimationDirection direction);
|
||||
String toString();
|
||||
}
|
||||
|
||||
/// Used by [AnimationPerformance] to convert the timing of a performance to a different timescale.
|
||||
/// Used by [Performance] to convert the timing of a performance to a different timescale.
|
||||
/// For example, by setting different values for the interval and reverseInterval, a performance
|
||||
/// can be made to take longer in one direction that the other.
|
||||
class AnimationTiming {
|
||||
AnimationTiming({
|
||||
this.interval: const Interval(0.0, 1.0),
|
||||
this.reverseInterval,
|
||||
this.curve: linear,
|
||||
this.reverseCurve
|
||||
});
|
||||
AnimationTiming({ this.curve, this.reverseCurve });
|
||||
|
||||
/// The interval during which this timing is active in the forward direction
|
||||
Interval interval;
|
||||
|
||||
/// The interval during which this timing is active in the reverse direction
|
||||
///
|
||||
/// If this field is null, the timing defaults to using [interval] in both directions.
|
||||
Interval reverseInterval;
|
||||
|
||||
/// The curve that this timing applies to the animation clock in the forward direction
|
||||
/// The curve to use in the forward direction
|
||||
Curve curve;
|
||||
|
||||
/// The curve that this timing applies to the animation clock in the reverse direction
|
||||
/// The curve to use in the reverse direction
|
||||
///
|
||||
/// If this field is null, the timing defaults to using [curve] in both directions.
|
||||
/// If this field is null, use [curve] in both directions.
|
||||
Curve reverseCurve;
|
||||
|
||||
/// Applies this timing to the given animation clock value in the given direction
|
||||
double transform(double t, Direction direction) {
|
||||
Interval interval = _getInterval(direction);
|
||||
if (interval != null)
|
||||
t = interval.transform(t);
|
||||
if (t == 1.0) // Or should we support inverse curves?
|
||||
double transform(double t, AnimationDirection direction) {
|
||||
Curve activeCurve = _getActiveCurve(direction);
|
||||
if (activeCurve == null)
|
||||
return t;
|
||||
Curve curve = _getCurve(direction);
|
||||
if (curve != null)
|
||||
t = curve.transform(t);
|
||||
return t;
|
||||
if (t == 0.0 || t == 1.0) {
|
||||
assert(activeCurve.transform(t).round() == 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) {
|
||||
if (direction == Direction.forward || reverseCurve == null)
|
||||
Curve _getActiveCurve(AnimationDirection direction) {
|
||||
if (direction == AnimationDirection.forward || reverseCurve == null)
|
||||
return curve;
|
||||
return reverseCurve;
|
||||
}
|
||||
}
|
||||
|
||||
/// An animated variable with a concrete type
|
||||
class AnimatedValue<T extends dynamic> extends AnimationTiming implements AnimatedVariable {
|
||||
AnimatedValue(this.begin, { this.end, Interval interval, Interval reverseInterval, Curve curve, Curve reverseCurve })
|
||||
: super(interval: interval, reverseInterval: reverseInterval, curve: curve, reverseCurve: reverseCurve) {
|
||||
class AnimatedValue<T extends dynamic> extends AnimationTiming implements Animatable {
|
||||
AnimatedValue(this.begin, { this.end, Curve curve, Curve reverseCurve })
|
||||
: super(curve: curve, reverseCurve: reverseCurve) {
|
||||
value = begin;
|
||||
}
|
||||
|
||||
@ -98,41 +78,28 @@ class AnimatedValue<T extends dynamic> extends AnimationTiming implements Animat
|
||||
T lerp(double t) => begin + (end - begin) * t;
|
||||
|
||||
/// Updates the value of this variable according to the given animation clock value and direction
|
||||
void setProgress(double t, Direction direction) {
|
||||
void setProgress(double t, AnimationDirection direction) {
|
||||
if (end != null) {
|
||||
t = transform(t, direction);
|
||||
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)';
|
||||
}
|
||||
|
||||
/// 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
|
||||
///
|
||||
/// This class specializes the interpolation of AnimatedValue<Color> to be
|
||||
/// appropriate for colors.
|
||||
class AnimatedColorValue extends AnimatedValue<Color> {
|
||||
AnimatedColorValue(Color begin, { Color end, Interval interval, Interval reverseInterval, Curve curve, Curve reverseCurve })
|
||||
: super(begin, end: end, interval: interval, reverseInterval: reverseInterval, curve: curve, reverseCurve: reverseCurve);
|
||||
AnimatedColorValue(Color begin, { Color end, Curve curve, Curve reverseCurve })
|
||||
: super(begin, end: end, curve: curve, reverseCurve: reverseCurve);
|
||||
|
||||
Color lerp(double t) => Color.lerp(begin, end, t);
|
||||
}
|
||||
@ -141,9 +108,9 @@ class AnimatedColorValue extends AnimatedValue<Color> {
|
||||
///
|
||||
/// This class specializes the interpolation of AnimatedValue<Rect> to be
|
||||
/// appropriate for rectangles.
|
||||
class AnimatedRect extends AnimatedValue<Rect> {
|
||||
AnimatedRect(Rect begin, { Rect end, Interval interval, Interval reverseInterval, Curve curve, Curve reverseCurve })
|
||||
: super(begin, end: end, interval: interval, reverseInterval: reverseInterval, curve: curve, reverseCurve: reverseCurve);
|
||||
class AnimatedRectValue extends AnimatedValue<Rect> {
|
||||
AnimatedRectValue(Rect begin, { Rect end, Curve curve, Curve reverseCurve })
|
||||
: super(begin, end: end, curve: curve, reverseCurve: reverseCurve);
|
||||
|
||||
Rect lerp(double t) => Rect.lerp(begin, end, t);
|
||||
}
|
||||
|
@ -27,9 +27,9 @@ class Linear implements Curve {
|
||||
double transform(double t) => t;
|
||||
}
|
||||
|
||||
/// A curve that is 0.0 until start, then linear from 0.0 to 1.0 at end, then 1.0
|
||||
/// A curve that is 0.0 until start, then curved from 0.0 to 1.0 at end, then 1.0
|
||||
class Interval implements Curve {
|
||||
const Interval(this.start, this.end);
|
||||
const Interval(this.start, this.end, { this.curve: linear });
|
||||
|
||||
/// The smallest value for which this interval is 0.0
|
||||
final double start;
|
||||
@ -37,12 +37,18 @@ class Interval implements Curve {
|
||||
/// The smallest value for which this interval is 1.0
|
||||
final double end;
|
||||
|
||||
/// The curve to apply between [start] and [end]
|
||||
final Curve curve;
|
||||
|
||||
double transform(double t) {
|
||||
assert(start >= 0.0);
|
||||
assert(start <= 1.0);
|
||||
assert(end >= 0.0);
|
||||
assert(end <= 1.0);
|
||||
return ((t - start) / (end - start)).clamp(0.0, 1.0);
|
||||
t = ((t - start) / (end - start)).clamp(0.0, 1.0);
|
||||
if (t == 0.0 || t == 1.0)
|
||||
return t;
|
||||
return curve.transform(t);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -6,10 +6,10 @@ import 'dart:async';
|
||||
|
||||
import 'package:sky/src/animation/animated_value.dart';
|
||||
import 'package:sky/src/animation/forces.dart';
|
||||
import 'package:sky/src/animation/timeline.dart';
|
||||
import 'package:sky/src/animation/simulation_stepper.dart';
|
||||
|
||||
/// The status of an animation
|
||||
enum AnimationStatus {
|
||||
enum PerformanceStatus {
|
||||
/// The animation is stopped at the beginning
|
||||
dismissed,
|
||||
|
||||
@ -23,27 +23,27 @@ enum AnimationStatus {
|
||||
completed,
|
||||
}
|
||||
|
||||
typedef void AnimationPerformanceListener();
|
||||
typedef void AnimationPerformanceStatusListener(AnimationStatus status);
|
||||
typedef void PerformanceListener();
|
||||
typedef void PerformanceStatusListener(PerformanceStatus status);
|
||||
|
||||
/// An interface that is implemented by [AnimationPerformance] that exposes a
|
||||
/// An interface that is implemented by [Performance] that exposes a
|
||||
/// read-only view of the underlying performance. This is used by classes that
|
||||
/// want to watch a performance but should not be able to change the
|
||||
/// performance's state.
|
||||
abstract class WatchableAnimationPerformance {
|
||||
abstract class PerformanceView {
|
||||
/// Update the given variable according to the current progress of the performance
|
||||
void updateVariable(AnimatedVariable variable);
|
||||
void updateVariable(Animatable variable);
|
||||
/// Calls the listener every time the progress of the performance changes
|
||||
void addListener(AnimationPerformanceListener listener);
|
||||
void addListener(PerformanceListener listener);
|
||||
/// Stop calling the listener every time the progress of the performance changes
|
||||
void removeListener(AnimationPerformanceListener listener);
|
||||
void removeListener(PerformanceListener listener);
|
||||
/// Calls listener every time the status of the performance changes
|
||||
void addStatusListener(AnimationPerformanceStatusListener listener);
|
||||
void addStatusListener(PerformanceStatusListener listener);
|
||||
/// Stops calling the listener every time the status of the performance changes
|
||||
void removeStatusListener(AnimationPerformanceStatusListener listener);
|
||||
void removeStatusListener(PerformanceStatusListener listener);
|
||||
}
|
||||
|
||||
/// A timeline that can be reversed and used to update [AnimatedVariable]s.
|
||||
/// A timeline that can be reversed and used to update [Animatable]s.
|
||||
///
|
||||
/// For example, a performance may handle an animation of a menu opening by
|
||||
/// sliding and fading in (changing Y value and opacity) over .5 seconds. The
|
||||
@ -51,41 +51,38 @@ abstract class WatchableAnimationPerformance {
|
||||
/// may also take direct control of the timeline by manipulating [progress], or
|
||||
/// [fling] the timeline causing a physics-based simulation to take over the
|
||||
/// progression.
|
||||
class AnimationPerformance implements WatchableAnimationPerformance {
|
||||
AnimationPerformance({ this.duration, double progress }) {
|
||||
_timeline = new Timeline(_tick);
|
||||
class Performance implements PerformanceView {
|
||||
Performance({ this.duration, double progress }) {
|
||||
_timeline = new SimulationStepper(_tick);
|
||||
if (progress != null)
|
||||
_timeline.value = progress.clamp(0.0, 1.0);
|
||||
}
|
||||
|
||||
/// Returns a [WatchableAnimationPerformance] for this performance,
|
||||
/// Returns a [PerformanceView] for this performance,
|
||||
/// so that a pointer to this object can be passed around without
|
||||
/// allowing users of that pointer to mutate the AnimationPerformance state.
|
||||
WatchableAnimationPerformance get view => this;
|
||||
PerformanceView get view => this;
|
||||
|
||||
/// The length of time this performance should last
|
||||
Duration duration;
|
||||
|
||||
Timeline _timeline;
|
||||
Direction _direction;
|
||||
SimulationStepper _timeline;
|
||||
AnimationDirection _direction;
|
||||
|
||||
/// The direction used to select the current curve
|
||||
///
|
||||
/// Curve direction is only reset when we hit the beginning or the end of the
|
||||
/// timeline to avoid discontinuities in the value of any variables this
|
||||
/// performance is used to animate.
|
||||
Direction _curveDirection;
|
||||
AnimationDirection _curveDirection;
|
||||
|
||||
/// If non-null, animate with this timing instead of a linear timing
|
||||
AnimationTiming timing;
|
||||
|
||||
/// If non-null, animate with this force instead of a zero-to-one timeline.
|
||||
Force attachedForce;
|
||||
|
||||
/// The progress of this performance along the timeline
|
||||
///
|
||||
/// Note: Setting this value stops the current animation.
|
||||
double get progress => _timeline.value;
|
||||
double get progress => _timeline.value.clamp(0.0, 1.0);
|
||||
void set progress(double t) {
|
||||
// TODO(mpcomplete): should this affect |direction|?
|
||||
stop();
|
||||
@ -98,51 +95,45 @@ class AnimationPerformance implements WatchableAnimationPerformance {
|
||||
}
|
||||
|
||||
/// Whether this animation is stopped at the beginning
|
||||
bool get isDismissed => status == AnimationStatus.dismissed;
|
||||
bool get isDismissed => status == PerformanceStatus.dismissed;
|
||||
|
||||
/// Whether this animation is stopped at the end
|
||||
bool get isCompleted => status == AnimationStatus.completed;
|
||||
bool get isCompleted => status == PerformanceStatus.completed;
|
||||
|
||||
/// Whether this animation is currently animating in either the forward or reverse direction
|
||||
bool get isAnimating => _timeline.isAnimating;
|
||||
|
||||
/// The current status of this animation
|
||||
AnimationStatus get status {
|
||||
PerformanceStatus get status {
|
||||
if (!isAnimating && progress == 1.0)
|
||||
return AnimationStatus.completed;
|
||||
return PerformanceStatus.completed;
|
||||
if (!isAnimating && progress == 0.0)
|
||||
return AnimationStatus.dismissed;
|
||||
return _direction == Direction.forward ?
|
||||
AnimationStatus.forward :
|
||||
AnimationStatus.reverse;
|
||||
return PerformanceStatus.dismissed;
|
||||
return _direction == AnimationDirection.forward ?
|
||||
PerformanceStatus.forward :
|
||||
PerformanceStatus.reverse;
|
||||
}
|
||||
|
||||
/// Update the given varaible according to the current progress of this performance
|
||||
void updateVariable(AnimatedVariable variable) {
|
||||
void updateVariable(Animatable variable) {
|
||||
variable.setProgress(_curvedProgress, _curveDirection);
|
||||
}
|
||||
|
||||
/// Start running this animation forwards (towards the end)
|
||||
Future forward() => play(Direction.forward);
|
||||
Future forward() => play(AnimationDirection.forward);
|
||||
|
||||
/// Start running this animation in reverse (towards the beginning)
|
||||
Future reverse() => play(Direction.reverse);
|
||||
Future reverse() => play(AnimationDirection.reverse);
|
||||
|
||||
/// Start running this animation in the given direction
|
||||
Future play([Direction direction = Direction.forward]) {
|
||||
Future play([AnimationDirection direction = AnimationDirection.forward]) {
|
||||
_direction = direction;
|
||||
return resume();
|
||||
}
|
||||
|
||||
/// Start running this animation in the most recently direction
|
||||
/// Start running this animation in the most recent direction
|
||||
Future resume() {
|
||||
if (attachedForce != null) {
|
||||
return fling(
|
||||
velocity: _direction == Direction.forward ? 1.0 : -1.0,
|
||||
force: attachedForce
|
||||
);
|
||||
}
|
||||
return _animateTo(_direction == Direction.forward ? 1.0 : 0.0);
|
||||
return _animateTo(_direction == AnimationDirection.forward ? 1.0 : 0.0);
|
||||
}
|
||||
|
||||
/// Stop running this animation
|
||||
@ -158,46 +149,46 @@ class AnimationPerformance implements WatchableAnimationPerformance {
|
||||
Future fling({double velocity: 1.0, Force force}) {
|
||||
if (force == null)
|
||||
force = kDefaultSpringForce;
|
||||
_direction = velocity < 0.0 ? Direction.reverse : Direction.forward;
|
||||
return _timeline.fling(force.release(progress, velocity));
|
||||
_direction = velocity < 0.0 ? AnimationDirection.reverse : AnimationDirection.forward;
|
||||
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
|
||||
void addListener(AnimationPerformanceListener listener) {
|
||||
void addListener(PerformanceListener listener) {
|
||||
_listeners.add(listener);
|
||||
}
|
||||
|
||||
/// Stop calling the listener every time the progress of this performance changes
|
||||
void removeListener(AnimationPerformanceListener listener) {
|
||||
void removeListener(PerformanceListener listener) {
|
||||
_listeners.remove(listener);
|
||||
}
|
||||
|
||||
void _notifyListeners() {
|
||||
List<AnimationPerformanceListener> localListeners = new List<AnimationPerformanceListener>.from(_listeners);
|
||||
for (AnimationPerformanceListener listener in localListeners)
|
||||
List<PerformanceListener> localListeners = new List<PerformanceListener>.from(_listeners);
|
||||
for (PerformanceListener listener in localListeners)
|
||||
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
|
||||
void addStatusListener(AnimationPerformanceStatusListener listener) {
|
||||
void addStatusListener(PerformanceStatusListener listener) {
|
||||
_statusListeners.add(listener);
|
||||
}
|
||||
|
||||
/// Stops calling the listener every time the status of this performance changes
|
||||
void removeStatusListener(AnimationPerformanceStatusListener listener) {
|
||||
void removeStatusListener(PerformanceStatusListener listener) {
|
||||
_statusListeners.remove(listener);
|
||||
}
|
||||
|
||||
AnimationStatus _lastStatus = AnimationStatus.dismissed;
|
||||
PerformanceStatus _lastStatus = PerformanceStatus.dismissed;
|
||||
void _checkStatusChanged() {
|
||||
AnimationStatus currentStatus = status;
|
||||
PerformanceStatus currentStatus = status;
|
||||
if (currentStatus != _lastStatus) {
|
||||
List<AnimationPerformanceStatusListener> localListeners = new List<AnimationPerformanceStatusListener>.from(_statusListeners);
|
||||
for (AnimationPerformanceStatusListener listener in localListeners)
|
||||
List<PerformanceStatusListener> localListeners = new List<PerformanceStatusListener>.from(_statusListeners);
|
||||
for (PerformanceStatusListener listener in localListeners)
|
||||
listener(currentStatus);
|
||||
}
|
||||
_lastStatus = currentStatus;
|
||||
@ -205,7 +196,7 @@ class AnimationPerformance implements WatchableAnimationPerformance {
|
||||
|
||||
void _updateCurveDirection() {
|
||||
if (status != _lastStatus) {
|
||||
if (_lastStatus == AnimationStatus.dismissed || _lastStatus == AnimationStatus.completed)
|
||||
if (_lastStatus == PerformanceStatus.dismissed || _lastStatus == PerformanceStatus.completed)
|
||||
_curveDirection = _direction;
|
||||
}
|
||||
}
|
||||
@ -230,8 +221,8 @@ class AnimationPerformance implements WatchableAnimationPerformance {
|
||||
}
|
||||
|
||||
/// An animation performance with an animated variable with a concrete type
|
||||
class ValueAnimation<T> extends AnimationPerformance {
|
||||
ValueAnimation({ this.variable, Duration duration, double progress }) :
|
||||
class ValuePerformance<T> extends Performance {
|
||||
ValuePerformance({ this.variable, Duration duration, double progress }) :
|
||||
super(duration: duration, progress: progress);
|
||||
|
||||
AnimatedValue<T> variable;
|
@ -14,7 +14,7 @@ double timeDilation = 1.0;
|
||||
/// scheduler's epoch. Use timeStamp to determine how far to advance animation
|
||||
/// timelines so that all the animations in the system are synchronized to a
|
||||
/// common time base.
|
||||
typedef void SchedulerCallback(double timeStamp);
|
||||
typedef void SchedulerCallback(Duration timeStamp);
|
||||
|
||||
/// Schedules callbacks to run in concert with the engine's animation system
|
||||
class Scheduler {
|
||||
@ -35,8 +35,10 @@ class Scheduler {
|
||||
/// This function first calls all the callbacks registered by
|
||||
/// [requestAnimationFrame] and then calls all the callbacks registered by
|
||||
/// [addPersistentFrameCallback], which typically drive the rendering pipeline.
|
||||
void beginFrame(double timeStamp) {
|
||||
timeStamp /= timeDilation;
|
||||
void beginFrame(double timeStampMS) {
|
||||
timeStampMS /= timeDilation;
|
||||
|
||||
Duration timeStamp = new Duration(microseconds: (timeStampMS * Duration.MICROSECONDS_PER_MILLISECOND).round());
|
||||
|
||||
_haveScheduledVisualUpdate = false;
|
||||
|
||||
|
105
packages/flutter/lib/src/animation/simulation_stepper.dart
Normal file
105
packages/flutter/lib/src/animation/simulation_stepper.dart
Normal file
@ -0,0 +1,105 @@
|
||||
// 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 'dart:async';
|
||||
|
||||
import 'package:newton/newton.dart';
|
||||
import 'package:sky/src/animation/animated_value.dart';
|
||||
import 'package:sky/src/animation/curves.dart';
|
||||
import 'package:sky/src/animation/ticker.dart';
|
||||
|
||||
/// A simulation that varies from [begin] to [end] over [duration] using [curve]
|
||||
///
|
||||
/// This class is an adaptor between the Simulation interface and the
|
||||
/// AnimatedValue interface.
|
||||
class _TweenSimulation extends Simulation {
|
||||
_TweenSimulation(double begin, double end, Duration duration, Curve curve)
|
||||
: _durationInSeconds = duration.inMicroseconds.toDouble() / Duration.MICROSECONDS_PER_SECOND,
|
||||
_tween = new AnimatedValue<double>(begin, end: end, curve: curve) {
|
||||
assert(_durationInSeconds > 0.0);
|
||||
assert(begin != null);
|
||||
assert(end != null);
|
||||
}
|
||||
|
||||
final double _durationInSeconds;
|
||||
final AnimatedValue<double> _tween;
|
||||
|
||||
double x(double timeInSeconds) {
|
||||
assert(timeInSeconds >= 0.0);
|
||||
final double t = (timeInSeconds / _durationInSeconds).clamp(0.0, 1.0);
|
||||
_tween.setProgress(t, AnimationDirection.forward);
|
||||
return _tween.value;
|
||||
}
|
||||
|
||||
double dx(double timeInSeconds) => 1.0;
|
||||
|
||||
bool isDone(double timeInSeconds) => timeInSeconds > _durationInSeconds;
|
||||
}
|
||||
|
||||
typedef TimelineCallback(double value);
|
||||
|
||||
/// Steps a simulation one per frame
|
||||
class SimulationStepper {
|
||||
SimulationStepper(TimelineCallback onTick) : _onTick = onTick {
|
||||
_ticker = new Ticker(_tick);
|
||||
}
|
||||
|
||||
final TimelineCallback _onTick;
|
||||
Ticker _ticker;
|
||||
Simulation _simulation;
|
||||
|
||||
/// The current value of the timeline
|
||||
double get value => _value;
|
||||
double _value = 0.0;
|
||||
void set value(double newValue) {
|
||||
assert(newValue != null);
|
||||
assert(!isAnimating);
|
||||
_value = newValue;
|
||||
_onTick(_value);
|
||||
}
|
||||
|
||||
/// Whether the timeline is currently animating
|
||||
bool get isAnimating => _ticker.isTicking;
|
||||
|
||||
/// Animate value of the timeline to the given target over the given duration
|
||||
///
|
||||
/// Returns a future that resolves when the timeline stops animating,
|
||||
/// typically when the timeline arives at the target value.
|
||||
Future animateTo(double target, { Duration duration, Curve curve: linear }) {
|
||||
assert(duration > Duration.ZERO);
|
||||
assert(!isAnimating);
|
||||
return _start(new _TweenSimulation(value, target, duration, curve));
|
||||
}
|
||||
|
||||
/// Gives the given simulation control over the timeline
|
||||
Future animateWith(Simulation simulation) {
|
||||
stop();
|
||||
return _start(simulation);
|
||||
}
|
||||
|
||||
/// Start ticking the given simulation once per frame
|
||||
///
|
||||
/// Returns a future that resolves when the simulation stops ticking.
|
||||
Future _start(Simulation simulation) {
|
||||
assert(simulation != null);
|
||||
assert(!isAnimating);
|
||||
_simulation = simulation;
|
||||
_value = simulation.x(0.0);
|
||||
return _ticker.start();
|
||||
}
|
||||
|
||||
/// Stop animating the timeline
|
||||
void stop() {
|
||||
_simulation = null;
|
||||
_ticker.stop();
|
||||
}
|
||||
|
||||
void _tick(Duration elapsed) {
|
||||
double elapsedInSeconds = elapsed.inMicroseconds.toDouble() / Duration.MICROSECONDS_PER_SECOND;
|
||||
_value = _simulation.x(elapsedInSeconds);
|
||||
if (_simulation.isDone(elapsedInSeconds))
|
||||
stop();
|
||||
_onTick(_value);
|
||||
}
|
||||
}
|
@ -4,29 +4,27 @@
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:newton/newton.dart';
|
||||
import 'package:sky/src/animation/scheduler.dart';
|
||||
|
||||
const double _kSecondsPerMillisecond = 1000.0;
|
||||
|
||||
// TODO(abarth): Change from double to Duration.
|
||||
typedef _TickerCallback(double timeStamp);
|
||||
typedef TickerCallback(Duration elapsed);
|
||||
|
||||
/// Calls its callback once per animation frame
|
||||
class Ticker {
|
||||
/// Constructs a ticker that will call onTick once per frame while running
|
||||
Ticker(_TickerCallback onTick) : _onTick = onTick;
|
||||
Ticker(TickerCallback onTick) : _onTick = onTick;
|
||||
|
||||
final _TickerCallback _onTick;
|
||||
final TickerCallback _onTick;
|
||||
|
||||
Completer _completer;
|
||||
int _animationId;
|
||||
Duration _startTime;
|
||||
|
||||
/// Start calling onTick once per animation frame
|
||||
///
|
||||
/// The returned future resolves once the ticker stops ticking.
|
||||
Future start() {
|
||||
assert(!isTicking);
|
||||
assert(_startTime == null);
|
||||
_completer = new Completer();
|
||||
_scheduleTick();
|
||||
return _completer.future;
|
||||
@ -39,12 +37,14 @@ class Ticker {
|
||||
if (!isTicking)
|
||||
return;
|
||||
|
||||
_startTime = null;
|
||||
|
||||
if (_animationId != null) {
|
||||
scheduler.cancelAnimationFrame(_animationId);
|
||||
_animationId = null;
|
||||
}
|
||||
|
||||
// We take the _completer into a local variable so that !isTicking
|
||||
// We take the _completer into a local variable so that isTicking is false
|
||||
// when we actually complete the future (isTicking uses _completer
|
||||
// to determine its state).
|
||||
Completer localCompleter = _completer;
|
||||
@ -56,12 +56,15 @@ class Ticker {
|
||||
/// Whether this ticker has scheduled a call to onTick
|
||||
bool get isTicking => _completer != null;
|
||||
|
||||
void _tick(double timeStamp) {
|
||||
void _tick(Duration timeStamp) {
|
||||
assert(isTicking);
|
||||
assert(_animationId != null);
|
||||
_animationId = null;
|
||||
|
||||
_onTick(timeStamp);
|
||||
if (_startTime == null)
|
||||
_startTime = timeStamp;
|
||||
|
||||
_onTick(timeStamp - _startTime);
|
||||
|
||||
// The onTick callback may have scheduled another tick already.
|
||||
if (isTicking && _animationId == null)
|
||||
@ -74,63 +77,3 @@ class Ticker {
|
||||
_animationId = scheduler.requestAnimationFrame(_tick);
|
||||
}
|
||||
}
|
||||
|
||||
/// Ticks a simulation once per frame
|
||||
class AnimatedSimulation {
|
||||
|
||||
AnimatedSimulation(Function onTick) : _onTick = onTick {
|
||||
_ticker = new Ticker(_tick);
|
||||
}
|
||||
|
||||
final Function _onTick;
|
||||
Ticker _ticker;
|
||||
|
||||
Simulation _simulation;
|
||||
double _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);
|
||||
}
|
||||
|
||||
}
|
@ -1,87 +0,0 @@
|
||||
// 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 'dart:async';
|
||||
|
||||
import 'package:newton/newton.dart';
|
||||
|
||||
import 'package:sky/src/animation/animated_simulation.dart';
|
||||
|
||||
/// A simulation that linearly varies from [begin] to [end] over [duration]
|
||||
class TweenSimulation extends Simulation {
|
||||
final double _durationInSeconds;
|
||||
|
||||
/// The initial value of the simulation
|
||||
final double begin;
|
||||
|
||||
/// The terminal value of the simulation
|
||||
final double end;
|
||||
|
||||
TweenSimulation(Duration duration, this.begin, this.end) :
|
||||
_durationInSeconds = duration.inMicroseconds / Duration.MICROSECONDS_PER_SECOND {
|
||||
assert(_durationInSeconds > 0.0);
|
||||
assert(begin != null && begin >= 0.0 && begin <= 1.0);
|
||||
assert(end != null && end >= 0.0 && end <= 1.0);
|
||||
}
|
||||
|
||||
double x(double timeInSeconds) {
|
||||
assert(timeInSeconds >= 0.0);
|
||||
final double t = timeInSeconds / _durationInSeconds;
|
||||
return t >= 1.0 ? end : begin + (end - begin) * t;
|
||||
}
|
||||
|
||||
double dx(double timeInSeconds) => 1.0;
|
||||
|
||||
bool isDone(double timeInSeconds) => timeInSeconds > _durationInSeconds;
|
||||
}
|
||||
|
||||
/// A timeline for an animation
|
||||
class Timeline {
|
||||
Timeline(Function onTick) {
|
||||
_animation = new AnimatedSimulation(onTick);
|
||||
}
|
||||
|
||||
AnimatedSimulation _animation;
|
||||
|
||||
/// The current value of the timeline
|
||||
double get value => _animation.value.clamp(0.0, 1.0);
|
||||
void set value(double newValue) {
|
||||
assert(newValue != null && newValue >= 0.0 && newValue <= 1.0);
|
||||
assert(!isAnimating);
|
||||
_animation.value = newValue;
|
||||
}
|
||||
|
||||
/// Whether the timeline is currently animating
|
||||
bool get isAnimating => _animation.isAnimating;
|
||||
|
||||
Future _start({
|
||||
Duration duration,
|
||||
double begin: 0.0,
|
||||
double end: 1.0
|
||||
}) {
|
||||
assert(!_animation.isAnimating);
|
||||
assert(duration > Duration.ZERO);
|
||||
return _animation.start(new TweenSimulation(duration, begin, end));
|
||||
}
|
||||
|
||||
/// Animate value of the timeline to the given target over the given duration
|
||||
///
|
||||
/// Returns a future that resolves when the timeline stops animating,
|
||||
/// typically when the timeline arives at the target value.
|
||||
Future animateTo(double target, { Duration duration }) {
|
||||
assert(duration > Duration.ZERO);
|
||||
return _start(duration: duration, begin: value, end: target);
|
||||
}
|
||||
|
||||
/// Stop animating the timeline
|
||||
void stop() {
|
||||
_animation.stop();
|
||||
}
|
||||
|
||||
// Gives the given simulation control over the timeline
|
||||
Future fling(Simulation simulation) {
|
||||
stop();
|
||||
return _animation.start(simulation);
|
||||
}
|
||||
}
|
@ -9,13 +9,13 @@ import 'package:sky/src/gestures/constants.dart';
|
||||
import 'package:sky/src/gestures/pointer_router.dart';
|
||||
import 'package:sky/src/gestures/recognizer.dart';
|
||||
|
||||
typedef void GestureLongPressListener();
|
||||
typedef void GestureLongPressCallback();
|
||||
|
||||
class LongPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
||||
LongPressGestureRecognizer({ PointerRouter router, this.onLongPress })
|
||||
: super(router: router, deadline: kTapTimeout + kLongPressTimeout);
|
||||
|
||||
GestureLongPressListener onLongPress;
|
||||
GestureLongPressCallback onLongPress;
|
||||
|
||||
void didExceedDeadline() {
|
||||
resolve(GestureDisposition.accepted);
|
||||
|
@ -12,7 +12,9 @@ import 'package:sky/src/gestures/pointer_router.dart';
|
||||
export 'package:sky/src/gestures/pointer_router.dart' show PointerRouter;
|
||||
|
||||
abstract class GestureRecognizer extends GestureArenaMember {
|
||||
GestureRecognizer({ PointerRouter router }) : _router = router;
|
||||
GestureRecognizer({ PointerRouter router }) : _router = router {
|
||||
assert(_router != null);
|
||||
}
|
||||
|
||||
PointerRouter _router;
|
||||
|
||||
|
@ -8,13 +8,13 @@ import 'package:sky/src/gestures/arena.dart';
|
||||
import 'package:sky/src/gestures/constants.dart';
|
||||
import 'package:sky/src/gestures/recognizer.dart';
|
||||
|
||||
typedef void GestureShowPressListener();
|
||||
typedef void GestureShowPressCallback();
|
||||
|
||||
class ShowPressGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
||||
ShowPressGestureRecognizer({ PointerRouter router, this.onShowPress })
|
||||
: super(router: router, deadline: kTapTimeout);
|
||||
|
||||
GestureShowPressListener onShowPress;
|
||||
GestureShowPressCallback onShowPress;
|
||||
|
||||
void didExceedDeadline() {
|
||||
// Show press isn't an exclusive gesture. We can recognize a show press
|
||||
|
@ -8,13 +8,15 @@ import 'package:sky/src/gestures/arena.dart';
|
||||
import 'package:sky/src/gestures/constants.dart';
|
||||
import 'package:sky/src/gestures/recognizer.dart';
|
||||
|
||||
typedef void GestureTapListener();
|
||||
typedef void GestureTapCallback();
|
||||
|
||||
class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
||||
TapGestureRecognizer({ PointerRouter router, this.onTap })
|
||||
: super(router: router, deadline: kTapTimeout);
|
||||
|
||||
GestureTapListener onTap;
|
||||
GestureTapCallback onTap;
|
||||
GestureTapCallback onTapDown;
|
||||
GestureTapCallback onTapCancel;
|
||||
|
||||
void didExceedDeadline() {
|
||||
stopTrackingPointer(primaryPointer);
|
||||
@ -22,9 +24,22 @@ class TapGestureRecognizer extends PrimaryPointerGestureRecognizer {
|
||||
}
|
||||
|
||||
void handlePrimaryPointer(sky.PointerEvent event) {
|
||||
if (event.type == 'pointerup') {
|
||||
if (event.type == 'pointerdown') {
|
||||
if (onTapDown != null)
|
||||
onTapDown();
|
||||
} else if (event.type == 'pointerup') {
|
||||
resolve(GestureDisposition.accepted);
|
||||
onTap();
|
||||
if (onTap != null)
|
||||
onTap();
|
||||
}
|
||||
}
|
||||
|
||||
void rejectGesture(int pointer) {
|
||||
super.rejectGesture(pointer);
|
||||
if (pointer == primaryPointer) {
|
||||
assert(state == GestureRecognizerState.defunct);
|
||||
if (onTapCancel != null)
|
||||
onTapCancel();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -30,13 +30,13 @@ class RadialReaction {
|
||||
_outerOpacity = new AnimatedValue<double>(0.0, end: _kMaxOpacity, curve: easeOut);
|
||||
_innerCenter = new AnimatedValue<Point>(startPosition, end: center, curve: easeOut);
|
||||
_innerRadius = new AnimatedValue<double>(0.0, end: radius, curve: easeOut);
|
||||
_showPerformance = new AnimationPerformance(duration: _kShowDuration)
|
||||
_showPerformance = new Performance(duration: _kShowDuration)
|
||||
..addListener(() {
|
||||
_showPerformance.updateVariable(_outerOpacity);
|
||||
_showPerformance.updateVariable(_innerCenter);
|
||||
_showPerformance.updateVariable(_innerRadius);
|
||||
});
|
||||
_fade = new ValueAnimation<double>(
|
||||
_fade = new ValuePerformance<double>(
|
||||
variable: new AnimatedValue(1.0, end: 0.0, curve: easeIn),
|
||||
duration: _kHideDuration
|
||||
);
|
||||
@ -48,14 +48,14 @@ class RadialReaction {
|
||||
/// The radius of the circle in which the reaction occurs
|
||||
final double radius;
|
||||
|
||||
AnimationPerformance _showPerformance;
|
||||
Performance _showPerformance;
|
||||
AnimatedValue<double> _outerOpacity;
|
||||
AnimatedValue<Point> _innerCenter;
|
||||
AnimatedValue<double> _innerRadius;
|
||||
|
||||
Future _showComplete;
|
||||
|
||||
ValueAnimation<double> _fade;
|
||||
ValuePerformance<double> _fade;
|
||||
|
||||
/// Show the reaction
|
||||
///
|
||||
|
@ -78,7 +78,7 @@ class FlutterBinding extends HitTestTarget {
|
||||
}
|
||||
|
||||
/// Pump the rendering pipeline to generate a frame for the given time stamp
|
||||
void beginFrame(double timeStamp) {
|
||||
void beginFrame(Duration timeStamp) {
|
||||
RenderObject.flushLayout();
|
||||
_renderView.updateCompositingBits();
|
||||
RenderObject.flushPaint();
|
||||
|
@ -178,6 +178,60 @@ class BoxConstraints extends Constraints {
|
||||
(minHeight <= size.height) && (size.height <= math.max(minHeight, maxHeight));
|
||||
}
|
||||
|
||||
BoxConstraints operator*(double other) {
|
||||
return new BoxConstraints(
|
||||
minWidth: minWidth * other,
|
||||
maxWidth: maxWidth * other,
|
||||
minHeight: minHeight * other,
|
||||
maxHeight: maxHeight * other
|
||||
);
|
||||
}
|
||||
|
||||
BoxConstraints operator/(double other) {
|
||||
return new BoxConstraints(
|
||||
minWidth: minWidth / other,
|
||||
maxWidth: maxWidth / other,
|
||||
minHeight: minHeight / other,
|
||||
maxHeight: maxHeight / other
|
||||
);
|
||||
}
|
||||
|
||||
BoxConstraints operator~/(double other) {
|
||||
return new BoxConstraints(
|
||||
minWidth: (minWidth ~/ other).toDouble(),
|
||||
maxWidth: (maxWidth ~/ other).toDouble(),
|
||||
minHeight: (minHeight ~/ other).toDouble(),
|
||||
maxHeight: (maxHeight ~/ other).toDouble()
|
||||
);
|
||||
}
|
||||
|
||||
BoxConstraints operator%(double other) {
|
||||
return new BoxConstraints(
|
||||
minWidth: minWidth % other,
|
||||
maxWidth: maxWidth % other,
|
||||
minHeight: minHeight % other,
|
||||
maxHeight: maxHeight % other
|
||||
);
|
||||
}
|
||||
|
||||
/// Linearly interpolate between two BoxConstraints
|
||||
///
|
||||
/// If either is null, this function interpolates from [BoxConstraints.zero].
|
||||
static BoxConstraints lerp(BoxConstraints a, BoxConstraints b, double t) {
|
||||
if (a == null && b == null)
|
||||
return null;
|
||||
if (a == null)
|
||||
return b * t;
|
||||
if (b == null)
|
||||
return a * (1.0 - t);
|
||||
return new BoxConstraints(
|
||||
minWidth: sky.lerpDouble(a.minWidth, b.minWidth, t),
|
||||
maxWidth: sky.lerpDouble(a.maxWidth, b.maxWidth, t),
|
||||
minHeight: sky.lerpDouble(a.minHeight, b.minHeight, t),
|
||||
maxHeight: sky.lerpDouble(a.maxHeight, b.maxHeight, t)
|
||||
);
|
||||
}
|
||||
|
||||
bool operator ==(other) {
|
||||
if (identical(this, other))
|
||||
return true;
|
||||
|
@ -87,6 +87,29 @@ class PictureLayer extends Layer {
|
||||
|
||||
}
|
||||
|
||||
/// A layer that indicates to the compositor that it should display
|
||||
/// certain statistics within it
|
||||
class StatisticsLayer extends Layer {
|
||||
StatisticsLayer({
|
||||
Offset offset: Offset.zero,
|
||||
this.paintBounds,
|
||||
this.optionsMask
|
||||
}) : super(offset: offset);
|
||||
|
||||
/// The rectangle in this layer's coodinate system that bounds the recording
|
||||
Rect paintBounds;
|
||||
|
||||
/// A mask specifying the statistics to display
|
||||
int optionsMask;
|
||||
|
||||
void addToScene(sky.SceneBuilder builder, Offset layerOffset) {
|
||||
assert(optionsMask != null);
|
||||
builder.addStatistics(optionsMask, paintBounds.shift(layerOffset));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/// A composited layer that has a list of children
|
||||
class ContainerLayer extends Layer {
|
||||
ContainerLayer({ Offset offset: Offset.zero }) : super(offset: offset);
|
||||
|
@ -16,6 +16,8 @@ import 'package:vector_math/vector_math_64.dart';
|
||||
export 'dart:sky' show Point, Offset, Size, Rect, Color, Paint, Path;
|
||||
export 'package:sky/src/rendering/hit_test.dart' show HitTestTarget, HitTestEntry, HitTestResult;
|
||||
|
||||
typedef sky.Shader ShaderCallback(Rect bounds);
|
||||
|
||||
/// Base class for data associated with a [RenderObject] by its parent
|
||||
///
|
||||
/// Some render objects wish to store data on their children, such as their
|
||||
@ -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
|
||||
// do additional work, such as clipping or transforming, at the same
|
||||
// time as painting the children.
|
||||
@ -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
|
||||
///
|
||||
/// Do not call directly. This function is visible so that it can be
|
||||
|
@ -659,6 +659,37 @@ class RenderColorFilter extends RenderProxyBox {
|
||||
}
|
||||
}
|
||||
|
||||
class RenderShaderMask extends RenderProxyBox {
|
||||
RenderShaderMask({ RenderBox child, ShaderCallback shaderCallback, sky.TransferMode transferMode })
|
||||
: _shaderCallback = shaderCallback, _transferMode = transferMode, super(child) {
|
||||
}
|
||||
|
||||
ShaderCallback get shaderCallback => _shaderCallback;
|
||||
ShaderCallback _shaderCallback;
|
||||
void set shaderCallback (ShaderCallback newShaderCallback) {
|
||||
assert(newShaderCallback != null);
|
||||
if (_shaderCallback == newShaderCallback)
|
||||
return;
|
||||
_shaderCallback = newShaderCallback;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
sky.TransferMode get transferMode => _transferMode;
|
||||
sky.TransferMode _transferMode;
|
||||
void set transferMode (sky.TransferMode newTransferMode) {
|
||||
assert(newTransferMode != null);
|
||||
if (_transferMode == newTransferMode)
|
||||
return;
|
||||
_transferMode = newTransferMode;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
if (child != null)
|
||||
context.paintChildWithShaderMask(child, offset.toPoint(), offset & size, _shaderCallback, _transferMode);
|
||||
}
|
||||
}
|
||||
|
||||
/// Clips its child using a rectangle
|
||||
///
|
||||
/// Prevents its child from painting outside its bounds.
|
||||
|
47
packages/flutter/lib/src/rendering/statistics_box.dart
Normal file
47
packages/flutter/lib/src/rendering/statistics_box.dart
Normal file
@ -0,0 +1,47 @@
|
||||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:sky/src/rendering/box.dart';
|
||||
import 'package:sky/src/rendering/object.dart';
|
||||
|
||||
class StatisticsBox extends RenderBox {
|
||||
|
||||
StatisticsBox({int optionsMask: 0}) : _optionsMask = optionsMask;
|
||||
|
||||
int _optionsMask;
|
||||
int get optionsMask => _optionsMask;
|
||||
void set optionsMask (int mask) {
|
||||
if (mask == _optionsMask) {
|
||||
return;
|
||||
}
|
||||
_optionsMask = mask;
|
||||
markNeedsPaint();
|
||||
}
|
||||
|
||||
bool get sizedByParent => true;
|
||||
|
||||
double getMinIntrinsicWidth(BoxConstraints constraints) {
|
||||
return constraints.minWidth;
|
||||
}
|
||||
|
||||
double getMaxIntrinsicWidth(BoxConstraints constraints) {
|
||||
return constraints.maxWidth;
|
||||
}
|
||||
|
||||
double getMinIntrinsicHeight(BoxConstraints constraints) {
|
||||
return constraints.minHeight;
|
||||
}
|
||||
|
||||
double getMaxIntrinsicHeight(BoxConstraints constraints) {
|
||||
return constraints.maxHeight;
|
||||
}
|
||||
|
||||
void performResize() {
|
||||
size = constraints.constrain(Size.infinite);
|
||||
}
|
||||
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
context.paintStatistics(optionsMask, offset, size);
|
||||
}
|
||||
}
|
@ -24,15 +24,15 @@ abstract class RenderToggleable extends RenderConstrainedBox {
|
||||
: _value = value,
|
||||
_onChanged = onChanged,
|
||||
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),
|
||||
duration: _kToggleDuration,
|
||||
progress: _value ? 1.0 : 0.0
|
||||
)..addListener(markNeedsPaint);
|
||||
}
|
||||
|
||||
ValueAnimation<double> get performance => _performance;
|
||||
ValueAnimation<double> _performance;
|
||||
ValuePerformance<double> get performance => _performance;
|
||||
ValuePerformance<double> _performance;
|
||||
|
||||
double get position => _performance.value;
|
||||
|
||||
@ -51,7 +51,7 @@ abstract class RenderToggleable extends RenderConstrainedBox {
|
||||
);
|
||||
}
|
||||
|
||||
void detatch() {
|
||||
void detach() {
|
||||
_tap.dispose();
|
||||
_tap = null;
|
||||
super.detach();
|
||||
@ -68,7 +68,7 @@ abstract class RenderToggleable extends RenderConstrainedBox {
|
||||
if (value == _value)
|
||||
return;
|
||||
_value = value;
|
||||
performance.play(value ? Direction.forward : Direction.reverse);
|
||||
performance.play(value ? AnimationDirection.forward : AnimationDirection.reverse);
|
||||
}
|
||||
|
||||
ValueChanged get onChanged => _onChanged;
|
||||
|
@ -9,13 +9,13 @@ abstract class AnimatedComponent extends StatefulComponent {
|
||||
const AnimatedComponent({ Key key, this.direction, this.duration }) : super(key: key);
|
||||
|
||||
final Duration duration;
|
||||
final Direction direction;
|
||||
final AnimationDirection direction;
|
||||
}
|
||||
|
||||
abstract class AnimatedState<T extends AnimatedComponent> extends State<T> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_performance = new AnimationPerformance(duration: config.duration);
|
||||
_performance = new Performance(duration: config.duration);
|
||||
performance.addStatusListener(_handleAnimationStatusChanged);
|
||||
if (buildDependsOnPerformance) {
|
||||
performance.addListener(() {
|
||||
@ -34,13 +34,13 @@ abstract class AnimatedState<T extends AnimatedComponent> extends State<T> {
|
||||
performance.play(config.direction);
|
||||
}
|
||||
|
||||
AnimationPerformance get performance => _performance;
|
||||
AnimationPerformance _performance;
|
||||
Performance get performance => _performance;
|
||||
Performance _performance;
|
||||
|
||||
void _handleAnimationStatusChanged(AnimationStatus status) {
|
||||
if (status == AnimationStatus.completed)
|
||||
void _handleAnimationStatusChanged(PerformanceStatus status) {
|
||||
if (status == PerformanceStatus.completed)
|
||||
handleCompleted();
|
||||
else if (status == AnimationStatus.dismissed)
|
||||
else if (status == PerformanceStatus.dismissed)
|
||||
handleDismissed();
|
||||
}
|
||||
|
||||
|
226
packages/flutter/lib/src/widgets/animated_container.dart
Normal file
226
packages/flutter/lib/src/widgets/animated_container.dart
Normal file
@ -0,0 +1,226 @@
|
||||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:sky/animation.dart';
|
||||
import 'package:sky/src/widgets/basic.dart';
|
||||
import 'package:sky/src/widgets/framework.dart';
|
||||
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
|
||||
class AnimatedBoxConstraintsValue extends AnimatedValue<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 {
|
||||
this.theme,
|
||||
this.routes,
|
||||
this.onGenerateRoute
|
||||
}): super(key: key);
|
||||
}) : super(key: key) {
|
||||
assert(() {
|
||||
'The "routes" argument to App() is required.';
|
||||
'This might be a sign that you have not upgraded to our new Widgets framework.';
|
||||
'For more details see: https://groups.google.com/forum/#!topic/flutter-dev/hcX3OvLws9c';
|
||||
'...or look at our examples: https://github.com/flutter/engine/tree/master/examples';
|
||||
return routes != null;
|
||||
});
|
||||
}
|
||||
|
||||
final String title;
|
||||
final ThemeData theme;
|
||||
|
@ -22,6 +22,7 @@ export 'package:sky/rendering.dart' show
|
||||
FlexAlignItems,
|
||||
FlexDirection,
|
||||
FlexJustifyContent,
|
||||
Matrix4,
|
||||
Offset,
|
||||
Paint,
|
||||
Path,
|
||||
@ -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 {
|
||||
DecoratedBox({
|
||||
Key key,
|
||||
@ -392,11 +420,11 @@ class Container extends StatelessComponent {
|
||||
this.constraints,
|
||||
this.decoration,
|
||||
this.foregroundDecoration,
|
||||
this.width,
|
||||
this.height,
|
||||
this.margin,
|
||||
this.padding,
|
||||
this.transform
|
||||
this.transform,
|
||||
this.width,
|
||||
this.height
|
||||
}) : super(key: key) {
|
||||
assert(margin == null || margin.isNonNegative);
|
||||
assert(padding == null || padding.isNonNegative);
|
||||
@ -684,6 +712,11 @@ class DefaultTextStyle extends InheritedWidget {
|
||||
}
|
||||
|
||||
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 {
|
||||
@ -710,6 +743,13 @@ class Text extends StatelessComponent {
|
||||
text = new StyledTextSpan(combinedStyle, [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 {
|
||||
@ -882,7 +922,7 @@ class Listener extends OneChildRenderObjectWidget {
|
||||
this.onPointerMove,
|
||||
this.onPointerUp,
|
||||
this.onPointerCancel
|
||||
}): super(key: key, child: child);
|
||||
}) : super(key: key, child: child);
|
||||
|
||||
final PointerEventListener onPointerDown;
|
||||
final PointerEventListener onPointerMove;
|
||||
@ -940,4 +980,4 @@ class KeyedSubtree extends StatelessComponent {
|
||||
final Widget child;
|
||||
|
||||
Widget build(BuildContext context) => child;
|
||||
}
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ class WidgetFlutterBinding extends FlutterBinding {
|
||||
);
|
||||
}
|
||||
|
||||
void beginFrame(double timeStamp) {
|
||||
void beginFrame(Duration timeStamp) {
|
||||
buildDirtyElements();
|
||||
super.beginFrame(timeStamp);
|
||||
Element.finalizeTree();
|
||||
|
@ -61,7 +61,7 @@ class _CheckboxWrapper extends LeafRenderObjectWidget {
|
||||
this.onChanged,
|
||||
this.uncheckedColor,
|
||||
this.accentColor
|
||||
}): super(key: key) {
|
||||
}) : super(key: key) {
|
||||
assert(uncheckedColor != null);
|
||||
assert(accentColor != null);
|
||||
}
|
||||
|
@ -375,22 +375,20 @@ class YearPickerState extends ScrollableWidgetListState<YearPicker> {
|
||||
for(int i = start; i < start + count; i++) {
|
||||
int year = config.firstDate.year + i;
|
||||
String label = year.toString();
|
||||
Widget item = new GestureDetector(
|
||||
Widget item = new InkWell(
|
||||
key: new Key(label),
|
||||
onTap: () {
|
||||
DateTime result = new DateTime(year, config.selectedDate.month, config.selectedDate.day);
|
||||
config.onChanged(result);
|
||||
},
|
||||
child: new InkWell(
|
||||
child: new Container(
|
||||
height: config.itemExtent,
|
||||
decoration: year == config.selectedDate.year ? new BoxDecoration(
|
||||
backgroundColor: Theme.of(context).primarySwatch[100],
|
||||
shape: Shape.circle
|
||||
) : null,
|
||||
child: new Center(
|
||||
child: new Text(label, style: style)
|
||||
)
|
||||
child: new Container(
|
||||
height: config.itemExtent,
|
||||
decoration: year == config.selectedDate.year ? new BoxDecoration(
|
||||
backgroundColor: Theme.of(context).primarySwatch[100],
|
||||
shape: Shape.circle
|
||||
) : null,
|
||||
child: new Center(
|
||||
child: new Text(label, style: style)
|
||||
)
|
||||
)
|
||||
);
|
||||
|
@ -5,6 +5,7 @@
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:sky/animation.dart';
|
||||
import 'package:sky/gestures.dart';
|
||||
import 'package:sky/material.dart';
|
||||
import 'package:sky/src/widgets/basic.dart';
|
||||
import 'package:sky/src/widgets/focus.dart';
|
||||
@ -30,7 +31,7 @@ class Dialog extends StatelessComponent {
|
||||
this.contentPadding,
|
||||
this.actions,
|
||||
this.onDismiss
|
||||
}): super(key: key);
|
||||
}) : super(key: key);
|
||||
|
||||
/// The (optional) title of the dialog is displayed in a large font at the top
|
||||
/// of the dialog.
|
||||
@ -52,7 +53,7 @@ class Dialog extends StatelessComponent {
|
||||
final List<Widget> actions;
|
||||
|
||||
/// An (optional) callback that is called when the dialog is dismissed.
|
||||
final Function onDismiss;
|
||||
final GestureTapCallback onDismiss;
|
||||
|
||||
Color _getColor(BuildContext context) {
|
||||
switch (Theme.of(context).brightness) {
|
||||
@ -140,11 +141,11 @@ class DialogRoute extends Route {
|
||||
|
||||
Duration get transitionDuration => _kTransitionDuration;
|
||||
bool get opaque => false;
|
||||
Widget build(NavigatorState navigator, WatchableAnimationPerformance nextRoutePerformance) {
|
||||
Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance) {
|
||||
return new FadeTransition(
|
||||
performance: performance,
|
||||
opacity: new AnimatedValue<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) {
|
||||
Completer completer = new Completer();
|
||||
navigator.push(new DialogRoute(
|
||||
completer: completer,
|
||||
builder: (navigator, route) {
|
||||
builder: (RouteArguments args) {
|
||||
return new Focus(
|
||||
key: new GlobalObjectKey(route),
|
||||
key: new GlobalObjectKey(completer),
|
||||
autofocus: true,
|
||||
child: builder(navigator)
|
||||
child: builder(args.navigator)
|
||||
);
|
||||
}
|
||||
));
|
||||
|
@ -12,7 +12,7 @@ import 'package:sky/src/widgets/gesture_detector.dart';
|
||||
|
||||
const Duration _kCardDismissFadeout = const Duration(milliseconds: 200);
|
||||
const Duration _kCardDismissResize = const Duration(milliseconds: 300);
|
||||
final Interval _kCardDismissResizeInterval = new Interval(0.4, 1.0);
|
||||
const Curve _kCardDismissResizeCurve = const Interval(0.4, 1.0, curve: ease);
|
||||
const double _kMinFlingVelocity = 700.0;
|
||||
const double _kMinFlingVelocityDelta = 400.0;
|
||||
const double _kFlingVelocityScale = 1.0 / 300.0;
|
||||
@ -50,15 +50,15 @@ class Dismissable extends StatefulComponent {
|
||||
class DismissableState extends State<Dismissable> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_fadePerformance = new AnimationPerformance(duration: _kCardDismissFadeout);
|
||||
_fadePerformance.addStatusListener((AnimationStatus status) {
|
||||
if (status == AnimationStatus.completed)
|
||||
_fadePerformance = new Performance(duration: _kCardDismissFadeout);
|
||||
_fadePerformance.addStatusListener((PerformanceStatus status) {
|
||||
if (status == PerformanceStatus.completed)
|
||||
_handleFadeCompleted();
|
||||
});
|
||||
}
|
||||
|
||||
AnimationPerformance _fadePerformance;
|
||||
AnimationPerformance _resizePerformance;
|
||||
Performance _fadePerformance;
|
||||
Performance _resizePerformance;
|
||||
|
||||
Size _size;
|
||||
double _dragExtent = 0.0;
|
||||
@ -97,16 +97,11 @@ class DismissableState extends State<Dismissable> {
|
||||
assert(_resizePerformance == null);
|
||||
|
||||
setState(() {
|
||||
_resizePerformance = new AnimationPerformance()
|
||||
_resizePerformance = new Performance()
|
||||
..duration = _kCardDismissResize
|
||||
..addListener(_handleResizeProgressChanged);
|
||||
_resizePerformance.play();
|
||||
});
|
||||
// Our squash curve (ease) does not return v=0.0 for t=0.0, so we
|
||||
// technically resize on the first frame. To make sure this doesn't confuse
|
||||
// any other widgets (like MixedViewport, which checks for this kind of
|
||||
// thing), we report a resize straight away.
|
||||
_maybeCallOnResized();
|
||||
}
|
||||
|
||||
void _handleResizeProgressChanged() {
|
||||
@ -226,13 +221,12 @@ class DismissableState extends State<Dismissable> {
|
||||
Widget build(BuildContext context) {
|
||||
if (_resizePerformance != null) {
|
||||
// make sure you remove this widget once it's been dismissed!
|
||||
assert(_resizePerformance.status == AnimationStatus.forward);
|
||||
assert(_resizePerformance.status == PerformanceStatus.forward);
|
||||
|
||||
AnimatedValue<double> squashAxisExtent = new AnimatedValue<double>(
|
||||
_directionIsYAxis ? _size.width : _size.height,
|
||||
end: 0.0,
|
||||
curve: ease,
|
||||
interval: _kCardDismissResizeInterval
|
||||
curve: _kCardDismissResizeCurve
|
||||
);
|
||||
|
||||
return new SquashTransition(
|
||||
|
@ -45,7 +45,7 @@ class Draggable extends StatefulComponent {
|
||||
this.feedback,
|
||||
this.feedbackOffset: Offset.zero,
|
||||
this.dragAnchor: DragAnchor.child
|
||||
}): super(key: key) {
|
||||
}) : super(key: key) {
|
||||
assert(navigator != null);
|
||||
assert(child != null);
|
||||
assert(feedback != null);
|
||||
@ -258,7 +258,7 @@ class DragRoute extends Route {
|
||||
bool get opaque => false;
|
||||
Duration get transitionDuration => const Duration();
|
||||
|
||||
Widget build(NavigatorState navigator, WatchableAnimationPerformance nextRoutePerformance) {
|
||||
Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance) {
|
||||
return new Positioned(
|
||||
left: _lastOffset.dx,
|
||||
top: _lastOffset.dy,
|
||||
|
@ -2,17 +2,16 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
|
||||
import 'package:sky/animation.dart';
|
||||
import 'package:sky/material.dart';
|
||||
import 'package:sky/src/widgets/animated_container.dart';
|
||||
import 'package:sky/src/widgets/framework.dart';
|
||||
import 'package:sky/src/widgets/basic.dart';
|
||||
import 'package:sky/src/widgets/gesture_detector.dart';
|
||||
import 'package:sky/src/widgets/navigator.dart';
|
||||
import 'package:sky/src/widgets/scrollable.dart';
|
||||
import 'package:sky/src/widgets/theme.dart';
|
||||
import 'package:sky/src/widgets/transitions.dart';
|
||||
import 'package:sky/src/widgets/focus.dart';
|
||||
|
||||
// TODO(eseidel): Draw width should vary based on device size:
|
||||
// http://www.google.com/design/spec/layout/structure.html#structure-side-nav
|
||||
@ -35,22 +34,16 @@ const Duration _kThemeChangeDuration = const Duration(milliseconds: 200);
|
||||
const Point _kOpenPosition = Point.origin;
|
||||
const Point _kClosedPosition = const Point(-_kWidth, 0.0);
|
||||
|
||||
typedef void DrawerDismissedCallback();
|
||||
|
||||
class Drawer extends StatefulComponent {
|
||||
Drawer({
|
||||
Key key,
|
||||
this.children,
|
||||
this.showing: false,
|
||||
this.level: 0,
|
||||
this.onDismissed,
|
||||
this.child,
|
||||
this.level: 3,
|
||||
this.navigator
|
||||
}) : super(key: key);
|
||||
|
||||
final List<Widget> children;
|
||||
final bool showing;
|
||||
final Widget child;
|
||||
final int level;
|
||||
final DrawerDismissedCallback onDismissed;
|
||||
final NavigatorState navigator;
|
||||
|
||||
DrawerState createState() => new DrawerState();
|
||||
@ -59,57 +52,37 @@ class Drawer extends StatefulComponent {
|
||||
class DrawerState extends State<Drawer> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_performance = new AnimationPerformance(duration: _kBaseSettleDuration);
|
||||
_performance.addStatusListener((AnimationStatus status) {
|
||||
if (status == AnimationStatus.dismissed)
|
||||
_handleDismissed();
|
||||
});
|
||||
// Use a spring force for animating the drawer. We can't use curves for
|
||||
// this because we need a linear curve in order to track the user's finger
|
||||
// while dragging.
|
||||
_performance.attachedForce = kDefaultSpringForce;
|
||||
if (config.navigator != null) {
|
||||
// TODO(ianh): This is crazy. We should convert drawer to use a pattern like openDialog().
|
||||
// https://github.com/domokit/sky_engine/pull/1186
|
||||
scheduleMicrotask(() {
|
||||
config.navigator.pushState(this, (_) => _performance.reverse());
|
||||
_performance = new Performance(duration: _kBaseSettleDuration)
|
||||
..addStatusListener((PerformanceStatus status) {
|
||||
if (status == PerformanceStatus.dismissed)
|
||||
config.navigator.pop();
|
||||
});
|
||||
}
|
||||
_performance.play(_direction);
|
||||
_open();
|
||||
}
|
||||
|
||||
AnimationPerformance _performance;
|
||||
|
||||
Direction get _direction => config.showing ? Direction.forward : Direction.reverse;
|
||||
|
||||
void didUpdateConfig(Drawer oldConfig) {
|
||||
if (config.showing != oldConfig.showing)
|
||||
_performance.play(_direction);
|
||||
}
|
||||
Performance _performance;
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
var mask = new GestureDetector(
|
||||
Widget mask = new GestureDetector(
|
||||
onTap: _close,
|
||||
child: new ColorTransition(
|
||||
performance: _performance.view,
|
||||
color: new AnimatedColorValue(Colors.transparent, end: const Color(0x7F000000)),
|
||||
color: new AnimatedColorValue(Colors.transparent, end: Colors.black54),
|
||||
child: new Container()
|
||||
),
|
||||
onTap: () {
|
||||
_performance.reverse();
|
||||
}
|
||||
)
|
||||
);
|
||||
|
||||
Widget content = new SlideTransition(
|
||||
performance: _performance.view,
|
||||
position: new AnimatedValue<Point>(_kClosedPosition, end: _kOpenPosition),
|
||||
// TODO(abarth): Use AnimatedContainer
|
||||
child: new Container(
|
||||
// behavior: implicitlyAnimate(const Duration(milliseconds: 200)),
|
||||
child: new AnimatedContainer(
|
||||
curve: ease,
|
||||
duration: _kThemeChangeDuration,
|
||||
decoration: new BoxDecoration(
|
||||
backgroundColor: Theme.of(context).canvasColor,
|
||||
boxShadow: shadows[config.level]),
|
||||
width: _kWidth,
|
||||
child: new Block(config.children)
|
||||
child: config.child
|
||||
)
|
||||
);
|
||||
|
||||
@ -117,33 +90,64 @@ class DrawerState extends State<Drawer> {
|
||||
onHorizontalDragStart: _performance.stop,
|
||||
onHorizontalDragUpdate: _handleDragUpdate,
|
||||
onHorizontalDragEnd: _handleDragEnd,
|
||||
child: new Stack([ mask, content ])
|
||||
child: new Stack([
|
||||
mask,
|
||||
new Positioned(
|
||||
top: 0.0,
|
||||
left: 0.0,
|
||||
bottom: 0.0,
|
||||
child: content
|
||||
)
|
||||
])
|
||||
);
|
||||
}
|
||||
|
||||
void _handleDismissed() {
|
||||
if (config.navigator != null &&
|
||||
config.navigator.currentRoute is StateRoute &&
|
||||
(config.navigator.currentRoute as StateRoute).owner == this) // TODO(ianh): remove cast once analyzer is cleverer
|
||||
config.navigator.pop();
|
||||
if (config.onDismissed != null)
|
||||
config.onDismissed();
|
||||
}
|
||||
|
||||
bool get _isMostlyClosed => _performance.progress < 0.5;
|
||||
|
||||
void _settle() { _isMostlyClosed ? _performance.reverse() : _performance.play(); }
|
||||
|
||||
void _handleDragUpdate(double delta) {
|
||||
_performance.progress += delta / _kWidth;
|
||||
}
|
||||
|
||||
void _open() {
|
||||
_performance.fling(velocity: 1.0);
|
||||
}
|
||||
|
||||
void _close() {
|
||||
_performance.fling(velocity: -1.0);
|
||||
}
|
||||
|
||||
void _handleDragEnd(Offset velocity) {
|
||||
if (velocity.dx.abs() >= _kMinFlingVelocity) {
|
||||
_performance.fling(velocity: velocity.dx * _kFlingVelocityScale);
|
||||
} else if (_isMostlyClosed) {
|
||||
_close();
|
||||
} else {
|
||||
_settle();
|
||||
_open();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class DrawerRoute extends Route {
|
||||
DrawerRoute({ this.child, this.level });
|
||||
|
||||
final Widget child;
|
||||
final int level;
|
||||
|
||||
bool get opaque => false;
|
||||
|
||||
Widget build(NavigatorState navigator, 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';
|
||||
import 'package:sky/src/widgets/basic.dart';
|
||||
import 'package:sky/src/widgets/button_state.dart';
|
||||
import 'package:sky/src/widgets/framework.dart';
|
||||
import 'package:sky/src/widgets/gesture_detector.dart';
|
||||
import 'package:sky/src/widgets/icon.dart';
|
||||
import 'package:sky/src/widgets/ink_well.dart';
|
||||
import 'package:sky/src/widgets/theme.dart';
|
||||
@ -21,7 +20,7 @@ class DrawerItem extends StatefulComponent {
|
||||
|
||||
final String icon;
|
||||
final Widget child;
|
||||
final GestureTapListener onPressed;
|
||||
final GestureTapCallback onPressed;
|
||||
final bool selected;
|
||||
|
||||
DrawerItemState createState() => new DrawerItemState();
|
||||
@ -76,14 +75,12 @@ class DrawerItemState extends ButtonState<DrawerItem> {
|
||||
)
|
||||
);
|
||||
|
||||
return new GestureDetector(
|
||||
onTap: config.onPressed,
|
||||
child: new Container(
|
||||
height: 48.0,
|
||||
decoration: new BoxDecoration(backgroundColor: _getBackgroundColor(themeData)),
|
||||
child: new InkWell(
|
||||
child: new Row(flexChildren)
|
||||
)
|
||||
return new Container(
|
||||
height: 48.0,
|
||||
decoration: new BoxDecoration(backgroundColor: _getBackgroundColor(themeData)),
|
||||
child: new InkWell(
|
||||
onTap: config.onPressed,
|
||||
child: new Row(flexChildren)
|
||||
)
|
||||
);
|
||||
}
|
||||
|
@ -2,6 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:sky/gestures.dart';
|
||||
import 'package:sky/material.dart';
|
||||
import 'package:sky/src/widgets/basic.dart';
|
||||
import 'package:sky/src/widgets/framework.dart';
|
||||
@ -13,7 +14,7 @@ class FlatButton extends MaterialButton {
|
||||
Key key,
|
||||
Widget child,
|
||||
bool enabled: true,
|
||||
Function onPressed
|
||||
GestureTapCallback onPressed
|
||||
}) : super(key: key,
|
||||
child: child,
|
||||
enabled: enabled,
|
||||
|
@ -6,7 +6,6 @@ import 'package:sky/gestures.dart';
|
||||
import 'package:sky/src/widgets/basic.dart';
|
||||
import 'package:sky/src/widgets/button_state.dart';
|
||||
import 'package:sky/src/widgets/framework.dart';
|
||||
import 'package:sky/src/widgets/gesture_detector.dart';
|
||||
import 'package:sky/src/widgets/icon.dart';
|
||||
import 'package:sky/src/widgets/ink_well.dart';
|
||||
import 'package:sky/src/widgets/material.dart';
|
||||
@ -26,7 +25,7 @@ class FloatingActionButton extends StatefulComponent {
|
||||
|
||||
final Widget child;
|
||||
final Color backgroundColor;
|
||||
final GestureTapListener onPressed;
|
||||
final GestureTapCallback onPressed;
|
||||
|
||||
FloatingActionButtonState createState() => new FloatingActionButtonState();
|
||||
}
|
||||
@ -46,17 +45,15 @@ class FloatingActionButtonState extends ButtonState<FloatingActionButton> {
|
||||
type: MaterialType.circle,
|
||||
level: highlight ? 3 : 2,
|
||||
child: new ClipOval(
|
||||
child: new GestureDetector(
|
||||
onTap: config.onPressed,
|
||||
child: new Container(
|
||||
width: _kSize,
|
||||
height: _kSize,
|
||||
child: new InkWell(
|
||||
child: new Center(
|
||||
child: new IconTheme(
|
||||
data: new IconThemeData(color: iconThemeColor),
|
||||
child: config.child
|
||||
)
|
||||
child: new Container(
|
||||
width: _kSize,
|
||||
height: _kSize,
|
||||
child: new InkWell(
|
||||
onTap: config.onPressed,
|
||||
child: new Center(
|
||||
child: new IconTheme(
|
||||
data: new IconThemeData(color: iconThemeColor),
|
||||
child: config.child
|
||||
)
|
||||
)
|
||||
)
|
||||
|
@ -181,7 +181,16 @@ abstract class Widget {
|
||||
/// Inflates this configuration to a concrete instance.
|
||||
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,
|
||||
@ -482,9 +491,11 @@ final _InactiveElements _inactiveElements = new _InactiveElements();
|
||||
typedef void ElementVisitor(Element element);
|
||||
|
||||
abstract class BuildContext {
|
||||
InheritedWidget inheritedWidgetOfType(Type targetType);
|
||||
Widget get widget;
|
||||
RenderObject findRenderObject();
|
||||
InheritedWidget inheritedWidgetOfType(Type targetType);
|
||||
void visitAncestorElements(bool visitor(Element element));
|
||||
void visitChildElements(void visitor(Element element));
|
||||
}
|
||||
|
||||
/// Elements are the instantiations of Widget configurations.
|
||||
@ -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.
|
||||
void visitChildren(ElementVisitor visitor) { }
|
||||
|
||||
/// Wrapper around visitChildren for BuildContext.
|
||||
void visitChildElements(void visitor(Element element)) {
|
||||
// don't allow visitChildElements() during build, since children aren't necessarily built yet
|
||||
assert(BuildableElement._debugStateLockLevel == 0);
|
||||
visitChildren(visitor);
|
||||
}
|
||||
|
||||
bool detachChild(Element child) => false;
|
||||
|
||||
/// This method is the core of the system.
|
||||
@ -782,6 +800,8 @@ abstract class Element<T extends Widget> implements BuildContext {
|
||||
description.add('no depth');
|
||||
if (widget == null)
|
||||
description.add('no widget');
|
||||
else
|
||||
widget.debugFillDescription(description);
|
||||
}
|
||||
|
||||
String toStringDeep([String prefixLineOne = '', String prefixOtherLines = '']) {
|
||||
@ -948,11 +968,11 @@ abstract class BuildableElement<T extends Widget> extends Element<T> {
|
||||
|
||||
/// Instantiation of StatelessComponent widgets.
|
||||
class StatelessComponentElement<T extends StatelessComponent> extends BuildableElement<T> {
|
||||
StatelessComponentElement(StatelessComponent widget) : super(widget) {
|
||||
StatelessComponentElement(T widget) : super(widget) {
|
||||
_builder = widget.build;
|
||||
}
|
||||
|
||||
void update(StatelessComponent newWidget) {
|
||||
void update(T newWidget) {
|
||||
super.update(newWidget);
|
||||
assert(widget == newWidget);
|
||||
_builder = widget.build;
|
||||
@ -962,10 +982,10 @@ class StatelessComponentElement<T extends StatelessComponent> extends BuildableE
|
||||
}
|
||||
|
||||
/// Instantiation of StatefulComponent widgets.
|
||||
class StatefulComponentElement extends BuildableElement<StatefulComponent> {
|
||||
StatefulComponentElement(StatefulComponent widget)
|
||||
class StatefulComponentElement<T extends StatefulComponent, U extends State<T>> extends BuildableElement<T> {
|
||||
StatefulComponentElement(T widget)
|
||||
: _state = widget.createState(), super(widget) {
|
||||
assert(_state._debugTypesAreRight(widget));
|
||||
assert(_state._debugTypesAreRight(widget)); // can't use T and U, since normally we don't actually set those
|
||||
assert(_state._element == null);
|
||||
_state._element = this;
|
||||
assert(_builder == null);
|
||||
@ -988,10 +1008,10 @@ class StatefulComponentElement extends BuildableElement<StatefulComponent> {
|
||||
assert(() { _state._debugLifecycleState = _StateLifecycle.ready; return true; });
|
||||
}
|
||||
|
||||
State get state => _state;
|
||||
State _state;
|
||||
U get state => _state;
|
||||
U _state;
|
||||
|
||||
void update(StatefulComponent newWidget) {
|
||||
void update(T newWidget) {
|
||||
super.update(newWidget);
|
||||
assert(widget == newWidget);
|
||||
StatefulComponent oldConfig = _state._config;
|
||||
|
@ -15,6 +15,8 @@ class GestureDetector extends StatefulComponent {
|
||||
this.child,
|
||||
this.onTap,
|
||||
this.onDoubleTap,
|
||||
this.onTapDown,
|
||||
this.onTapCancel,
|
||||
this.onShowPress,
|
||||
this.onLongPress,
|
||||
this.onVerticalDragStart,
|
||||
@ -32,10 +34,14 @@ class GestureDetector extends StatefulComponent {
|
||||
}) : super(key: key);
|
||||
|
||||
final Widget child;
|
||||
final GestureTapListener onTap;
|
||||
|
||||
final GestureTapCallback onTap;
|
||||
final GestureTapCallback onTapDown;
|
||||
final GestureTapCallback onTapCancel;
|
||||
final GestureTapListener onDoubleTap;
|
||||
final GestureShowPressListener onShowPress;
|
||||
final GestureLongPressListener onLongPress;
|
||||
|
||||
final GestureShowPressCallback onShowPress;
|
||||
final GestureLongPressCallback onLongPress;
|
||||
|
||||
final GestureDragStartCallback onVerticalDragStart;
|
||||
final GestureDragUpdateCallback onVerticalDragUpdate;
|
||||
@ -57,69 +63,24 @@ class GestureDetector extends StatefulComponent {
|
||||
}
|
||||
|
||||
class GestureDetectorState extends State<GestureDetector> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
didUpdateConfig(null);
|
||||
}
|
||||
|
||||
final PointerRouter _router = FlutterBinding.instance.pointerRouter;
|
||||
|
||||
TapGestureRecognizer _tap;
|
||||
TapGestureRecognizer _ensureTap() {
|
||||
if (_tap == null)
|
||||
_tap = new TapGestureRecognizer(router: _router);
|
||||
return _tap;
|
||||
}
|
||||
|
||||
DoubleTapGestureRecognizer _doubleTap;
|
||||
DoubleTapGestureRecognizer _ensureDoubleTap() {
|
||||
if (_doubleTap == null)
|
||||
_doubleTap = new DoubleTapGestureRecognizer(router: _router);
|
||||
return _doubleTap;
|
||||
}
|
||||
|
||||
ShowPressGestureRecognizer _showPress;
|
||||
ShowPressGestureRecognizer _ensureShowPress() {
|
||||
if (_showPress == null)
|
||||
_showPress = new ShowPressGestureRecognizer(router: _router);
|
||||
return _showPress;
|
||||
}
|
||||
|
||||
LongPressGestureRecognizer _longPress;
|
||||
LongPressGestureRecognizer _ensureLongPress() {
|
||||
if (_longPress == null)
|
||||
_longPress = new LongPressGestureRecognizer(router: _router);
|
||||
return _longPress;
|
||||
}
|
||||
|
||||
VerticalDragGestureRecognizer _verticalDrag;
|
||||
VerticalDragGestureRecognizer _ensureVerticalDrag() {
|
||||
if (_verticalDrag == null)
|
||||
_verticalDrag = new VerticalDragGestureRecognizer(router: _router);
|
||||
return _verticalDrag;
|
||||
}
|
||||
|
||||
HorizontalDragGestureRecognizer _horizontalDrag;
|
||||
HorizontalDragGestureRecognizer _ensureHorizontalDrag() {
|
||||
if (_horizontalDrag == null)
|
||||
_horizontalDrag = new HorizontalDragGestureRecognizer(router: _router);
|
||||
return _horizontalDrag;
|
||||
}
|
||||
|
||||
PanGestureRecognizer _pan;
|
||||
PanGestureRecognizer _ensurePan() {
|
||||
assert(_scale == null); // Scale is a superset of pan; just use scale
|
||||
if (_pan == null)
|
||||
_pan = new PanGestureRecognizer(router: _router);
|
||||
return _pan;
|
||||
ScaleGestureRecognizer _scale;
|
||||
|
||||
void initState() {
|
||||
super.initState();
|
||||
_syncAll();
|
||||
}
|
||||
|
||||
ScaleGestureRecognizer _scale;
|
||||
ScaleGestureRecognizer _ensureScale() {
|
||||
assert(_pan == null); // Scale is a superset of pan; just use scale
|
||||
if (_scale == null)
|
||||
_scale = new ScaleGestureRecognizer(router: _router);
|
||||
return _scale;
|
||||
void didUpdateConfig(GestureDetector oldConfig) {
|
||||
_syncAll();
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
@ -134,7 +95,7 @@ class GestureDetectorState extends State<GestureDetector> {
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
void didUpdateConfig(GestureDetector oldConfig) {
|
||||
void _syncAll() {
|
||||
_syncTap();
|
||||
_syncDoubleTap();
|
||||
_syncShowPress();
|
||||
@ -146,10 +107,15 @@ class GestureDetectorState extends State<GestureDetector> {
|
||||
}
|
||||
|
||||
void _syncTap() {
|
||||
if (config.onTap == null)
|
||||
if (config.onTap == null && config.onTapDown == null && config.onTapCancel == null) {
|
||||
_tap = _ensureDisposed(_tap);
|
||||
else
|
||||
_ensureTap().onTap = config.onTap;
|
||||
} else {
|
||||
_tap ??= new TapGestureRecognizer(router: _router);
|
||||
_tap
|
||||
..onTap = config.onTap
|
||||
..onTapDown = config.onTapDown
|
||||
..onTapCancel = config.onTapCancel;
|
||||
}
|
||||
}
|
||||
|
||||
void _syncDoubleTap() {
|
||||
@ -160,24 +126,29 @@ class GestureDetectorState extends State<GestureDetector> {
|
||||
}
|
||||
|
||||
void _syncShowPress() {
|
||||
if (config.onShowPress == null)
|
||||
if (config.onShowPress == null) {
|
||||
_showPress = _ensureDisposed(_showPress);
|
||||
else
|
||||
_ensureShowPress().onShowPress = config.onShowPress;
|
||||
} else {
|
||||
_showPress ??= new ShowPressGestureRecognizer(router: _router);
|
||||
_showPress.onShowPress = config.onShowPress;
|
||||
}
|
||||
}
|
||||
|
||||
void _syncLongPress() {
|
||||
if (config.onLongPress == null)
|
||||
if (config.onLongPress == null) {
|
||||
_longPress = _ensureDisposed(_longPress);
|
||||
else
|
||||
_ensureLongPress().onLongPress = config.onLongPress;
|
||||
} else {
|
||||
_longPress ??= new LongPressGestureRecognizer(router: _router);
|
||||
_longPress.onLongPress = config.onLongPress;
|
||||
}
|
||||
}
|
||||
|
||||
void _syncVerticalDrag() {
|
||||
if (config.onVerticalDragStart == null && config.onVerticalDragUpdate == null && config.onVerticalDragEnd == null) {
|
||||
_verticalDrag = _ensureDisposed(_verticalDrag);
|
||||
} else {
|
||||
_ensureVerticalDrag()
|
||||
_verticalDrag ??= new VerticalDragGestureRecognizer(router: _router);
|
||||
_verticalDrag
|
||||
..onStart = config.onVerticalDragStart
|
||||
..onUpdate = config.onVerticalDragUpdate
|
||||
..onEnd = config.onVerticalDragEnd;
|
||||
@ -188,7 +159,8 @@ class GestureDetectorState extends State<GestureDetector> {
|
||||
if (config.onHorizontalDragStart == null && config.onHorizontalDragUpdate == null && config.onHorizontalDragEnd == null) {
|
||||
_horizontalDrag = _ensureDisposed(_horizontalDrag);
|
||||
} else {
|
||||
_ensureHorizontalDrag()
|
||||
_horizontalDrag ??= new HorizontalDragGestureRecognizer(router: _router);
|
||||
_horizontalDrag
|
||||
..onStart = config.onHorizontalDragStart
|
||||
..onUpdate = config.onHorizontalDragUpdate
|
||||
..onEnd = config.onHorizontalDragEnd;
|
||||
@ -199,7 +171,9 @@ class GestureDetectorState extends State<GestureDetector> {
|
||||
if (config.onPanStart == null && config.onPanUpdate == null && config.onPanEnd == null) {
|
||||
_pan = _ensureDisposed(_pan);
|
||||
} else {
|
||||
_ensurePan()
|
||||
assert(_scale == null); // Scale is a superset of pan; just use scale
|
||||
_pan ??= new PanGestureRecognizer(router: _router);
|
||||
_pan
|
||||
..onStart = config.onPanStart
|
||||
..onUpdate = config.onPanUpdate
|
||||
..onEnd = config.onPanEnd;
|
||||
@ -208,9 +182,11 @@ class GestureDetectorState extends State<GestureDetector> {
|
||||
|
||||
void _syncScale() {
|
||||
if (config.onScaleStart == null && config.onScaleUpdate == null && config.onScaleEnd == null) {
|
||||
_scale = _ensureDisposed(_pan);
|
||||
_scale = _ensureDisposed(_scale);
|
||||
} else {
|
||||
_ensureScale()
|
||||
assert(_pan == null); // Scale is a superset of pan; just use scale
|
||||
_scale ??= new ScaleGestureRecognizer(router: _router);
|
||||
_scale
|
||||
..onStart = config.onScaleStart
|
||||
..onUpdate = config.onScaleUpdate
|
||||
..onEnd = config.onScaleEnd;
|
||||
|
@ -4,6 +4,7 @@
|
||||
|
||||
import 'dart:sky' as sky;
|
||||
|
||||
import 'package:sky/gestures.dart';
|
||||
import 'package:sky/src/widgets/basic.dart';
|
||||
import 'package:sky/src/widgets/icon.dart';
|
||||
import 'package:sky/src/widgets/framework.dart';
|
||||
@ -13,8 +14,8 @@ class IconButton extends StatelessComponent {
|
||||
const IconButton({ Key key, this.icon, this.onPressed, this.color }) : super(key: key);
|
||||
|
||||
final String icon;
|
||||
final Function onPressed;
|
||||
final Color color;
|
||||
final GestureTapCallback onPressed;
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
Widget child = new Icon(type: icon, size: 24);
|
||||
|
@ -2,16 +2,18 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'dart:async';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:sky' as sky;
|
||||
|
||||
import 'package:sky/animation.dart';
|
||||
import 'package:sky/gestures.dart';
|
||||
import 'package:sky/rendering.dart';
|
||||
import 'package:sky/src/widgets/basic.dart';
|
||||
import 'package:sky/src/widgets/framework.dart';
|
||||
|
||||
const int _kSplashInitialOpacity = 0x30;
|
||||
const double _kSplashCancelledVelocity = 0.7;
|
||||
const double _kSplashCanceledVelocity = 0.7;
|
||||
const double _kSplashConfirmedVelocity = 0.7;
|
||||
const double _kSplashInitialSize = 0.0;
|
||||
const double _kSplashUnconfirmedVelocity = 0.2;
|
||||
@ -25,40 +27,60 @@ double _getSplashTargetSize(Size bounds, Point position) {
|
||||
}
|
||||
|
||||
class InkSplash {
|
||||
InkSplash(this.pointer, this.position, this.well) {
|
||||
InkSplash(this.position, this.well) {
|
||||
_targetRadius = _getSplashTargetSize(well.size, position);
|
||||
_radius = new AnimatedValue<double>(
|
||||
_kSplashInitialSize, end: _targetRadius, curve: easeOut);
|
||||
|
||||
_performance = new ValueAnimation<double>(
|
||||
_performance = new ValuePerformance<double>(
|
||||
variable: _radius,
|
||||
duration: new Duration(milliseconds: (_targetRadius / _kSplashUnconfirmedVelocity).floor())
|
||||
)..addListener(_handleRadiusChange)
|
||||
..play();
|
||||
)..addListener(_handleRadiusChange);
|
||||
|
||||
// Wait kTapTimeout to avoid creating tiny splashes during scrolls.
|
||||
_startTimer = new Timer(kTapTimeout, _play);
|
||||
}
|
||||
|
||||
final int pointer;
|
||||
final Point position;
|
||||
final RenderInkWell well;
|
||||
|
||||
double _targetRadius;
|
||||
double _pinnedRadius;
|
||||
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) {
|
||||
int duration = (_targetRadius / velocity).floor();
|
||||
_performance
|
||||
..duration = new Duration(milliseconds: duration)
|
||||
..play();
|
||||
_performance.duration = new Duration(milliseconds: duration);
|
||||
_play();
|
||||
}
|
||||
|
||||
void confirm() {
|
||||
if (_cancelStartTimer())
|
||||
return;
|
||||
_updateVelocity(_kSplashConfirmedVelocity);
|
||||
_pinnedRadius = null;
|
||||
}
|
||||
|
||||
void cancel() {
|
||||
_updateVelocity(_kSplashCancelledVelocity);
|
||||
if (_cancelStartTimer())
|
||||
return;
|
||||
_updateVelocity(_kSplashCanceledVelocity);
|
||||
_pinnedRadius = _radius.value;
|
||||
}
|
||||
|
||||
@ -77,38 +99,95 @@ class InkSplash {
|
||||
}
|
||||
|
||||
class RenderInkWell extends RenderProxyBox {
|
||||
RenderInkWell({ RenderBox child }) : super(child);
|
||||
RenderInkWell({
|
||||
RenderBox child,
|
||||
GestureTapCallback onTap,
|
||||
GestureLongPressCallback onLongPress
|
||||
}) : super(child) {
|
||||
this.onTap = onTap;
|
||||
this.onLongPress = onLongPress;
|
||||
}
|
||||
|
||||
GestureTapCallback get onTap => _onTap;
|
||||
GestureTapCallback _onTap;
|
||||
void set onTap (GestureTapCallback value) {
|
||||
_onTap = value;
|
||||
_syncTapRecognizer();
|
||||
}
|
||||
|
||||
GestureTapCallback get onLongPress => _onLongPress;
|
||||
GestureTapCallback _onLongPress;
|
||||
void set onLongPress (GestureTapCallback value) {
|
||||
_onLongPress = value;
|
||||
_syncLongPressRecognizer();
|
||||
}
|
||||
|
||||
final List<InkSplash> _splashes = new List<InkSplash>();
|
||||
|
||||
TapGestureRecognizer _tap;
|
||||
LongPressGestureRecognizer _longPress;
|
||||
|
||||
void handleEvent(sky.Event event, BoxHitTestEntry entry) {
|
||||
// TODO(abarth): We should trigger these effects based on gestures.
|
||||
// https://github.com/flutter/engine/issues/1271
|
||||
if (event is sky.PointerEvent) {
|
||||
switch (event.type) {
|
||||
case 'pointerdown':
|
||||
_startSplash(event.pointer, entry.localPosition);
|
||||
break;
|
||||
case 'pointerup':
|
||||
_confirmSplash(event.pointer);
|
||||
break;
|
||||
}
|
||||
if (event.type == 'pointerdown' && (_tap != null || _longPress != null)) {
|
||||
_tap?.addPointer(event);
|
||||
_longPress?.addPointer(event);
|
||||
_splashes.add(new InkSplash(entry.localPosition, this));
|
||||
}
|
||||
}
|
||||
|
||||
void _startSplash(int pointer, Point position) {
|
||||
_splashes.add(new InkSplash(pointer, position, this));
|
||||
markNeedsPaint();
|
||||
void attach() {
|
||||
super.attach();
|
||||
_syncTapRecognizer();
|
||||
_syncLongPressRecognizer();
|
||||
}
|
||||
|
||||
void _forEachSplash(int pointer, Function callback) {
|
||||
_splashes.where((splash) => splash.pointer == pointer)
|
||||
.forEach(callback);
|
||||
void detach() {
|
||||
_disposeTapRecognizer();
|
||||
_disposeLongPressRecognizer();
|
||||
super.detach();
|
||||
}
|
||||
|
||||
void _confirmSplash(int pointer) {
|
||||
_forEachSplash(pointer, (splash) { splash.confirm(); });
|
||||
markNeedsPaint();
|
||||
void _syncTapRecognizer() {
|
||||
if (onTap == null) {
|
||||
_disposeTapRecognizer();
|
||||
} else {
|
||||
_tap ??= new TapGestureRecognizer(router: FlutterBinding.instance.pointerRouter)
|
||||
..onTap = _handleTap
|
||||
..onTapCancel = _handleTapCancel;
|
||||
}
|
||||
}
|
||||
|
||||
void _disposeTapRecognizer() {
|
||||
_tap?.dispose();
|
||||
_tap = null;
|
||||
}
|
||||
|
||||
void _syncLongPressRecognizer() {
|
||||
if (onLongPress == null) {
|
||||
_disposeLongPressRecognizer();
|
||||
} else {
|
||||
_longPress ??= new LongPressGestureRecognizer(router: FlutterBinding.instance.pointerRouter)
|
||||
..onLongPress = _handleLongPress;
|
||||
}
|
||||
}
|
||||
|
||||
void _disposeLongPressRecognizer() {
|
||||
_longPress?.dispose();
|
||||
_longPress = null;
|
||||
}
|
||||
|
||||
void _handleTap() {
|
||||
_splashes.last?.confirm();
|
||||
onTap();
|
||||
}
|
||||
|
||||
void _handleTapCancel() {
|
||||
_splashes.last?.cancel();
|
||||
}
|
||||
|
||||
void _handleLongPress() {
|
||||
_splashes.last?.confirm();
|
||||
onLongPress();
|
||||
}
|
||||
|
||||
void paint(PaintingContext context, Offset offset) {
|
||||
@ -126,6 +205,20 @@ class RenderInkWell extends RenderProxyBox {
|
||||
}
|
||||
|
||||
class InkWell extends OneChildRenderObjectWidget {
|
||||
InkWell({ Key key, Widget child }) : super(key: key, child: child);
|
||||
RenderInkWell createRenderObject() => new RenderInkWell();
|
||||
InkWell({
|
||||
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 {
|
||||
this.placeholder,
|
||||
this.onChanged,
|
||||
this.keyboardType: KeyboardType.TEXT
|
||||
}): super(
|
||||
}) : super(
|
||||
key: key,
|
||||
initialScrollOffset: 0.0,
|
||||
scrollDirection: ScrollDirection.horizontal
|
||||
|
@ -2,8 +2,10 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:sky/animation.dart';
|
||||
import 'package:sky/painting.dart';
|
||||
import 'package:sky/material.dart';
|
||||
import 'package:sky/src/widgets/animated_container.dart';
|
||||
import 'package:sky/src/widgets/basic.dart';
|
||||
import 'package:sky/src/widgets/framework.dart';
|
||||
import 'package:sky/src/widgets/theme.dart';
|
||||
@ -61,10 +63,11 @@ class Material extends StatelessComponent {
|
||||
);
|
||||
}
|
||||
}
|
||||
// TODO(abarth): This should use AnimatedContainer.
|
||||
return new DefaultTextStyle(
|
||||
style: Theme.of(context).text.body1,
|
||||
child: new Container(
|
||||
child: new AnimatedContainer(
|
||||
curve: ease,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
decoration: new BoxDecoration(
|
||||
backgroundColor: getBackgroundColor(context),
|
||||
borderRadius: edges[type],
|
||||
|
@ -2,10 +2,10 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:sky/gestures.dart';
|
||||
import 'package:sky/src/widgets/basic.dart';
|
||||
import 'package:sky/src/widgets/button_state.dart';
|
||||
import 'package:sky/src/widgets/framework.dart';
|
||||
import 'package:sky/src/widgets/gesture_detector.dart';
|
||||
import 'package:sky/src/widgets/ink_well.dart';
|
||||
import 'package:sky/src/widgets/material.dart';
|
||||
|
||||
@ -22,7 +22,7 @@ abstract class MaterialButton extends StatefulComponent {
|
||||
|
||||
final Widget child;
|
||||
final bool enabled;
|
||||
final Function onPressed;
|
||||
final GestureTapCallback onPressed;
|
||||
}
|
||||
|
||||
abstract class MaterialButtonState<T extends MaterialButton> extends ButtonState<T> {
|
||||
@ -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...
|
||||
)
|
||||
);
|
||||
return new GestureDetector(
|
||||
onTap: config.enabled ? config.onPressed : null,
|
||||
child: new Container(
|
||||
height: 36.0,
|
||||
constraints: new BoxConstraints(minWidth: 88.0),
|
||||
margin: new EdgeDims.all(8.0),
|
||||
child: new Material(
|
||||
type: MaterialType.button,
|
||||
child: config.enabled ? new InkWell(child: contents) : contents,
|
||||
level: level,
|
||||
color: getColor(context)
|
||||
return new Container(
|
||||
height: 36.0,
|
||||
constraints: new BoxConstraints(minWidth: 88.0),
|
||||
margin: new EdgeDims.all(8.0),
|
||||
child: new Material(
|
||||
type: MaterialType.button,
|
||||
level: level,
|
||||
color: getColor(context),
|
||||
child: new InkWell(
|
||||
onTap: config.enabled ? config.onPressed : null,
|
||||
child: contents
|
||||
)
|
||||
)
|
||||
);
|
||||
|
@ -22,7 +22,7 @@ class MixedViewport extends RenderObjectWidget {
|
||||
this.token,
|
||||
this.onExtentsUpdate,
|
||||
this.onInvalidatorAvailable
|
||||
}): super(key: key);
|
||||
}) : super(key: key);
|
||||
|
||||
final double startOffset;
|
||||
final ScrollDirection direction;
|
||||
|
@ -8,7 +8,14 @@ import 'package:sky/src/widgets/focus.dart';
|
||||
import 'package:sky/src/widgets/framework.dart';
|
||||
import 'package:sky/src/widgets/transitions.dart';
|
||||
|
||||
typedef Widget RouteBuilder(NavigatorState navigator, Route route);
|
||||
class RouteArguments {
|
||||
const RouteArguments({ this.navigator, this.previousPerformance, this.nextPerformance });
|
||||
final NavigatorState navigator;
|
||||
final PerformanceView previousPerformance;
|
||||
final PerformanceView nextPerformance;
|
||||
}
|
||||
|
||||
typedef Widget RouteBuilder(RouteArguments args);
|
||||
typedef RouteBuilder RouteGenerator(String name);
|
||||
typedef void StateRouteCallback(StateRoute route);
|
||||
typedef void NotificationCallback();
|
||||
@ -118,7 +125,7 @@ class NavigatorState extends State<Navigator> {
|
||||
Widget build(BuildContext context) {
|
||||
List<Widget> visibleRoutes = new List<Widget>();
|
||||
bool alreadyInsertModalBarrier = false;
|
||||
WatchableAnimationPerformance nextPerformance;
|
||||
PerformanceView nextPerformance;
|
||||
for (int i = _history.length-1; i >= 0; i -= 1) {
|
||||
Route route = _history[i];
|
||||
if (!route.hasContent) {
|
||||
@ -126,11 +133,16 @@ class NavigatorState extends State<Navigator> {
|
||||
continue;
|
||||
}
|
||||
route.ensurePerformance(
|
||||
direction: (i <= _currentPosition) ? Direction.forward : Direction.reverse
|
||||
direction: (i <= _currentPosition) ? AnimationDirection.forward : AnimationDirection.reverse
|
||||
);
|
||||
route._onDismissed = () {
|
||||
assert(_history.contains(route));
|
||||
if (_history.lastIndexOf(route) <= _currentPosition)
|
||||
popRoute(route);
|
||||
};
|
||||
route._onRemoveRoute = () {
|
||||
assert(_history.contains(route));
|
||||
setState(() {
|
||||
assert(_history.contains(route));
|
||||
_history.remove(route);
|
||||
});
|
||||
};
|
||||
@ -154,33 +166,39 @@ class NavigatorState extends State<Navigator> {
|
||||
}
|
||||
return new Focus(child: new Stack(visibleRoutes.reversed.toList()));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
abstract class Route {
|
||||
|
||||
WatchableAnimationPerformance get performance => _performance?.view;
|
||||
AnimationPerformance _performance;
|
||||
PerformanceView get performance => _performance?.view;
|
||||
Performance _performance;
|
||||
NotificationCallback _onDismissed;
|
||||
NotificationCallback _onRemoveRoute;
|
||||
|
||||
AnimationPerformance createPerformance() {
|
||||
Performance createPerformance() {
|
||||
Duration duration = transitionDuration;
|
||||
if (duration > Duration.ZERO) {
|
||||
return new AnimationPerformance(duration: duration)
|
||||
..addStatusListener((AnimationStatus status) {
|
||||
if (status == AnimationStatus.dismissed && _onDismissed != null)
|
||||
_onDismissed();
|
||||
return new Performance(duration: duration)
|
||||
..addStatusListener((PerformanceStatus status) {
|
||||
if (status == PerformanceStatus.dismissed) {
|
||||
if (_onDismissed != null)
|
||||
_onDismissed();
|
||||
if (_onRemoveRoute != null)
|
||||
_onRemoveRoute();
|
||||
}
|
||||
});
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
void ensurePerformance({ Direction direction }) {
|
||||
void ensurePerformance({ AnimationDirection direction }) {
|
||||
assert(direction != null);
|
||||
if (_performance == null)
|
||||
_performance = createPerformance();
|
||||
if (_performance != null) {
|
||||
AnimationStatus desiredStatus = direction == Direction.forward ? AnimationStatus.forward : AnimationStatus.reverse;
|
||||
PerformanceStatus desiredStatus = direction == AnimationDirection.forward ? PerformanceStatus.forward : PerformanceStatus.reverse;
|
||||
if (_performance.status != desiredStatus)
|
||||
_performance.play(direction);
|
||||
}
|
||||
@ -236,17 +254,17 @@ abstract class Route {
|
||||
/// cover the entire application surface or are in any way semi-transparent.
|
||||
bool get opaque => false;
|
||||
|
||||
/// If this is set to a non-zero [Duration], then an [AnimationPerformance]
|
||||
/// If this is set to a non-zero [Duration], then an [Performance]
|
||||
/// object, available via the performance field, will be created when the
|
||||
/// route is first built, using the duration described here.
|
||||
Duration get transitionDuration => Duration.ZERO;
|
||||
|
||||
bool get isActuallyOpaque => (performance == null || _performance.isCompleted) && opaque;
|
||||
|
||||
Widget build(NavigatorState navigator, WatchableAnimationPerformance nextRoutePerformance);
|
||||
Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance);
|
||||
void didPop([dynamic result]) {
|
||||
if (performance == null && _onDismissed != null)
|
||||
_onDismissed();
|
||||
if (performance == null && _onRemoveRoute != null)
|
||||
_onRemoveRoute();
|
||||
}
|
||||
|
||||
String toString() => '$runtimeType()';
|
||||
@ -263,7 +281,7 @@ class PageRoute extends Route {
|
||||
bool get opaque => true;
|
||||
Duration get transitionDuration => _kTransitionDuration;
|
||||
|
||||
Widget build(NavigatorState navigator, WatchableAnimationPerformance nextRoutePerformance) {
|
||||
Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance) {
|
||||
// TODO(jackson): Hit testing should ignore transform
|
||||
// TODO(jackson): Block input unless content is interactive
|
||||
return new SlideTransition(
|
||||
@ -272,7 +290,7 @@ class PageRoute extends Route {
|
||||
child: new FadeTransition(
|
||||
performance: performance,
|
||||
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 {
|
||||
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';
|
||||
import 'package:sky/src/widgets/basic.dart';
|
||||
import 'package:sky/src/widgets/focus.dart';
|
||||
import 'package:sky/src/widgets/framework.dart';
|
||||
import 'package:sky/src/widgets/gesture_detector.dart';
|
||||
import 'package:sky/src/widgets/ink_well.dart';
|
||||
import 'package:sky/src/widgets/navigator.dart';
|
||||
import 'package:sky/src/widgets/popup_menu_item.dart';
|
||||
import 'package:sky/src/widgets/scrollable.dart';
|
||||
@ -29,7 +29,7 @@ const double _kMenuVerticalPadding = 8.0;
|
||||
|
||||
typedef List<PopupMenuItem> PopupMenuItemsBuilder(NavigatorState navigator);
|
||||
|
||||
class PopupMenu extends StatefulComponent {
|
||||
class PopupMenu extends StatelessComponent {
|
||||
PopupMenu({
|
||||
Key key,
|
||||
this.items,
|
||||
@ -44,78 +44,48 @@ class PopupMenu extends StatefulComponent {
|
||||
final List<PopupMenuItem> items;
|
||||
final int level;
|
||||
final NavigatorState navigator;
|
||||
final WatchableAnimationPerformance 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);
|
||||
}
|
||||
final PerformanceView performance;
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
_updateBoxPainter(new BoxDecoration(
|
||||
final BoxPainter painter = new BoxPainter(new BoxDecoration(
|
||||
backgroundColor: Theme.of(context).canvasColor,
|
||||
borderRadius: 2.0,
|
||||
boxShadow: shadows[config.level]
|
||||
boxShadow: shadows[level]
|
||||
));
|
||||
double unit = 1.0 / (config.items.length + 1.5); // 1.0 for the width and 0.5 for the last item's fade.
|
||||
|
||||
double unit = 1.0 / (items.length + 1.5); // 1.0 for the width and 0.5 for the last item's fade.
|
||||
List<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 end = (start + 1.5 * unit).clamp(0.0, 1.0);
|
||||
children.add(new FadeTransition(
|
||||
performance: config.performance,
|
||||
opacity: new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(start, end)),
|
||||
child: new GestureDetector(
|
||||
onTap: () { config.navigator.pop(config.items[i].value); },
|
||||
child: config.items[i]
|
||||
performance: performance,
|
||||
opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(start, end)),
|
||||
child: new InkWell(
|
||||
onTap: () { navigator.pop(items[i].value); },
|
||||
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(
|
||||
performance: config.performance,
|
||||
opacity: new AnimatedValue<double>(0.0, end: 1.0, interval: new Interval(0.0, 1.0 / 3.0)),
|
||||
performance: performance,
|
||||
opacity: new AnimatedValue<double>(0.0, end: 1.0, curve: new Interval(0.0, 1.0 / 3.0)),
|
||||
child: new Container(
|
||||
margin: new EdgeDims.all(_kMenuMargin),
|
||||
child: new BuilderTransition(
|
||||
performance: config.performance,
|
||||
performance: performance,
|
||||
variables: [width, height],
|
||||
builder: (BuildContext context) {
|
||||
return new CustomPaint(
|
||||
callback: (sky.Canvas canvas, Size size) {
|
||||
double widthValue = width.value * size.width;
|
||||
double heightValue = height.value * size.height;
|
||||
_painter.paint(canvas, new Rect.fromLTWH(size.width - widthValue, 0.0, widthValue, heightValue));
|
||||
painter.paint(canvas, new Rect.fromLTWH(size.width - widthValue, 0.0, widthValue, heightValue));
|
||||
},
|
||||
child: new ConstrainedBox(
|
||||
constraints: new BoxConstraints(
|
||||
@ -159,10 +129,10 @@ class MenuRoute extends Route {
|
||||
final PopupMenuItemsBuilder builder;
|
||||
final int level;
|
||||
|
||||
AnimationPerformance createPerformance() {
|
||||
AnimationPerformance result = super.createPerformance();
|
||||
Performance createPerformance() {
|
||||
Performance result = super.createPerformance();
|
||||
AnimationTiming timing = new AnimationTiming();
|
||||
timing.reverseInterval = new Interval(0.0, _kMenuCloseIntervalEnd);
|
||||
timing.reverseCurve = new Interval(0.0, _kMenuCloseIntervalEnd);
|
||||
result.timing = timing;
|
||||
return result;
|
||||
}
|
||||
@ -172,7 +142,7 @@ class MenuRoute extends Route {
|
||||
bool get opaque => false;
|
||||
Duration get transitionDuration => _kMenuDuration;
|
||||
|
||||
Widget build(NavigatorState navigator, WatchableAnimationPerformance nextRoutePerformance) {
|
||||
Widget build(NavigatorState navigator, PerformanceView nextRoutePerformance) {
|
||||
return new Positioned(
|
||||
top: position?.top,
|
||||
right: position?.right,
|
||||
|
@ -4,7 +4,6 @@
|
||||
|
||||
import 'package:sky/src/widgets/basic.dart';
|
||||
import 'package:sky/src/widgets/framework.dart';
|
||||
import 'package:sky/src/widgets/ink_well.dart';
|
||||
import 'package:sky/src/widgets/theme.dart';
|
||||
|
||||
const double _kMenuItemHeight = 48.0;
|
||||
@ -21,15 +20,13 @@ class PopupMenuItem extends StatelessComponent {
|
||||
final dynamic value;
|
||||
|
||||
Widget build(BuildContext context) {
|
||||
return new InkWell(
|
||||
child: new Container(
|
||||
height: _kMenuItemHeight,
|
||||
child: new DefaultTextStyle(
|
||||
style: Theme.of(context).text.subhead,
|
||||
child: new Baseline(
|
||||
baseline: _kMenuItemHeight - _kBaselineOffsetFromBottom,
|
||||
child: child
|
||||
)
|
||||
return new Container(
|
||||
height: _kMenuItemHeight,
|
||||
child: new DefaultTextStyle(
|
||||
style: Theme.of(context).text.subhead,
|
||||
child: new Baseline(
|
||||
baseline: _kMenuItemHeight - _kBaselineOffsetFromBottom,
|
||||
child: child
|
||||
)
|
||||
)
|
||||
);
|
||||
|
@ -36,16 +36,16 @@ abstract class ProgressIndicator extends StatefulComponent {
|
||||
|
||||
class ProgressIndicatorState extends State<ProgressIndicator> {
|
||||
|
||||
ValueAnimation<double> _performance;
|
||||
ValuePerformance<double> _performance;
|
||||
|
||||
void initState() {
|
||||
super.initState();
|
||||
_performance = new ValueAnimation<double>(
|
||||
_performance = new ValuePerformance<double>(
|
||||
variable: new AnimatedValue<double>(0.0, end: 1.0, curve: ease),
|
||||
duration: const Duration(milliseconds: 1500)
|
||||
);
|
||||
_performance.addStatusListener((AnimationStatus status) {
|
||||
if (status == AnimationStatus.completed)
|
||||
_performance.addStatusListener((PerformanceStatus status) {
|
||||
if (status == PerformanceStatus.completed)
|
||||
_restartAnimation();
|
||||
});
|
||||
_performance.play();
|
||||
|
@ -2,6 +2,7 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:sky/gestures.dart';
|
||||
import 'package:sky/material.dart';
|
||||
import 'package:sky/src/widgets/basic.dart';
|
||||
import 'package:sky/src/widgets/framework.dart';
|
||||
@ -13,7 +14,7 @@ class RaisedButton extends MaterialButton {
|
||||
Key key,
|
||||
Widget child,
|
||||
bool enabled: true,
|
||||
Function onPressed
|
||||
GestureTapCallback onPressed
|
||||
}) : super(key: key,
|
||||
child: child,
|
||||
enabled: enabled,
|
||||
|
@ -51,16 +51,10 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
|
||||
super.initState();
|
||||
if (config.initialScrollOffset is double)
|
||||
_scrollOffset = config.initialScrollOffset;
|
||||
_toEndAnimation = new AnimatedSimulation(_setScrollOffset);
|
||||
_toOffsetAnimation = new ValueAnimation<double>()
|
||||
..addListener(() {
|
||||
AnimatedValue<double> offset = _toOffsetAnimation.variable;
|
||||
_setScrollOffset(offset.value);
|
||||
});
|
||||
_animation = new SimulationStepper(_setScrollOffset);
|
||||
}
|
||||
|
||||
AnimatedSimulation _toEndAnimation; // See _startToEndAnimation()
|
||||
ValueAnimation<double> _toOffsetAnimation; // Started by scrollTo()
|
||||
SimulationStepper _animation;
|
||||
|
||||
double _scrollOffset = 0.0;
|
||||
double get scrollOffset => _scrollOffset;
|
||||
@ -106,23 +100,10 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
|
||||
|
||||
Widget buildContent(BuildContext context);
|
||||
|
||||
Future _startToOffsetAnimation(double newScrollOffset, Duration duration, Curve curve) {
|
||||
_stopAnimations();
|
||||
_toOffsetAnimation
|
||||
..variable = new AnimatedValue<double>(scrollOffset,
|
||||
end: newScrollOffset,
|
||||
curve: curve
|
||||
)
|
||||
..progress = 0.0
|
||||
..duration = duration;
|
||||
return _toOffsetAnimation.play();
|
||||
}
|
||||
|
||||
void _stopAnimations() {
|
||||
if (_toOffsetAnimation.isAnimating)
|
||||
_toOffsetAnimation.stop();
|
||||
if (_toEndAnimation.isAnimating)
|
||||
_toEndAnimation.stop();
|
||||
Future _animateTo(double newScrollOffset, Duration duration, Curve curve) {
|
||||
_animation.stop();
|
||||
_animation.value = scrollOffset;
|
||||
return _animation.animateTo(newScrollOffset, duration: duration, curve: curve);
|
||||
}
|
||||
|
||||
bool _scrollOffsetIsInBounds(double offset) {
|
||||
@ -165,16 +146,16 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
|
||||
}
|
||||
|
||||
Future _startToEndAnimation({ double velocity }) {
|
||||
_stopAnimations();
|
||||
_animation.stop();
|
||||
Simulation simulation =
|
||||
_createSnapSimulation(velocity) ?? _createFlingSimulation(velocity ?? 0.0);
|
||||
if (simulation == null)
|
||||
return new Future.value();
|
||||
return _toEndAnimation.start(simulation);
|
||||
return _animation.animateWith(simulation);
|
||||
}
|
||||
|
||||
void dispose() {
|
||||
_stopAnimations();
|
||||
_animation.stop();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@ -193,12 +174,12 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
|
||||
return new Future.value();
|
||||
|
||||
if (duration == null) {
|
||||
_stopAnimations();
|
||||
_animation.stop();
|
||||
_setScrollOffset(newScrollOffset);
|
||||
return new Future.value();
|
||||
}
|
||||
|
||||
return _startToOffsetAnimation(newScrollOffset, duration, curve);
|
||||
return _animateTo(newScrollOffset, duration, curve);
|
||||
}
|
||||
|
||||
Future scrollBy(double scrollDelta, { Duration duration, Curve curve }) {
|
||||
@ -209,7 +190,7 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
|
||||
Future fling(Offset velocity) {
|
||||
if (velocity != Offset.zero)
|
||||
return _startToEndAnimation(velocity: _scrollVelocity(velocity));
|
||||
if (!_toEndAnimation.isAnimating && (_toOffsetAnimation == null || !_toOffsetAnimation.isAnimating))
|
||||
if (!_animation.isAnimating)
|
||||
return settleScrollOffset();
|
||||
return new Future.value();
|
||||
}
|
||||
@ -226,7 +207,7 @@ abstract class ScrollableState<T extends Scrollable> extends State<T> {
|
||||
}
|
||||
|
||||
void _handlePointerDown(_) {
|
||||
_stopAnimations();
|
||||
_animation.stop();
|
||||
}
|
||||
|
||||
void _handleDragUpdate(double delta) {
|
||||
@ -337,7 +318,7 @@ class ScrollableViewportState extends ScrollableState<ScrollableViewport> {
|
||||
});
|
||||
}
|
||||
void _updateScrollBehaviour() {
|
||||
// if you don't call this from build() or syncConstructorArguments(), you must call it from setState().
|
||||
// if you don't call this from build(), you must call it from setState().
|
||||
scrollTo(scrollBehavior.updateExtents(
|
||||
contentExtent: _childSize,
|
||||
containerExtent: _viewportSize,
|
||||
|
@ -2,10 +2,10 @@
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
|
||||
import 'package:sky/animation.dart';
|
||||
import 'package:sky/painting.dart';
|
||||
import 'package:sky/gestures.dart';
|
||||
import 'package:sky/material.dart';
|
||||
import 'package:sky/painting.dart';
|
||||
import 'package:sky/src/widgets/animated_component.dart';
|
||||
import 'package:sky/src/widgets/basic.dart';
|
||||
import 'package:sky/src/widgets/framework.dart';
|
||||
@ -28,7 +28,7 @@ class SnackBarAction extends StatelessComponent {
|
||||
}
|
||||
|
||||
final String label;
|
||||
final Function onPressed;
|
||||
final GestureTapCallback onPressed;
|
||||
|
||||
Widget build(BuildContext) {
|
||||
return new GestureDetector(
|
||||
@ -49,7 +49,7 @@ class SnackBar extends AnimatedComponent {
|
||||
this.actions,
|
||||
bool showing,
|
||||
this.onDismissed
|
||||
}) : super(key: key, direction: showing ? Direction.forward : Direction.reverse, duration: _kSlideInDuration) {
|
||||
}) : super(key: key, direction: showing ? AnimationDirection.forward : AnimationDirection.reverse, duration: _kSlideInDuration) {
|
||||
assert(content != null);
|
||||
}
|
||||
|
||||
|
60
packages/flutter/lib/src/widgets/statistics_overlay.dart
Normal file
60
packages/flutter/lib/src/widgets/statistics_overlay.dart
Normal file
@ -0,0 +1,60 @@
|
||||
// Copyright 2015 The Chromium Authors. All rights reserved.
|
||||
// Use of this source code is governed by a BSD-style license that can be
|
||||
// found in the LICENSE file.
|
||||
|
||||
import 'package:sky/src/widgets/framework.dart';
|
||||
import 'package:sky/src/rendering/statistics_box.dart';
|
||||
|
||||
/// The options that control whether the statistics overlay displays certain
|
||||
/// aspects of the compositor
|
||||
enum StatisticsOption {
|
||||
/// Display the frame time and FPS of the last frame rendered. This field is
|
||||
/// updated every frame.
|
||||
///
|
||||
/// This is the time spent by the rasterizer as it tries
|
||||
/// to convert the layer tree obtained from the widgets into OpenGL commands
|
||||
/// and tries to flush them onto the screen. When the total time taken by this
|
||||
/// step exceeds the frame slice, a frame is lost.
|
||||
displayRasterizerStatistics,
|
||||
/// Display the rasterizer frame times as they change over a set period of
|
||||
/// time in the form of a graph. The y axis of the graph denotes the total
|
||||
/// time spent by the rasterizer as a fraction of the total frame slice. When
|
||||
/// the bar turns red, a frame is lost.
|
||||
visualizeRasterizerStatistics,
|
||||
/// Display the frame time and FPS at which the interface can construct a
|
||||
/// layer tree for the rasterizer (whose behavior is described above) to
|
||||
/// consume.
|
||||
///
|
||||
/// This involves all layout, animations, etc. When the total time taken by
|
||||
/// this step exceeds the frame slice, a frame is lost.
|
||||
displayEngineStatistics,
|
||||
/// Display the engine frame times as they change over a set period of time
|
||||
/// in the form of a graph. The y axis of the graph denotes the total time
|
||||
/// spent by the eninge as a fraction of the total frame slice. When the bar
|
||||
/// turns red, a frame is lost.
|
||||
visualizeEngineStatistics,
|
||||
}
|
||||
|
||||
class StatisticsOverlay extends LeafRenderObjectWidget {
|
||||
|
||||
/// Create a statistics overlay that only displays specific statistics. The
|
||||
/// mask is created by shifting 1 by the index of the specific StatisticOption
|
||||
/// to enable.
|
||||
StatisticsOverlay({ this.optionsMask, Key key }) : super(key: key);
|
||||
|
||||
/// Create a statistics overaly that displays all available statistics
|
||||
StatisticsOverlay.allEnabled({ Key key }) : super(key: key), optionsMask = (
|
||||
1 << StatisticsOption.displayRasterizerStatistics.index |
|
||||
1 << StatisticsOption.visualizeRasterizerStatistics.index |
|
||||
1 << StatisticsOption.displayEngineStatistics.index |
|
||||
1 << StatisticsOption.visualizeEngineStatistics.index
|
||||
);
|
||||
|
||||
final int optionsMask;
|
||||
|
||||
StatisticsBox createRenderObject() => new StatisticsBox(optionsMask: optionsMask);
|
||||
|
||||
void updateRenderObject(StatisticsBox renderObject, RenderObjectWidget oldWidget) {
|
||||
renderObject.optionsMask = optionsMask;
|
||||
}
|
||||
}
|
@ -7,12 +7,12 @@ import 'dart:sky' as sky;
|
||||
|
||||
import 'package:newton/newton.dart';
|
||||
import 'package:sky/animation.dart';
|
||||
import 'package:sky/gestures.dart';
|
||||
import 'package:sky/material.dart';
|
||||
import 'package:sky/painting.dart';
|
||||
import 'package:sky/rendering.dart';
|
||||
import 'package:sky/material.dart';
|
||||
import 'package:sky/src/widgets/basic.dart';
|
||||
import 'package:sky/src/widgets/framework.dart';
|
||||
import 'package:sky/src/widgets/gesture_detector.dart';
|
||||
import 'package:sky/src/widgets/icon.dart';
|
||||
import 'package:sky/src/widgets/ink_well.dart';
|
||||
import 'package:sky/src/widgets/scrollable.dart';
|
||||
@ -307,6 +307,7 @@ class TabLabel {
|
||||
class Tab extends StatelessComponent {
|
||||
Tab({
|
||||
Key key,
|
||||
this.onSelected,
|
||||
this.label,
|
||||
this.color,
|
||||
this.selected: false,
|
||||
@ -315,6 +316,7 @@ class Tab extends StatelessComponent {
|
||||
assert(label.text != null || label.icon != null);
|
||||
}
|
||||
|
||||
final GestureTapCallback onSelected;
|
||||
final TabLabel label;
|
||||
final Color color;
|
||||
final bool selected;
|
||||
@ -359,7 +361,10 @@ class Tab extends StatelessComponent {
|
||||
padding: _kTabLabelPadding
|
||||
);
|
||||
|
||||
return new InkWell(child: centeredLabel);
|
||||
return new InkWell(
|
||||
onTap: onSelected,
|
||||
child: centeredLabel
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -403,16 +408,16 @@ class TabBar extends Scrollable {
|
||||
class TabBarState extends ScrollableState<TabBar> {
|
||||
void initState() {
|
||||
super.initState();
|
||||
_indicatorAnimation = new ValueAnimation<Rect>()
|
||||
_indicatorAnimation = new ValuePerformance<Rect>()
|
||||
..duration = _kTabBarScroll
|
||||
..variable = new AnimatedRect(null, curve: ease);
|
||||
..variable = new AnimatedRectValue(null, curve: ease);
|
||||
scrollBehavior.isScrollable = config.isScrollable;
|
||||
}
|
||||
|
||||
Size _tabBarSize;
|
||||
Size _viewportSize = Size.zero;
|
||||
List<double> _tabWidths;
|
||||
ValueAnimation<Rect> _indicatorAnimation;
|
||||
ValuePerformance<Rect> _indicatorAnimation;
|
||||
|
||||
void didUpdateConfig(TabBar oldConfig) {
|
||||
super.didUpdateConfig(oldConfig);
|
||||
@ -420,7 +425,7 @@ class TabBarState extends ScrollableState<TabBar> {
|
||||
scrollTo(0.0);
|
||||
}
|
||||
|
||||
AnimatedRect get _indicatorRect => _indicatorAnimation.variable;
|
||||
AnimatedRectValue get _indicatorRect => _indicatorAnimation.variable;
|
||||
|
||||
void _startIndicatorAnimation(int fromTabIndex, int toTabIndex) {
|
||||
_indicatorRect
|
||||
@ -458,7 +463,7 @@ class TabBarState extends ScrollableState<TabBar> {
|
||||
.clamp(scrollBehavior.minScrollOffset, scrollBehavior.maxScrollOffset);
|
||||
}
|
||||
|
||||
void _handleTap(int tabIndex) {
|
||||
void _handleTabSelected(int tabIndex) {
|
||||
if (tabIndex != config.selectedIndex) {
|
||||
if (_tabWidths != null) {
|
||||
if (config.isScrollable)
|
||||
@ -471,14 +476,12 @@ class TabBarState extends ScrollableState<TabBar> {
|
||||
}
|
||||
|
||||
Widget _toTab(TabLabel label, int tabIndex, Color color, Color selectedColor) {
|
||||
return new GestureDetector(
|
||||
onTap: () => _handleTap(tabIndex),
|
||||
child: new Tab(
|
||||
label: label,
|
||||
color: color,
|
||||
selected: tabIndex == config.selectedIndex,
|
||||
selectedColor: selectedColor
|
||||
)
|
||||
return new Tab(
|
||||
onSelected: () => _handleTabSelected(tabIndex),
|
||||
label: label,
|
||||
color: color,
|
||||
selected: tabIndex == config.selectedIndex,
|
||||
selectedColor: selectedColor
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -7,7 +7,7 @@ import 'package:sky/src/widgets/basic.dart';
|
||||
import 'package:sky/src/widgets/framework.dart';
|
||||
import 'package:vector_math/vector_math_64.dart';
|
||||
|
||||
export 'package:sky/animation.dart' show Direction;
|
||||
export 'package:sky/animation.dart' show AnimationDirection;
|
||||
|
||||
abstract class TransitionComponent extends StatefulComponent {
|
||||
TransitionComponent({
|
||||
@ -17,7 +17,7 @@ abstract class TransitionComponent extends StatefulComponent {
|
||||
assert(performance != null);
|
||||
}
|
||||
|
||||
final WatchableAnimationPerformance performance;
|
||||
final PerformanceView performance;
|
||||
|
||||
Widget build(BuildContext context);
|
||||
|
||||
@ -57,7 +57,7 @@ abstract class TransitionWithChild extends TransitionComponent {
|
||||
TransitionWithChild({
|
||||
Key key,
|
||||
this.child,
|
||||
WatchableAnimationPerformance performance
|
||||
PerformanceView performance
|
||||
}) : super(key: key, performance: performance);
|
||||
|
||||
final Widget child;
|
||||
@ -71,7 +71,7 @@ class SlideTransition extends TransitionWithChild {
|
||||
SlideTransition({
|
||||
Key key,
|
||||
this.position,
|
||||
WatchableAnimationPerformance performance,
|
||||
PerformanceView performance,
|
||||
Widget child
|
||||
}) : super(key: key,
|
||||
performance: performance,
|
||||
@ -91,7 +91,7 @@ class FadeTransition extends TransitionWithChild {
|
||||
FadeTransition({
|
||||
Key key,
|
||||
this.opacity,
|
||||
WatchableAnimationPerformance performance,
|
||||
PerformanceView performance,
|
||||
Widget child
|
||||
}) : super(key: key,
|
||||
performance: performance,
|
||||
@ -109,7 +109,7 @@ class ColorTransition extends TransitionWithChild {
|
||||
ColorTransition({
|
||||
Key key,
|
||||
this.color,
|
||||
WatchableAnimationPerformance performance,
|
||||
PerformanceView performance,
|
||||
Widget child
|
||||
}) : super(key: key,
|
||||
performance: performance,
|
||||
@ -131,7 +131,7 @@ class SquashTransition extends TransitionWithChild {
|
||||
Key key,
|
||||
this.width,
|
||||
this.height,
|
||||
WatchableAnimationPerformance performance,
|
||||
PerformanceView performance,
|
||||
Widget child
|
||||
}) : super(key: key,
|
||||
performance: performance,
|
||||
@ -156,7 +156,7 @@ class BuilderTransition extends TransitionComponent {
|
||||
Key key,
|
||||
this.variables,
|
||||
this.builder,
|
||||
WatchableAnimationPerformance performance
|
||||
PerformanceView performance
|
||||
}) : super(key: key,
|
||||
performance: performance);
|
||||
|
||||
|
@ -6,6 +6,7 @@
|
||||
library widgets;
|
||||
|
||||
export 'src/widgets/animated_component.dart';
|
||||
export 'src/widgets/animated_container.dart';
|
||||
export 'src/widgets/app.dart';
|
||||
export 'src/widgets/basic.dart';
|
||||
export 'src/widgets/binding.dart';
|
||||
@ -43,6 +44,7 @@ export 'src/widgets/radio.dart';
|
||||
export 'src/widgets/raised_button.dart';
|
||||
export 'src/widgets/scaffold.dart';
|
||||
export 'src/widgets/scrollable.dart';
|
||||
export 'src/widgets/statistics_overlay.dart';
|
||||
export 'src/widgets/snack_bar.dart';
|
||||
export 'src/widgets/switch.dart';
|
||||
export 'src/widgets/tabs.dart';
|
||||
|
@ -131,6 +131,19 @@ class Node {
|
||||
|
||||
void set rotation(double rotation) {
|
||||
assert(rotation != null);
|
||||
|
||||
if (_physicsBody != null && parent is PhysicsNode) {
|
||||
PhysicsNode physicsNode = parent;
|
||||
physicsNode._updateRotation(this.physicsBody, rotation);
|
||||
return;
|
||||
}
|
||||
|
||||
_rotation = rotation;
|
||||
invalidateTransformMatrix();
|
||||
}
|
||||
|
||||
void _setRotationFromPhysics(double rotation) {
|
||||
assert(rotation != null);
|
||||
_rotation = rotation;
|
||||
invalidateTransformMatrix();
|
||||
}
|
||||
@ -142,6 +155,19 @@ class Node {
|
||||
|
||||
void set position(Point position) {
|
||||
assert(position != null);
|
||||
|
||||
if (_physicsBody != null && parent is PhysicsNode) {
|
||||
PhysicsNode physicsNode = parent;
|
||||
physicsNode._updatePosition(this.physicsBody, position);
|
||||
return;
|
||||
}
|
||||
|
||||
_position = position;
|
||||
invalidateTransformMatrix();
|
||||
}
|
||||
|
||||
void _setPositionFromPhysics(Point position) {
|
||||
assert(position != null);
|
||||
_position = position;
|
||||
invalidateTransformMatrix();
|
||||
}
|
||||
@ -609,4 +635,24 @@ class Node {
|
||||
bool handleEvent(SpriteBoxEvent event) {
|
||||
return false;
|
||||
}
|
||||
|
||||
// Physics
|
||||
|
||||
PhysicsBody _physicsBody;
|
||||
|
||||
PhysicsBody get physicsBody => _physicsBody;
|
||||
|
||||
set physicsBody(PhysicsBody physicsBody) {
|
||||
if (parent != null) {
|
||||
assert(parent is PhysicsNode);
|
||||
|
||||
if (physicsBody == null) {
|
||||
physicsBody._detach();
|
||||
} else {
|
||||
physicsBody._attach(parent, this);
|
||||
}
|
||||
}
|
||||
|
||||
_physicsBody = physicsBody;
|
||||
}
|
||||
}
|
||||
|
138
packages/flutter_sprites/lib/physics_body.dart
Normal file
138
packages/flutter_sprites/lib/physics_body.dart
Normal file
@ -0,0 +1,138 @@
|
||||
part of skysprites;
|
||||
|
||||
enum PhysicsBodyType {
|
||||
static,
|
||||
dynamic
|
||||
}
|
||||
|
||||
class PhysicsBody {
|
||||
PhysicsBody(this.shape, {
|
||||
this.tag: null,
|
||||
this.type: PhysicsBodyType.dynamic,
|
||||
this.density: 1.0,
|
||||
this.friction: 0.0,
|
||||
this.restitution: 0.0,
|
||||
this.isSensor: false,
|
||||
this.linearVelocity: Offset.zero,
|
||||
this.angularVelocity: 0.0,
|
||||
this.linearDampening: 0.0,
|
||||
this.angularDampening: 0.0,
|
||||
this.allowSleep: true,
|
||||
this.awake: true,
|
||||
this.fixedRotation: false,
|
||||
this.bullet: false,
|
||||
this.active: true,
|
||||
this.gravityScale: 1.0
|
||||
});
|
||||
|
||||
Object tag;
|
||||
|
||||
PhysicsShape shape;
|
||||
|
||||
PhysicsBodyType type;
|
||||
|
||||
double density;
|
||||
double friction;
|
||||
double restitution;
|
||||
bool isSensor;
|
||||
|
||||
Offset linearVelocity;
|
||||
double angularVelocity;
|
||||
|
||||
double linearDampening;
|
||||
|
||||
double angularDampening;
|
||||
|
||||
bool allowSleep;
|
||||
|
||||
bool awake;
|
||||
|
||||
bool fixedRotation;
|
||||
|
||||
bool bullet;
|
||||
|
||||
bool active;
|
||||
|
||||
double gravityScale;
|
||||
|
||||
PhysicsNode _physicsNode;
|
||||
Node _node;
|
||||
|
||||
box2d.Body _body;
|
||||
|
||||
bool _attached = false;
|
||||
|
||||
void _attach(PhysicsNode physicsNode, Node node) {
|
||||
assert(_attached == false);
|
||||
|
||||
// Create BodyDef
|
||||
box2d.BodyDef bodyDef = new box2d.BodyDef();
|
||||
bodyDef.linearVelocity = new Vector2(linearVelocity.dx, linearVelocity.dy);
|
||||
bodyDef.angularVelocity = angularVelocity;
|
||||
bodyDef.linearDamping = linearDampening;
|
||||
bodyDef.angularDamping = angularDampening;
|
||||
bodyDef.allowSleep = allowSleep;
|
||||
bodyDef.awake = awake;
|
||||
bodyDef.fixedRotation = fixedRotation;
|
||||
bodyDef.bullet = bullet;
|
||||
bodyDef.active = active;
|
||||
bodyDef.gravityScale = gravityScale;
|
||||
if (type == PhysicsBodyType.dynamic)
|
||||
bodyDef.type = box2d.BodyType.DYNAMIC;
|
||||
else
|
||||
bodyDef.type = box2d.BodyType.STATIC;
|
||||
|
||||
double conv = physicsNode.b2WorldToNodeConversionFactor;
|
||||
bodyDef.position = new Vector2(node.position.x / conv, node.position.y / conv);
|
||||
bodyDef.angle = radians(node.rotation);
|
||||
|
||||
// Create Body
|
||||
_body = physicsNode.b2World.createBody(bodyDef);
|
||||
|
||||
// Create FixtureDef
|
||||
box2d.FixtureDef fixtureDef = new box2d.FixtureDef();
|
||||
fixtureDef.friction = friction;
|
||||
fixtureDef.restitution = restitution;
|
||||
fixtureDef.density = density;
|
||||
fixtureDef.isSensor = isSensor;
|
||||
|
||||
// Get shapes
|
||||
List<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);
|
||||
}
|
||||
}
|
||||
}
|
303
packages/flutter_sprites/lib/physics_node.dart
Normal file
303
packages/flutter_sprites/lib/physics_node.dart
Normal file
@ -0,0 +1,303 @@
|
||||
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);
|
||||
}
|
||||
}
|
65
packages/flutter_sprites/lib/physics_shape.dart
Normal file
65
packages/flutter_sprites/lib/physics_shape.dart
Normal file
@ -0,0 +1,65 @@
|
||||
part of skysprites;
|
||||
|
||||
abstract class PhysicsShape {
|
||||
|
||||
box2d.Shape _b2Shape;
|
||||
|
||||
Object userObject;
|
||||
|
||||
box2d.Shape getB2Shape(PhysicsNode node) {
|
||||
if (_b2Shape == null) {
|
||||
_b2Shape = _createB2Shape(node);
|
||||
}
|
||||
return _b2Shape;
|
||||
}
|
||||
|
||||
box2d.Shape _createB2Shape(PhysicsNode node);
|
||||
}
|
||||
|
||||
class PhysicsShapeCircle extends PhysicsShape {
|
||||
PhysicsShapeCircle(this.point, this.radius);
|
||||
|
||||
final Point point;
|
||||
final double radius;
|
||||
|
||||
box2d.Shape _createB2Shape(PhysicsNode node) {
|
||||
box2d.CircleShape shape = new box2d.CircleShape();
|
||||
shape.p.x = point.x / node.b2WorldToNodeConversionFactor;
|
||||
shape.p.y = point.y / node.b2WorldToNodeConversionFactor;
|
||||
shape.radius = radius / node.b2WorldToNodeConversionFactor;
|
||||
return shape;
|
||||
}
|
||||
}
|
||||
|
||||
class PhysicsShapePolygon extends PhysicsShape {
|
||||
|
||||
PhysicsShapePolygon(this.points);
|
||||
|
||||
final List<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;
|
||||
import 'dart:typed_data';
|
||||
import 'dart:sky' as sky;
|
||||
|
||||
import 'package:box2d/box2d.dart' as box2d;
|
||||
import 'package:mojo/core.dart';
|
||||
import 'package:sky_services/media/media.mojom.dart';
|
||||
import 'package:sky/animation.dart';
|
||||
@ -31,6 +32,9 @@ part 'node.dart';
|
||||
part 'node3d.dart';
|
||||
part 'node_with_size.dart';
|
||||
part 'particle_system.dart';
|
||||
part 'physics_body.dart';
|
||||
part 'physics_node.dart';
|
||||
part 'physics_shape.dart';
|
||||
part 'sound.dart';
|
||||
part 'sound_manager.dart';
|
||||
part 'sprite.dart';
|
||||
|
@ -47,7 +47,7 @@ class SpriteBox extends RenderBox {
|
||||
}
|
||||
|
||||
// Tracking of frame rate and updates
|
||||
double _lastTimeStamp;
|
||||
Duration _lastTimeStamp;
|
||||
double _frameRate = 0.0;
|
||||
|
||||
double get frameRate => _frameRate;
|
||||
@ -76,6 +76,8 @@ class SpriteBox extends RenderBox {
|
||||
|
||||
List<Node> _constrainedNodes;
|
||||
|
||||
List<PhysicsNode> _physicsNodes;
|
||||
|
||||
Rect _visibleArea;
|
||||
|
||||
Rect get visibleArea {
|
||||
@ -84,6 +86,8 @@ class SpriteBox extends RenderBox {
|
||||
return _visibleArea;
|
||||
}
|
||||
|
||||
bool _initialized = false;
|
||||
|
||||
// Setup
|
||||
|
||||
/// Creates a new SpriteBox with a node as its content, by default uses letterboxing.
|
||||
@ -134,19 +138,22 @@ class SpriteBox extends RenderBox {
|
||||
size = constraints.biggest;
|
||||
_invalidateTransformMatrix();
|
||||
_callSpriteBoxPerformedLayout(_rootNode);
|
||||
_initialized = true;
|
||||
}
|
||||
|
||||
// Adding and removing nodes
|
||||
|
||||
_registerNode(Node node) {
|
||||
void _registerNode(Node node) {
|
||||
_actionControllers = null;
|
||||
_eventTargets = null;
|
||||
_physicsNodes = null;
|
||||
if (node == null || node.constraints != null) _constrainedNodes = null;
|
||||
}
|
||||
|
||||
_deregisterNode(Node node) {
|
||||
void _deregisterNode(Node node) {
|
||||
_actionControllers = null;
|
||||
_eventTargets = null;
|
||||
_physicsNodes = null;
|
||||
if (node == null || node.constraints != null) _constrainedNodes = null;
|
||||
}
|
||||
|
||||
@ -345,21 +352,25 @@ class SpriteBox extends RenderBox {
|
||||
scheduler.requestAnimationFrame(_tick);
|
||||
}
|
||||
|
||||
void _tick(double timeStamp) {
|
||||
void _tick(Duration timeStamp) {
|
||||
if (!attached)
|
||||
return;
|
||||
|
||||
// Calculate delta and frame rate
|
||||
if (_lastTimeStamp == null) _lastTimeStamp = timeStamp;
|
||||
double delta = (timeStamp - _lastTimeStamp) / 1000;
|
||||
if (_lastTimeStamp == null)
|
||||
_lastTimeStamp = timeStamp;
|
||||
double delta = (timeStamp - _lastTimeStamp).inMicroseconds.toDouble() / Duration.MICROSECONDS_PER_SECOND;
|
||||
_lastTimeStamp = timeStamp;
|
||||
|
||||
_frameRate = 1.0/delta;
|
||||
|
||||
_callConstraintsPreUpdate(delta);
|
||||
_runActions(delta);
|
||||
_callUpdate(_rootNode, delta);
|
||||
_callConstraintsConstrain(delta);
|
||||
if (_initialized) {
|
||||
_callConstraintsPreUpdate(delta);
|
||||
_runActions(delta);
|
||||
_callUpdate(_rootNode, delta);
|
||||
_callStepPhysics(delta);
|
||||
_callConstraintsConstrain(delta);
|
||||
}
|
||||
|
||||
// Schedule next update
|
||||
_scheduleTick();
|
||||
@ -370,20 +381,26 @@ class SpriteBox extends RenderBox {
|
||||
|
||||
void _runActions(double dt) {
|
||||
if (_actionControllers == null) {
|
||||
_actionControllers = [];
|
||||
_addActionControllers(_rootNode, _actionControllers);
|
||||
_rebuildActionControllersAndPhysicsNodes();
|
||||
}
|
||||
for (ActionController actions in _actionControllers) {
|
||||
actions.step(dt);
|
||||
}
|
||||
}
|
||||
|
||||
void _addActionControllers(Node node, List<ActionController> controllers) {
|
||||
if (node._actions != null) controllers.add(node._actions);
|
||||
void _rebuildActionControllersAndPhysicsNodes() {
|
||||
_actionControllers = [];
|
||||
_physicsNodes = [];
|
||||
_addActionControllersAndPhysicsNodes(_rootNode);
|
||||
}
|
||||
|
||||
void _addActionControllersAndPhysicsNodes(Node node) {
|
||||
if (node._actions != null) _actionControllers.add(node._actions);
|
||||
if (node is PhysicsNode) _physicsNodes.add(node);
|
||||
|
||||
for (int i = node.children.length - 1; i >= 0; i--) {
|
||||
Node child = node.children[i];
|
||||
_addActionControllers(child, controllers);
|
||||
_addActionControllersAndPhysicsNodes(child);
|
||||
}
|
||||
}
|
||||
|
||||
@ -397,6 +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) {
|
||||
if (_constrainedNodes == null) {
|
||||
_constrainedNodes = [];
|
||||
|
@ -6,6 +6,7 @@ homepage: http://flutter.io
|
||||
dependencies:
|
||||
sky: ">=0.0.36 < 0.1.0"
|
||||
sky_tools: ">=0.0.10 < 0.1.0"
|
||||
box2d: any
|
||||
dependency_overrides:
|
||||
sky:
|
||||
path: ../sky/packages/sky
|
||||
|
@ -8,18 +8,18 @@ void main() {
|
||||
bool firstCallbackRan = false;
|
||||
bool secondCallbackRan = false;
|
||||
|
||||
void firstCallback(double timeStamp) {
|
||||
void firstCallback(Duration timeStamp) {
|
||||
expect(firstCallbackRan, isFalse);
|
||||
expect(secondCallbackRan, isFalse);
|
||||
expect(timeStamp, equals(16.0));
|
||||
expect(timeStamp.inMilliseconds, equals(16));
|
||||
firstCallbackRan = true;
|
||||
scheduler.cancelAnimationFrame(secondId);
|
||||
}
|
||||
|
||||
void secondCallback(double timeStamp) {
|
||||
void secondCallback(Duration timeStamp) {
|
||||
expect(firstCallbackRan, isTrue);
|
||||
expect(secondCallbackRan, isFalse);
|
||||
expect(timeStamp, equals(16.0));
|
||||
expect(timeStamp.inMilliseconds, equals(16));
|
||||
secondCallbackRan = true;
|
||||
}
|
||||
|
||||
|
48
packages/unit/test/widget/animated_container_test.dart
Normal file
48
packages/unit/test/widget/animated_container_test.dart
Normal file
@ -0,0 +1,48 @@
|
||||
import 'package:sky/rendering.dart';
|
||||
import 'package:sky/widgets.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'widget_tester.dart';
|
||||
|
||||
void main() {
|
||||
test('AnimatedContainer control test', () {
|
||||
testWidgets((WidgetTester tester) {
|
||||
GlobalKey key = new GlobalKey();
|
||||
|
||||
BoxDecoration decorationA = new BoxDecoration(
|
||||
backgroundColor: new Color(0xFF00FF00)
|
||||
);
|
||||
|
||||
BoxDecoration decorationB = new BoxDecoration(
|
||||
backgroundColor: new Color(0xFF0000FF)
|
||||
);
|
||||
|
||||
tester.pumpWidget(
|
||||
new AnimatedContainer(
|
||||
key: key,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
decoration: decorationA
|
||||
)
|
||||
);
|
||||
|
||||
RenderDecoratedBox box = key.currentState.context.findRenderObject();
|
||||
expect(box.decoration.backgroundColor, equals(decorationA.backgroundColor));
|
||||
|
||||
tester.pumpWidget(
|
||||
new AnimatedContainer(
|
||||
key: key,
|
||||
duration: const Duration(milliseconds: 200),
|
||||
decoration: decorationB
|
||||
)
|
||||
);
|
||||
|
||||
expect(key.currentState.context.findRenderObject(), equals(box));
|
||||
expect(box.decoration.backgroundColor, equals(decorationA.backgroundColor));
|
||||
|
||||
tester.pump(const Duration(seconds: 1));
|
||||
|
||||
expect(box.decoration.backgroundColor, equals(decorationB.backgroundColor));
|
||||
|
||||
});
|
||||
});
|
||||
}
|
@ -1,3 +1,4 @@
|
||||
import 'package:sky/rendering.dart';
|
||||
import 'package:sky/widgets.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
@ -44,13 +45,11 @@ Widget widgetBuilder() {
|
||||
);
|
||||
}
|
||||
|
||||
void dismissItem(WidgetTester tester, int item, { DismissDirection gestureDirection }) {
|
||||
void dismissElement(WidgetTester tester, Element itemElement, { DismissDirection gestureDirection }) {
|
||||
assert(itemElement != null);
|
||||
assert(gestureDirection != DismissDirection.horizontal);
|
||||
assert(gestureDirection != DismissDirection.vertical);
|
||||
|
||||
Element itemElement = tester.findText(item.toString());
|
||||
expect(itemElement, isNotNull);
|
||||
|
||||
Point downLocation;
|
||||
Point upLocation;
|
||||
switch(gestureDirection) {
|
||||
@ -84,12 +83,35 @@ void dismissItem(WidgetTester tester, int item, { DismissDirection gestureDirect
|
||||
tester.dispatchEvent(pointer.down(downLocation), downLocation);
|
||||
tester.dispatchEvent(pointer.move(upLocation), downLocation);
|
||||
tester.dispatchEvent(pointer.up(), downLocation);
|
||||
}
|
||||
|
||||
void dismissItem(WidgetTester tester, int item, { DismissDirection gestureDirection }) {
|
||||
assert(gestureDirection != DismissDirection.horizontal);
|
||||
assert(gestureDirection != DismissDirection.vertical);
|
||||
|
||||
Element itemElement = tester.findText(item.toString());
|
||||
expect(itemElement, isNotNull);
|
||||
|
||||
dismissElement(tester, itemElement, gestureDirection: gestureDirection);
|
||||
|
||||
tester.pumpWidget(widgetBuilder()); // start the resize animation
|
||||
tester.pumpWidget(widgetBuilder(), const Duration(seconds: 1)); // finish the resize animation
|
||||
tester.pumpWidget(widgetBuilder(), const Duration(seconds: 1)); // dismiss
|
||||
}
|
||||
|
||||
class Test1215DismissableComponent extends StatelessComponent {
|
||||
Test1215DismissableComponent(this.text);
|
||||
final String text;
|
||||
Widget build(BuildContext context) {
|
||||
return new Dismissable(
|
||||
child: new AspectRatio(
|
||||
aspectRatio: 1.0,
|
||||
child: new Text(this.text)
|
||||
)
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
void main() {
|
||||
test('Horizontal drag triggers dismiss scrollDirection=vertical', () {
|
||||
testWidgets((WidgetTester tester) {
|
||||
@ -230,4 +252,31 @@ void main() {
|
||||
tester.pumpWidget(widgetBuilder());
|
||||
});
|
||||
});
|
||||
|
||||
// This one is for
|
||||
// https://github.com/flutter/engine/issues/1215
|
||||
test('dismissing bottom then top (smoketest)', () {
|
||||
testWidgets((WidgetTester tester) {
|
||||
tester.pumpWidget(new Center(
|
||||
child: new Container(
|
||||
width: 100.0,
|
||||
height: 1000.0,
|
||||
child: new Column([
|
||||
new Test1215DismissableComponent('1'),
|
||||
new Test1215DismissableComponent('2')
|
||||
])
|
||||
)
|
||||
));
|
||||
expect(tester.findText('1'), isNotNull);
|
||||
expect(tester.findText('2'), isNotNull);
|
||||
dismissElement(tester, tester.findText('2'), gestureDirection: DismissDirection.right);
|
||||
tester.pump(new Duration(seconds: 1));
|
||||
expect(tester.findText('1'), isNotNull);
|
||||
expect(tester.findText('2'), isNull);
|
||||
dismissElement(tester, tester.findText('1'), gestureDirection: DismissDirection.right);
|
||||
tester.pump(new Duration(seconds: 1));
|
||||
expect(tester.findText('1'), isNull);
|
||||
expect(tester.findText('2'), isNull);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -13,9 +13,9 @@ void main() {
|
||||
|
||||
tester.pumpWidget(new Navigator(
|
||||
routes: {
|
||||
'/': (NavigatorState navigator, Route route) { return new Column([
|
||||
'/': (RouteArguments args) { return new Column([
|
||||
new Draggable(
|
||||
navigator: navigator,
|
||||
navigator: args.navigator,
|
||||
data: 1,
|
||||
child: new Text('Source'),
|
||||
feedback: new Text('Dragging')
|
||||
|
@ -1,3 +1,4 @@
|
||||
import 'package:sky/rendering.dart';
|
||||
import 'package:sky/widgets.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
@ -29,7 +30,8 @@ void main() {
|
||||
)
|
||||
));
|
||||
expect(detectedSize, equals(const Size(50.0, 25.0)));
|
||||
expect(inner.currentContext.findRenderObject().localToGlobal(Point.origin), equals(const Point(25.0, 37.5)));
|
||||
RenderBox box = inner.currentContext.findRenderObject();
|
||||
expect(box.localToGlobal(Point.origin), equals(const Point(25.0, 37.5)));
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -88,4 +88,38 @@ void main() {
|
||||
tester.pumpWidget(new Container());
|
||||
});
|
||||
});
|
||||
|
||||
test('Pan doesn\'t crash', () {
|
||||
testWidgets((WidgetTester tester) {
|
||||
bool didStartPan = false;
|
||||
Offset panDelta;
|
||||
bool didEndPan = false;
|
||||
|
||||
tester.pumpWidget(
|
||||
new GestureDetector(
|
||||
onPanStart: () {
|
||||
didStartPan = true;
|
||||
},
|
||||
onPanUpdate: (Offset delta) {
|
||||
panDelta = delta;
|
||||
},
|
||||
onPanEnd: (_) {
|
||||
didEndPan = true;
|
||||
},
|
||||
child: new Container()
|
||||
)
|
||||
);
|
||||
|
||||
expect(didStartPan, isFalse);
|
||||
expect(panDelta, isNull);
|
||||
expect(didEndPan, isFalse);
|
||||
|
||||
tester.scrollAt(new Point(10.0, 10.0), new Offset(20.0, 30.0));
|
||||
|
||||
expect(didStartPan, isTrue);
|
||||
expect(panDelta.dx, 20.0);
|
||||
expect(panDelta.dy, 30.0);
|
||||
expect(didEndPan, isTrue);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
@ -49,8 +49,8 @@ void main() {
|
||||
test('Can navigator navigate to and from a stateful component', () {
|
||||
testWidgets((WidgetTester tester) {
|
||||
final Map<String, RouteBuilder> routes = <String, RouteBuilder>{
|
||||
'/': (navigator, route) => new FirstComponent(navigator),
|
||||
'/second': (navigator, route) => new SecondComponent(navigator),
|
||||
'/': (RouteArguments args) => new FirstComponent(args.navigator),
|
||||
'/second': (RouteArguments args) => new SecondComponent(args.navigator),
|
||||
};
|
||||
|
||||
tester.pumpWidget(new Navigator(routes: routes));
|
||||
|
27
packages/unit/test/widget/shader_mask_test.dart
Normal file
27
packages/unit/test/widget/shader_mask_test.dart
Normal file
@ -0,0 +1,27 @@
|
||||
import 'dart:sky' as sky;
|
||||
|
||||
import 'package:sky/painting.dart';
|
||||
import 'package:sky/widgets.dart';
|
||||
import 'package:test/test.dart';
|
||||
|
||||
import 'widget_tester.dart';
|
||||
|
||||
sky.Shader createShader(Rect bounds) {
|
||||
return new LinearGradient(
|
||||
begin: Point.origin,
|
||||
end: new Point(0.0, bounds.height),
|
||||
colors: [const Color(0x00FFFFFF), const Color(0xFFFFFFFF)],
|
||||
stops: [0.1, 0.35]
|
||||
)
|
||||
.createShader();
|
||||
}
|
||||
|
||||
|
||||
void main() {
|
||||
test('Can be constructed', () {
|
||||
testWidgets((WidgetTester tester) {
|
||||
Widget child = new Container(width: 100.0, height: 100.0);
|
||||
tester.pumpWidget(new ShaderMask(child: child, shaderCallback: createShader));
|
||||
});
|
||||
});
|
||||
}
|
@ -145,7 +145,10 @@ class WidgetTester {
|
||||
}
|
||||
|
||||
void scroll(Element element, Offset offset, { int pointer: 1 }) {
|
||||
Point startLocation = getCenter(element);
|
||||
scrollAt(getCenter(element), offset, pointer: pointer);
|
||||
}
|
||||
|
||||
void scrollAt(Point startLocation, Offset offset, { int pointer: 1 }) {
|
||||
Point endLocation = startLocation + offset;
|
||||
TestPointer p = new TestPointer(pointer);
|
||||
// Events for the entire press-drag-release gesture are dispatched
|
||||
|
Loading…
x
Reference in New Issue
Block a user