Commit b7a212f7 authored by Adam Barth's avatar Adam Barth

Merge branch 'flutter'

parents a491bcbc e7c837b4
# 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.
group("examples") {
testonly = true
deps = [
"//examples/fitness",
"//examples/game",
"//examples/mine_digger",
"//examples/stocks",
]
}
Flutter Examples
================
This directory contains several examples of using Flutter. Each of these is an
individual Dart application package.
To run a sample with the `flutter` tool, run `pub get` inside its directory,
then run `flutter start`. (See the
[getting started guide](https://flutter.github.io/getting-started/) to install
the `flutter` tool.)
Available examples include:
- *Hello, world.* The [hello world app](hello_world) is a basic app that shows
the text "hello, world."
- *Stocks.* The [stocks app](stocks) is an example of a typical mobile app
built using Flutter. The app shows a list of all the stocks in the NASDAQ.
- *Widgets.* The [widget apps](widgets) demonstrate a number of Flutter widgets
so you can experiment with them in a simple container. There is no main.dart
in this directory because each file is a standalone sample. To run a
particular file, use `flutter start -t filename.dart`.
name: address_book
version: 0.0.2
material-design-icons:
- name: av/volume_up
- name: communication/email
- name: communication/phone
- name: content/add
- name: image/photo_camera
- name: maps/place
- name: navigation/check
- name: navigation/arrow_back
- name: social/person
// 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:flutter/material.dart';
class Field extends StatelessComponent {
Field({
Key key,
this.inputKey,
this.icon,
this.placeholder
}) : super(key: key);
final GlobalKey inputKey;
final String icon;
final String placeholder;
Widget build(BuildContext context) {
return new Row(<Widget>[
new Padding(
padding: const EdgeDims.symmetric(horizontal: 16.0),
child: new Icon(icon: icon)
),
new Flexible(
child: new Input(
key: inputKey,
placeholder: placeholder
)
)
]
);
}
}
class AddressBookHome extends StatelessComponent {
Widget buildToolBar(BuildContext context) {
return new ToolBar(
left: new IconButton(icon: "navigation/arrow_back"),
right: <Widget>[new IconButton(icon: "navigation/check")]
);
}
Widget buildFloatingActionButton(BuildContext context) {
return new FloatingActionButton(
child: new Icon(icon: 'image/photo_camera'),
backgroundColor: Theme.of(context).accentColor
);
}
static final GlobalKey nameKey = new GlobalKey(label: 'name field');
static final GlobalKey phoneKey = new GlobalKey(label: 'phone field');
static final GlobalKey emailKey = new GlobalKey(label: 'email field');
static final GlobalKey addressKey = new GlobalKey(label: 'address field');
static final GlobalKey ringtoneKey = new GlobalKey(label: 'ringtone field');
static final GlobalKey noteKey = new GlobalKey(label: 'note field');
Widget buildBody(BuildContext context) {
return new Block(<Widget>[
new AspectRatio(
aspectRatio: 16.0 / 9.0,
child: new Container(
decoration: new BoxDecoration(backgroundColor: Colors.purple[300])
)
),
new Field(inputKey: nameKey, icon: "social/person", placeholder: "Name"),
new Field(inputKey: phoneKey, icon: "communication/phone", placeholder: "Phone"),
new Field(inputKey: emailKey, icon: "communication/email", placeholder: "Email"),
new Field(inputKey: addressKey, icon: "maps/place", placeholder: "Address"),
new Field(inputKey: ringtoneKey, icon: "av/volume_up", placeholder: "Ringtone"),
new Field(inputKey: noteKey, icon: "content/add", placeholder: "Add note"),
]);
}
Widget build(BuildContext context) {
return new Scaffold(
toolBar: buildToolBar(context),
body: buildBody(context),
floatingActionButton: buildFloatingActionButton(context)
);
}
}
final ThemeData theme = new ThemeData(
brightness: ThemeBrightness.light,
primarySwatch: Colors.teal,
accentColor: Colors.pinkAccent[100]
);
void main() {
runApp(new MaterialApp(
title: 'Address Book',
theme: theme,
routes: <String, RouteBuilder>{
'/': (RouteArguments args) => new AddressBookHome()
}
));
}
name: address_book
dependencies:
flutter:
'0.0.16'
sky_tools: any
dependency_overrides:
material_design_icons:
path: ../../sky/packages/material_design_icons
flutter:
path: ../../sky/packages/sky
# 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("//sky/build/sky_app.gni")
sky_app("fitness") {
main_dart = "lib/main.dart"
manifest = "flutter.yaml"
if (is_android) {
apk_name = "Fitness"
deps = [
"//examples/fitness/apk:resources",
]
} else if (is_mac) {
info_plist = "mac/Info.plist"
xibs = [ "mac/sky_mac.xib" ]
}
}
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android" package="org.domokit.fitness" android:versionCode="4" android:versionName="0.0.4">
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="21" />
<uses-permission android:name="android.permission.INTERNET" />
<application android:icon="@mipmap/ic_launcher" android:label="Fitness" android:name="org.domokit.sky.shell.SkyApplication">
<activity android:configChanges="orientation|keyboardHidden|keyboard|screenSize" android:hardwareAccelerated="true" android:launchMode="singleTask" android:name="org.domokit.sky.shell.SkyActivity" android:theme="@android:style/Theme.Black.NoTitleBar">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service
android:name="org.domokit.sky.shell.UpdateService"
android:exported="false"
android:process=":remote"/>
</application>
</manifest>
# 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.
assert(is_android)
import("//build/config/android/config.gni")
import("//build/config/android/rules.gni")
android_resources("resources") {
resource_dirs = [ "res" ]
android_manifest = "AndroidManifest.xml"
}
Icon image comes from:
https://openclipart.org/detail/22309/apple-icon
and is public domain.
Icon resources were generated using:
http://romannurik.github.io/AndroidAssetStudio/icons-launcher.html
with settings:
http://romannurik.github.io/AndroidAssetStudio/icons-launcher.html#foreground.type=image&foreground.space.trim=1&foreground.space.pad=0&foreColor=607d8b%2C0&crop=0&backgroundShape=none&backColor=ffffff%2C100&effects=none
which produces art under CC 3.0:
http://creativecommons.org/licenses/by/3.0/
Still barely works
Fixed crash when entering an invalid number
Made date list look less-awful.
Adds very basic charting support
Now supports setting goal weight.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
name: fitness
version: 0.0.1
update-url: http://localhost:9888/
material-design-icons:
- name: action/assessment
- name: action/help
- name: action/settings
- name: action/view_list
- name: av/stop
- name: content/add
- name: maps/directions_run
- name: navigation/arrow_back
- name: navigation/close
- name: navigation/menu
- name: navigation/more_vert
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<!--
This executable name must match the name of the app provided to the
ios_app GN template
-->
<key>CFBundleExecutable</key>
<string>fitness_app</string>
<key>CFBundleDisplayName</key>
<string>Fitness</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>CFBundleIdentifier</key>
<string>com.google.SkyFitness</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>game_app</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>DTPlatformName</key>
<string>iphonesimulator</string>
<key>DTSDKName</key>
<string>iphonesimulator8.3</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>MinimumOSVersion</key>
<string>7.0</string>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>iPhoneSimulator</string>
</array>
</dict>
</plist>
// 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.
// Forked from https://github.com/dart-lang/sdk/blob/master/samples-dev/swarm/swarm_ui_lib/util/DateUtils.dart
class DateUtils {
static const WEEKDAYS = const ['Monday', 'Tuesday', 'Wednesday', 'Thursday',
'Friday', 'Saturday', 'Sunday'];
static const YESTERDAY = 'Yesterday';
static const MS_IN_WEEK = DateTime.DAYS_PER_WEEK * Duration.MILLISECONDS_PER_DAY;
// TODO(jmesserly): locale specific date format
static String _twoDigits(int n) {
if (n >= 10)
return '$n';
return '0$n';
}
/// Formats a time in H:MM A format
static String toHourMinutesString(Duration duration) {
assert(duration.inDays == 0);
int hours = duration.inHours;
String a;
if (hours >= 12) {
a = 'pm';
if (hours != 12)
hours -= 12;
} else {
a = 'am';
if (hours == 0)
hours += 12;
}
String twoDigits(int n) {
if (n >= 10)
return '$n';
return '0$n';
}
String mm = twoDigits(duration.inMinutes.remainder(Duration.MINUTES_PER_HOUR));
return '$hours:$mm $a';
}
/// A date/time formatter that takes into account the current date/time:
/// - if it's from today, just show the time
/// - if it's from yesterday, just show 'Yesterday'
/// - if it's from the same week, just show the weekday
/// - otherwise, show just the date
static String toRecentTimeString(DateTime then) {
bool datesAreEqual(DateTime d1, DateTime d2) {
return (d1.year == d2.year) &&
(d1.month == d2.month) &&
(d1.day == d2.day);
}
final now = new DateTime.now();
if (datesAreEqual(then, now)) {
return toHourMinutesString(new Duration(
days: 0,
hours: then.hour,
minutes: then.minute,
seconds: then.second,
milliseconds: then.millisecond)
);
}
final today = new DateTime(now.year, now.month, now.day, 0, 0, 0, 0);
Duration delta = today.difference(then);
if (delta.inMilliseconds < Duration.MILLISECONDS_PER_DAY) {
return YESTERDAY;
} else if (delta.inMilliseconds < MS_IN_WEEK) {
return WEEKDAYS[then.weekday];
} else {
String twoDigitMonth = _twoDigits(then.month);
String twoDigitDay = _twoDigits(then.day);
return '${then.year}-$twoDigitMonth-$twoDigitDay';
}
}
static String toDateString(DateTime then) {
// TODO(jmesserly): locale specific date format
String twoDigitMonth = _twoDigits(then.month);
String twoDigitDay = _twoDigits(then.day);
return '${then.year}-$twoDigitMonth-$twoDigitDay';
}
}
// 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.
part of fitness;
class FitnessItemList extends StatelessComponent {
FitnessItemList({ Key key, this.items, this.onDismissed }) : super(key: key) {
assert(items != null);
assert(onDismissed != null);
}
final List<FitnessItem> items;
final FitnessItemHandler onDismissed;
Widget build(BuildContext context) {
return new ScrollableList<FitnessItem>(
padding: const EdgeDims.all(4.0),
items: items,
itemExtent: kFitnessItemHeight,
itemBuilder: (BuildContext context, FitnessItem item) => item.toRow(onDismissed: onDismissed)
);
}
}
class DialogMenuItem extends StatelessComponent {
DialogMenuItem(this.children, { Key key, this.onPressed }) : super(key: key);
List<Widget> children;
Function onPressed;
Widget build(BuildContext context) {
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)
)
)
);
}
}
class FeedFragment extends StatefulComponent {
FeedFragment({ this.userData, this.onItemCreated, this.onItemDeleted });
final UserData userData;
final FitnessItemHandler onItemCreated;
final FitnessItemHandler onItemDeleted;
FeedFragmentState createState() => new FeedFragmentState();
}
class FeedFragmentState extends State<FeedFragment> {
final GlobalKey<PlaceholderState> _snackBarPlaceholderKey = new GlobalKey<PlaceholderState>();
FitnessMode _fitnessMode = FitnessMode.feed;
void _handleFitnessModeChange(FitnessMode value) {
setState(() {
_fitnessMode = value;
});
Navigator.of(context).pop();
}
void _showDrawer() {
showDrawer(
context: context,
child: new Block(<Widget>[
new DrawerHeader(child: new Text('Fitness')),
new DrawerItem(
icon: 'action/view_list',
onPressed: () => _handleFitnessModeChange(FitnessMode.feed),
selected: _fitnessMode == FitnessMode.feed,
child: new Text('Feed')),
new DrawerItem(
icon: 'action/assessment',
onPressed: () => _handleFitnessModeChange(FitnessMode.chart),
selected: _fitnessMode == FitnessMode.chart,
child: new Text('Chart')),
new DrawerDivider(),
new DrawerItem(
icon: 'action/settings',
onPressed: _handleShowSettings,
child: new Text('Settings')),
new DrawerItem(
icon: 'action/help',
child: new Text('Help & Feedback'))
])
);
}
void _handleShowSettings() {
Navigator.of(context)..pop()
..pushNamed('/settings');
}
// TODO(jackson): We should be localizing
String get fitnessModeTitle {
switch(_fitnessMode) {
case FitnessMode.feed: return "Feed";
case FitnessMode.chart: return "Chart";
}
}
Widget buildToolBar() {
return new ToolBar(
left: new IconButton(
icon: "navigation/menu",
onPressed: _showDrawer),
center: new Text(fitnessModeTitle)
);
}
void _handleItemDismissed(FitnessItem item) {
config.onItemDeleted(item);
showSnackBar(
context: context,
placeholderKey: _snackBarPlaceholderKey,
content: new Text("Item deleted."),
actions: <SnackBarAction>[new SnackBarAction(label: "UNDO", onPressed: () {
config.onItemCreated(item);
Navigator.of(context).pop();
})]
);
}
Widget buildChart() {
double startX;
double endX;
double startY;
double endY;
List<Point> dataSet = new List<Point>();
for (FitnessItem item in config.userData.items) {
if (item is Measurement) {
double x = item.when.millisecondsSinceEpoch.toDouble();
double y = item.weight;
if (startX == null || startX > x)
startX = x;
if (endX == null || endX < x)
endX = x;
if (startY == null || startY > y)
startY = y;
if (endY == null || endY < y)
endY = y;
dataSet.add(new Point(x, y));
}
}
if (config.userData.goalWeight != null && config.userData.goalWeight > 0.0) {
startY = math.min(startY, config.userData.goalWeight);
endY = math.max(endY, config.userData.goalWeight);
}
playfair.ChartData data = new playfair.ChartData(
startX: startX,
startY: startY,
endX: endX,
endY: endY,
dataSet: dataSet,
numHorizontalGridlines: 5,
roundToPlaces: 1,
indicatorLine: config.userData.goalWeight,
indicatorText: "GOAL WEIGHT"
);
return new playfair.Chart(data: data);
}
Widget buildBody() {
TextStyle style = Theme.of(context).text.title;
if (config.userData == null)
return new Container();
if (config.userData.items.length == 0) {
return new Row(
<Widget>[new Text("No data yet.\nAdd some!", style: style)],
justifyContent: FlexJustifyContent.center
);
}
switch (_fitnessMode) {
case FitnessMode.feed:
return new FitnessItemList(
items: config.userData.items.reversed.toList(),
onDismissed: _handleItemDismissed
);
case FitnessMode.chart:
return new Container(
padding: const EdgeDims.all(20.0),
child: buildChart()
);
}
}
void _handleActionButtonPressed() {
showDialog(context: context, child: new AddItemDialog()).then((routeName) {
if (routeName != null)
Navigator.of(context).pushNamed(routeName);
});
}
Widget buildFloatingActionButton() {
switch (_fitnessMode) {
case FitnessMode.feed:
return new FloatingActionButton(
child: new Icon(icon: 'content/add'),
onPressed: _handleActionButtonPressed
);
case FitnessMode.chart:
return null;
}
}
Widget build(BuildContext context) {
return new Scaffold(
toolBar: buildToolBar(),
body: buildBody(),
snackBar: new Placeholder(key: _snackBarPlaceholderKey),
floatingActionButton: buildFloatingActionButton()
);
}
}
class AddItemDialog extends StatefulComponent {
AddItemDialogState createState() => new AddItemDialogState();
}
class AddItemDialogState extends State<AddItemDialog> {
// TODO(jackson): Internationalize
static final Map<String, String> _labels = <String, String>{
'/measurements/new': 'Measure',
'/meals/new': 'Eat',
};
String _addItemRoute = _labels.keys.first;
void _handleAddItemRouteChanged(String routeName) {
setState(() {
_addItemRoute = routeName;
});
}
Widget build(BuildContext context) {
List<Widget> menuItems = <Widget>[];
for (String routeName in _labels.keys) {
menuItems.add(new DialogMenuItem(<Widget>[
new Flexible(child: new Text(_labels[routeName])),
new Radio<String>(value: routeName, groupValue: _addItemRoute, onChanged: _handleAddItemRouteChanged),
], onPressed: () => _handleAddItemRouteChanged(routeName)));
}
return new Dialog(
title: new Text("What are you doing?"),
content: new Block(menuItems),
actions: <Widget>[
new FlatButton(
child: new Text('CANCEL'),
onPressed: () {
Navigator.of(context).pop();
}
),
new FlatButton(
child: new Text('ADD'),
onPressed: () {
Navigator.of(context).pop(_addItemRoute);
}
),
]
);
}
}
// 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.
part of fitness;
typedef void FitnessItemHandler(FitnessItem item);
// TODO(eseidel): This should be a constant on a SingleLineTile class
// https://www.google.com/design/spec/components/lists.html#lists-specs
const double kFitnessItemHeight = 48.0;
abstract class FitnessItem {
FitnessItem.fromJson(Map json) : when = DateTime.parse(json['when']);
FitnessItem({ this.when }) {
assert(when != null);
}
final DateTime when;
Map toJson() => { 'when' : when.toIso8601String() };
// TODO(jackson): Internationalize
String get displayDate => DateUtils.toDateString(when);
FitnessItemRow toRow({ FitnessItemHandler onDismissed });
}
abstract class FitnessItemRow extends StatelessComponent {
FitnessItemRow({ FitnessItem item, this.onDismissed })
: this.item = item,
super(key: new ValueKey<DateTime>(item.when)) {
assert(onDismissed != null);
}
final FitnessItem item;
final FitnessItemHandler onDismissed;
Widget buildContent(BuildContext context);
Widget build(BuildContext context) {
return new Dismissable(
onDismissed: () => onDismissed(item),
child: new Container(
height: kFitnessItemHeight,
// TODO(eseidel): Padding top should be 16px for a single-line tile:
// https://www.google.com/design/spec/components/lists.html#lists-specs
padding: const EdgeDims.all(10.0),
// TODO(eseidel): This line should be drawn by the list as it should
// stay put even when the tile is dismissed!
decoration: new BoxDecoration(
border: new Border(
bottom: new BorderSide(color: Theme.of(context).dividerColor)
)
),
child: buildContent(context)
)
);
}
}
// 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.
part of fitness;
enum FitnessMode { feed, chart }
enum BackupMode { enabled, disabled }
// 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.
library fitness;
import 'package:playfair/playfair.dart' as playfair;
import 'package:flutter/material.dart';
import 'package:flutter/painting.dart';
import 'user_data.dart';
import 'date_utils.dart';
import 'dart:async';
import 'dart:math' as math;
part 'feed.dart';
part 'fitness_item.dart';
part 'fitness_types.dart';
part 'meal.dart';
part 'measurement.dart';
part 'settings.dart';
abstract class UserData {
BackupMode get backupMode;
double get goalWeight;
List<FitnessItem> get items;
}
class UserDataImpl extends UserData {
UserDataImpl();
List<FitnessItem> _items = <FitnessItem>[];
BackupMode _backupMode;
BackupMode get backupMode => _backupMode;
void set backupMode(BackupMode value) {
_backupMode = value;
}
double _goalWeight;
double get goalWeight => _goalWeight;
void set goalWeight(double value) {
_goalWeight = value;
}
List<FitnessItem> get items => _items;
void sort() {
_items.sort((FitnessItem a, FitnessItem b) => a.when.compareTo(b.when));
}
void add(FitnessItem item) {
_items.add(item);
sort();
}
void remove(FitnessItem item) {
_items.remove(item);
}
Future save() => saveFitnessData(this);
UserDataImpl.fromJson(Map json) {
json['items'].forEach((item) {
_items.add(new Measurement.fromJson(item));
});
try {
_backupMode = BackupMode.values.firstWhere((BackupMode mode) {
return mode.toString() == json['backupMode'];
});
} catch(e) {
print("Failed to load backup mode: $e");
}
_goalWeight = json['goalWeight'];
}
Map toJson() {
Map json = new Map();
json['items'] = _items.map((FitnessItem item) => item.toJson()).toList();
json['backupMode'] = _backupMode.toString();
json['goalWeight'] = _goalWeight;
return json;
}
}
class FitnessApp extends StatefulComponent {
FitnessAppState createState() => new FitnessAppState();
}
class FitnessAppState extends State<FitnessApp> {
UserDataImpl _userData;
void initState() {
super.initState();
loadFitnessData().then((UserData data) {
setState(() => _userData = data);
}).catchError((e) {
print("Failed to load data: $e");
setState(() => _userData = new UserDataImpl());
});
}
void _handleItemCreated(FitnessItem item) {
setState(() {
_userData.add(item);
_userData.save();
});
}
void _handleItemDeleted(FitnessItem item) {
setState(() {
_userData.remove(item);
_userData.save();
});
}
void settingsUpdater({ BackupMode backup, double goalWeight }) {
setState(() {
if (backup != null)
_userData.backupMode = backup;
if (goalWeight != null)
_userData.goalWeight = goalWeight;
_userData.save();
});
}
Widget build(BuildContext) {
return new MaterialApp(
theme: new ThemeData(
brightness: ThemeBrightness.light,
primarySwatch: Colors.indigo,
accentColor: Colors.pinkAccent[200]
),
title: 'Fitness',
routes: <String, RouteBuilder>{
'/': (RouteArguments args) {
return new FeedFragment(
userData: _userData,
onItemCreated: _handleItemCreated,
onItemDeleted: _handleItemDeleted
);
},
'/meals/new': (RouteArguments args) {
return new MealFragment(
onCreated: _handleItemCreated
);
},
'/measurements/new': (RouteArguments args) {
return new MeasurementFragment(
onCreated: _handleItemCreated
);
},
'/settings': (RouteArguments args) {
return new SettingsFragment(
userData: _userData,
updater: settingsUpdater
);
}
}
);
}
}
void main() {
runApp(new FitnessApp());
}
// Copyright 2014 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.
part of fitness;
class Meal extends FitnessItem {
Meal({ DateTime when, this.description }) : super(when: when);
final String description;
FitnessItemRow toRow({ FitnessItemHandler onDismissed }) {
return new MealRow(meal: this, onDismissed: onDismissed);
}
}
class MealRow extends FitnessItemRow {
MealRow({ Meal meal, FitnessItemHandler onDismissed })
: super(item: meal, onDismissed: onDismissed);
Widget buildContent(BuildContext context) {
Meal meal = item;
List<Widget> children = <Widget>[
new Flexible(
child: new Text(
meal.description,
style: const TextStyle(textAlign: TextAlign.right)
)
),
new Flexible(
child: new Text(
meal.displayDate,
style: Theme.of(context).text.caption.copyWith(textAlign: TextAlign.right)
)
)
];
return new Row(
children,
alignItems: FlexAlignItems.baseline,
textBaseline: DefaultTextStyle.of(context).textBaseline
);
}
}
class MealFragment extends StatefulComponent {
MealFragment({ this.onCreated });
FitnessItemHandler onCreated;
MealFragmentState createState() => new MealFragmentState();
}
class MealFragmentState extends State<MealFragment> {
String _description = "";
void _handleSave() {
config.onCreated(new Meal(when: new DateTime.now(), description: _description));
Navigator.of(context).pop();
}
Widget buildToolBar() {
return new ToolBar(
left: new IconButton(
icon: "navigation/close",
onPressed: Navigator.of(context).pop),
center: new Text('New Meal'),
right: <Widget>[
// TODO(abarth): Should this be a FlatButton?
new InkWell(
onTap: _handleSave,
child: new Text('SAVE')
)
]
);
}
void _handleDescriptionChanged(String description) {
setState(() {
_description = description;
});
}
static final GlobalKey descriptionKey = new GlobalKey();
Widget buildBody() {
Meal meal = new Meal(when: new DateTime.now());
return new Block(<Widget>[
new Text(meal.displayDate),
new Input(
key: descriptionKey,
placeholder: 'Describe meal',
onChanged: _handleDescriptionChanged
),
],
padding: const EdgeDims.all(20.0)
);
}
Widget build(BuildContext context) {
return new Scaffold(
toolBar: buildToolBar(),
body: buildBody()
);
}
}
// Copyright 2014 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.
part of fitness;
class Measurement extends FitnessItem {
Measurement({ DateTime when, this.weight }) : super(when: when);
Measurement.fromJson(Map json) : weight = json['weight'], super.fromJson(json);
final double weight;
// TODO(jackson): Internationalize
String get displayWeight => "${weight.toStringAsFixed(1)} lbs";
@override
Map toJson() {
Map json = super.toJson();
json['weight'] = weight;
json['type'] = runtimeType.toString();
return json;
}
FitnessItemRow toRow({ FitnessItemHandler onDismissed }) {
return new MeasurementRow(measurement: this, onDismissed: onDismissed);
}
}
class MeasurementRow extends FitnessItemRow {
MeasurementRow({ Measurement measurement, FitnessItemHandler onDismissed })
: super(item: measurement, onDismissed: onDismissed);
Widget buildContent(BuildContext context) {
Measurement measurement = item;
List<Widget> children = <Widget>[
new Flexible(
child: new Text(
measurement.displayWeight,
style: Theme.of(context).text.subhead
)
),
new Flexible(
child: new Text(
measurement.displayDate,
style: Theme.of(context).text.caption.copyWith(textAlign: TextAlign.right)
)
)
];
return new Row(
children,
alignItems: FlexAlignItems.baseline,
textBaseline: DefaultTextStyle.of(context).textBaseline
);
}
}
class MeasurementDateDialog extends StatefulComponent {
MeasurementDateDialog({ this.previousDate });
final DateTime previousDate;
MeasurementDateDialogState createState() => new MeasurementDateDialogState();
}
class MeasurementDateDialogState extends State<MeasurementDateDialog> {
@override
void initState() {
_selectedDate = config.previousDate;
}
DateTime _selectedDate;
void _handleDateChanged(DateTime value) {
setState(() {
_selectedDate = value;
});
}
Widget build(BuildContext context) {
return new Dialog(
content: new DatePicker(
selectedDate: _selectedDate,
firstDate: new DateTime(2015, 8),
lastDate: new DateTime(2101),
onChanged: _handleDateChanged
),
contentPadding: EdgeDims.zero,
actions: <Widget>[
new FlatButton(
child: new Text('CANCEL'),
onPressed: () {
Navigator.of(context).pop();
}
),
new FlatButton(
child: new Text('OK'),
onPressed: () {
Navigator.of(context).pop(_selectedDate);
}
),
]
);
}
}
class MeasurementFragment extends StatefulComponent {
MeasurementFragment({ this.onCreated });
final FitnessItemHandler onCreated;
MeasurementFragmentState createState() => new MeasurementFragmentState();
}
class MeasurementFragmentState extends State<MeasurementFragment> {
final GlobalKey<PlaceholderState> _snackBarPlaceholderKey = new GlobalKey<PlaceholderState>();
String _weight = "";
DateTime _when = new DateTime.now();
void _handleSave() {
double parsedWeight;
try {
parsedWeight = double.parse(_weight);
} on FormatException catch(e) {
print("Exception $e");
showSnackBar(
context: context,
placeholderKey: _snackBarPlaceholderKey,
content: new Text('Save failed')
);
}
config.onCreated(new Measurement(when: _when, weight: parsedWeight));
Navigator.of(context).pop();
}
Widget buildToolBar() {
return new ToolBar(
left: new IconButton(
icon: "navigation/close",
onPressed: Navigator.of(context).pop),
center: new Text('New Measurement'),
right: <Widget>[
// TODO(abarth): Should this be a FlatButton?
new InkWell(
onTap: _handleSave,
child: new Text('SAVE')
)
]
);
}
void _handleWeightChanged(String weight) {
setState(() {
_weight = weight;
});
}
static final GlobalKey weightKey = new GlobalKey();
Future _handleDatePressed() async {
DateTime value = await showDialog(
context: context,
child: new MeasurementDateDialog(previousDate: _when)
);
if (value != null) {
setState(() {
_when = value;
});
}
}
Widget buildBody(BuildContext context) {
Measurement measurement = new Measurement(when: _when);
// TODO(jackson): Revisit the layout of this pane to be more maintainable
return new Container(
padding: const EdgeDims.all(20.0),
child: new Column(<Widget>[
new GestureDetector(
onTap: _handleDatePressed,
child: new Container(
height: 50.0,
child: new Column(<Widget>[
new Text('Measurement Date'),
new Text(measurement.displayDate, style: Theme.of(context).text.caption),
], alignItems: FlexAlignItems.start)
)
),
new Input(
key: weightKey,
placeholder: 'Enter weight',
keyboardType: KeyboardType.NUMBER,
onChanged: _handleWeightChanged
),
], alignItems: FlexAlignItems.stretch)
);
}
Widget build(BuildContext context) {
return new Scaffold(
toolBar: buildToolBar(),
body: buildBody(context),
snackBar: new Placeholder(key: _snackBarPlaceholderKey)
);
}
}
// 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.
part of fitness;
typedef void SettingsUpdater({
BackupMode backup,
double goalWeight
});
class SettingsFragment extends StatefulComponent {
SettingsFragment({ this.userData, this.updater });
final UserData userData;
final SettingsUpdater updater;
SettingsFragmentState createState() => new SettingsFragmentState();
}
class SettingsFragmentState extends State<SettingsFragment> {
void _handleBackupChanged(bool value) {
assert(config.updater != null);
config.updater(backup: value ? BackupMode.enabled : BackupMode.disabled);
}
Widget buildToolBar() {
return new ToolBar(
left: new IconButton(
icon: "navigation/arrow_back",
onPressed: () => Navigator.of(context).pop()
),
center: new Text('Settings')
);
}
String get goalWeightText {
if (config.userData.goalWeight == null || config.userData.goalWeight == 0.0)
return "None";
return "${config.userData.goalWeight}";
}
static final GlobalKey weightGoalKey = new GlobalKey();
double _goalWeight;
void _handleGoalWeightChanged(String goalWeight) {
// TODO(jackson): Looking for null characters to detect enter key is a hack
if (goalWeight.endsWith("\u{0}")) {
Navigator.of(context).pop(double.parse(goalWeight.replaceAll("\u{0}", "")));
} else {
setState(() {
try {
_goalWeight = double.parse(goalWeight);
} on FormatException {
_goalWeight = 0.0;
}
});
}
}
Future _handleGoalWeightPressed() async {
double goalWeight = await showDialog(
context: context,
child: new Dialog(
title: new Text("Goal Weight"),
content: new Input(
key: weightGoalKey,
placeholder: 'Goal weight in lbs',
keyboardType: KeyboardType.NUMBER,
onChanged: _handleGoalWeightChanged
),
actions: <Widget>[
new FlatButton(
child: new Text('CANCEL'),
onPressed: () {
Navigator.of(context).pop();
}
),
new FlatButton(
child: new Text('SAVE'),
onPressed: () {
Navigator.of(context).pop(_goalWeight);
}
),
]
)
);
config.updater(goalWeight: goalWeight);
}
Widget buildSettingsPane(BuildContext context) {
return new Block(<Widget>[
new DrawerItem(
onPressed: () { _handleBackupChanged(!(config.userData.backupMode == BackupMode.enabled)); },
child: new Row(<Widget>[
new Flexible(child: new Text('Back up data to the cloud')),
new Switch(value: config.userData.backupMode == BackupMode.enabled, onChanged: _handleBackupChanged),
])
),
new DrawerItem(
onPressed: () => _handleGoalWeightPressed(),
child: new Column(<Widget>[
new Text('Goal Weight'),
new Text(goalWeightText, style: Theme.of(context).text.caption),
],
alignItems: FlexAlignItems.start
)
),
],
padding: const EdgeDims.symmetric(vertical: 20.0)
);
}
Widget build(BuildContext context) {
return new Scaffold(
toolBar: buildToolBar(),
body: buildSettingsPane(context)
);
}
}
// 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:convert';
import 'dart:io';
import 'dart:async';
import 'package:path/path.dart' as path;
import 'main.dart';
import 'package:flutter/services.dart';
String cachedDataFilePath = null;
Future<String> dataFilePath() async {
if (cachedDataFilePath == null) {
String dataDir = await getFilesDir();
cachedDataFilePath = path.join(dataDir, 'data.json');
}
return cachedDataFilePath;
}
Future<UserData> loadFitnessData() async {
String dataPath = await dataFilePath();
print("Loading from $dataPath");
JsonDecoder decoder = new JsonDecoder();
Map data = await decoder.convert(await new File(dataPath).readAsString());
return new UserDataImpl.fromJson(data);
}
// Intentionally synchronous for execution just before shutdown.
Future saveFitnessData(UserDataImpl data) async {
String dataPath = await dataFilePath();
print("Saving to $dataPath");
JsonEncoder encoder = new JsonEncoder();
String contents = await encoder.convert(data);
File dataFile = await new File(dataPath).writeAsString(contents);
print("Success! $dataFile");
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>fitness_app</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>org.domokit.sky.fitness</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Asteroids</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
<string>10.6</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2015 The Chromium Authors. All rights reserved.</string>
<key>NSMainNibFile</key>
<string>sky_mac</string>
<key>NSPrincipalClass</key>
<string>SkyApplication</string>
</dict>
</plist>
This diff is collapsed.
name: fitness
dependencies:
flutter:
'0.0.16'
playfair: ^0.0.10
path: ^1.3.6
sky_tools: any
dependency_overrides:
material_design_icons:
path: ../../sky/packages/material_design_icons
flutter:
path: ../../sky/packages/sky
# 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("//sky/build/sky_app.gni")
sky_app("game") {
main_dart = "lib/main.dart"
manifest = "flutter.yaml"
if (is_android) {
apk_name = "Asteroids"
deps = [
"//examples/game/apk:resources",
]
} else if (is_ios) {
info_plist = "ios/Info.plist"
launcher_resources = [
"assets/Icon.png",
"assets/Icon@2x.png",
"ios/LaunchScreen.storyboardc",
]
} else if (is_mac) {
info_plist = "mac/Info.plist"
xibs = [ "mac/sky_mac.xib" ]
}
}
Assets for this game are from Galactic Guardian:
https://github.com/slembcke/GalacticGuardian.spritebuilder
And are used under MIT license:
https://github.com/slembcke/GalacticGuardian.spritebuilder/pull/2
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.domokit.asteroids">
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="21" />
<uses-permission android:name="android.permission.INTERNET"/>
<application android:icon="@mipmap/ic_launcher" android:name="org.domokit.sky.shell.SkyApplication" android:label="Asteroids">
<activity android:name="org.domokit.sky.shell.SkyActivity"
android:launchMode="singleTask"
android:theme="@android:style/Theme.Black.NoTitleBar"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize"
android:hardwareAccelerated="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
# 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.
assert(is_android)
import("//build/config/android/config.gni")
import("//build/config/android/rules.gni")
android_resources("resources") {
resource_dirs = [ "res" ]
android_manifest = "AndroidManifest.xml"
}
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
{"frames": [
{
"filename": "bar_shield.png",
"frame": {"x":2,"y":954,"w":412,"h":100},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":412,"h":100},
"sourceSize": {"w":412,"h":100},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "bar_shield_fill.png",
"frame": {"x":2,"y":1156,"w":320,"h":72},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":320,"h":72},
"sourceSize": {"w":320,"h":72},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "btn_play_down.png",
"frame": {"x":2,"y":484,"w":468,"h":468},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":2,"y":16,"w":468,"h":468},
"sourceSize": {"w":472,"h":484},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "btn_play_up.png",
"frame": {"x":2,"y":2,"w":468,"h":480},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":2,"y":4,"w":468,"h":480},
"sourceSize": {"w":472,"h":484},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "coinboard.png",
"frame": {"x":366,"y":1056,"w":261,"h":100},
"rotated": true,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":261,"h":100},
"sourceSize": {"w":261,"h":100},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "number_0.png",
"frame": {"x":416,"y":954,"w":38,"h":63},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":23,"y":22,"w":38,"h":63},
"sourceSize": {"w":84,"h":107},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "number_1.png",
"frame": {"x":299,"y":1286,"w":16,"h":57},
"rotated": true,
"trimmed": true,
"spriteSourceSize": {"x":45,"y":25,"w":16,"h":57},
"sourceSize": {"w":84,"h":107},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "number_2.png",
"frame": {"x":324,"y":1156,"w":38,"h":63},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":23,"y":22,"w":38,"h":63},
"sourceSize": {"w":84,"h":107},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "number_3.png",
"frame": {"x":197,"y":1230,"w":35,"h":63},
"rotated": true,
"trimmed": true,
"spriteSourceSize": {"x":26,"y":22,"w":35,"h":63},
"sourceSize": {"w":84,"h":107},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "number_4.png",
"frame": {"x":197,"y":1267,"w":38,"h":57},
"rotated": true,
"trimmed": true,
"spriteSourceSize": {"x":23,"y":25,"w":38,"h":57},
"sourceSize": {"w":84,"h":107},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "number_5.png",
"frame": {"x":324,"y":1221,"w":38,"h":63},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":23,"y":22,"w":38,"h":63},
"sourceSize": {"w":84,"h":107},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "number_6.png",
"frame": {"x":2,"y":1230,"w":38,"h":63},
"rotated": true,
"trimmed": true,
"spriteSourceSize": {"x":23,"y":22,"w":38,"h":63},
"sourceSize": {"w":84,"h":107},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "number_7.png",
"frame": {"x":262,"y":1230,"w":35,"h":60},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":26,"y":22,"w":35,"h":60},
"sourceSize": {"w":84,"h":107},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "number_8.png",
"frame": {"x":67,"y":1230,"w":38,"h":63},
"rotated": true,
"trimmed": true,
"spriteSourceSize": {"x":23,"y":22,"w":38,"h":63},
"sourceSize": {"w":84,"h":107},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "number_9.png",
"frame": {"x":132,"y":1230,"w":38,"h":63},
"rotated": true,
"trimmed": true,
"spriteSourceSize": {"x":23,"y":22,"w":38,"h":63},
"sourceSize": {"w":84,"h":107},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "scoreboard.png",
"frame": {"x":2,"y":1056,"w":362,"h":98},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":362,"h":98},
"sourceSize": {"w":362,"h":98},
"pivot": {"x":0.5,"y":0.5}
}],
"meta": {
"app": "http://www.codeandweb.com/texturepacker",
"version": "1.0",
"image": "game_ui.png",
"format": "RGBA8888",
"size": {"w":472,"h":1319},
"scale": "1",
"smartupdate": "$TexturePacker:SmartUpdate:2fc7dfe766eba152274e2661737d6b51:b44b32d1e0a4146b3591d51b12f2d7ae:10ac111e32c27e51f4e8444dbb39eff6$"
}
}
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
{"frames": [
{
"filename": "asteroid_big_0.nrm.png",
"frame": {"x":2,"y":260,"w":200,"h":188},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":200,"h":188},
"sourceSize": {"w":200,"h":188},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "asteroid_big_0.png",
"frame": {"x":2,"y":450,"w":200,"h":167},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":21,"w":200,"h":167},
"sourceSize": {"w":200,"h":188},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "asteroid_big_1.nrm.png",
"frame": {"x":2,"y":619,"w":204,"h":166},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":204,"h":166},
"sourceSize": {"w":204,"h":166},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "asteroid_big_1.png",
"frame": {"x":2,"y":787,"w":204,"h":166},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":204,"h":166},
"sourceSize": {"w":204,"h":166},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "asteroid_big_2.nrm.png",
"frame": {"x":204,"y":335,"w":194,"h":165},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":194,"h":165},
"sourceSize": {"w":194,"h":165},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "asteroid_big_2.png",
"frame": {"x":208,"y":502,"w":194,"h":165},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":2,"w":194,"h":165},
"sourceSize": {"w":194,"h":167},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "asteroid_small_0.nrm.png",
"frame": {"x":347,"y":843,"w":102,"h":84},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":102,"h":84},
"sourceSize": {"w":102,"h":84},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "asteroid_small_0.png",
"frame": {"x":347,"y":929,"w":102,"h":84},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":102,"h":84},
"sourceSize": {"w":102,"h":84},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "asteroid_small_1.nrm.png",
"frame": {"x":404,"y":463,"w":96,"h":102},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":96,"h":102},
"sourceSize": {"w":96,"h":102},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "asteroid_small_1.png",
"frame": {"x":404,"y":567,"w":96,"h":102},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":0,"y":0,"w":96,"h":102},
"sourceSize": {"w":96,"h":106},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "asteroid_small_2.nrm.png",
"frame": {"x":347,"y":671,"w":109,"h":84},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":109,"h":84},
"sourceSize": {"w":109,"h":84},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "asteroid_small_2.png",
"frame": {"x":347,"y":757,"w":109,"h":84},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":109,"h":84},
"sourceSize": {"w":109,"h":84},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "coin.png",
"frame": {"x":429,"y":270,"w":42,"h":47},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":6,"y":4,"w":42,"h":47},
"sourceSize": {"w":54,"h":56},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "enemy_destroyer_1.png",
"frame": {"x":260,"y":193,"w":167,"h":140},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":10,"y":31,"w":167,"h":140},
"sourceSize": {"w":188,"h":188},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "enemy_scout_0.png",
"frame": {"x":400,"y":335,"w":107,"h":126},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":11,"y":1,"w":107,"h":126},
"sourceSize": {"w":129,"h":129},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "explosion_flare.png",
"frame": {"x":451,"y":2,"w":56,"h":209},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":0,"w":56,"h":209},
"sourceSize": {"w":64,"h":256},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "explosion_particle.png",
"frame": {"x":204,"y":260,"w":36,"h":60},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":14,"y":1,"w":36,"h":60},
"sourceSize": {"w":64,"h":64},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "explosion_ring.png",
"frame": {"x":2,"y":2,"w":256,"h":256},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":256,"h":256},
"sourceSize": {"w":256,"h":256},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "fire_particle.png",
"frame": {"x":429,"y":213,"w":55,"h":55},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":4,"y":4,"w":55,"h":55},
"sourceSize": {"w":64,"h":64},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "laser.png",
"frame": {"x":458,"y":671,"w":37,"h":76},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":37,"h":76},
"sourceSize": {"w":37,"h":76},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "shield.png",
"frame": {"x":260,"y":2,"w":189,"h":189},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":189,"h":189},
"sourceSize": {"w":189,"h":189},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "ship.nrm.png",
"frame": {"x":208,"y":669,"w":137,"h":167},
"rotated": false,
"trimmed": false,
"spriteSourceSize": {"x":0,"y":0,"w":137,"h":167},
"sourceSize": {"w":137,"h":167},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "ship.png",
"frame": {"x":208,"y":838,"w":137,"h":167},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":25,"y":10,"w":137,"h":167},
"sourceSize": {"w":188,"h":188},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "star_0.png",
"frame": {"x":2,"y":955,"w":62,"h":62},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":1,"y":1,"w":62,"h":62},
"sourceSize": {"w":64,"h":64},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "star_1.png",
"frame": {"x":66,"y":955,"w":62,"h":62},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":1,"y":1,"w":62,"h":62},
"sourceSize": {"w":64,"h":64},
"pivot": {"x":0.5,"y":0.5}
},
{
"filename": "star_2.png",
"frame": {"x":130,"y":955,"w":62,"h":62},
"rotated": false,
"trimmed": true,
"spriteSourceSize": {"x":1,"y":1,"w":62,"h":62},
"sourceSize": {"w":64,"h":64},
"pivot": {"x":0.5,"y":0.5}
}],
"meta": {
"app": "http://www.codeandweb.com/texturepacker",
"version": "1.0",
"image": "sprites.png",
"format": "RGBA8888",
"size": {"w":509,"h":1019},
"scale": "1",
"smartupdate": "$TexturePacker:SmartUpdate:2164a72d7de59aa3603277aa505ff73b:f339feca6ccc1f78132ecc733c94dbc5:1eabdf11f75e3a4fe3147baf7b5be24b$"
}
}
This diff was suppressed by a .gitattributes entry.
This diff was suppressed by a .gitattributes entry.
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_sprites/flutter_sprites.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(<String>[
'assets/checker.png',
'assets/line_effects.png'
]);
assert(_images["assets/checker.png"] != null);
runApp(new TestApp());
}
class TestApp extends StatefulComponent {
TestAppState createState() => new TestAppState();
}
final ThemeData _theme = new ThemeData(
brightness: ThemeBrightness.light,
primarySwatch: Colors.blue
);
class TestAppState extends State<TestApp> {
TestApp() {
_testBed = new TestBed(_labelTexts[_selectedLine]);
}
TestBed _testBed;
int _selectedLine = 0;
List<String> _labelTexts = <String>[
"Colored",
"Smoke",
"Electric",
"Rocket Trail"
];
Widget build(BuildContext context) {
return new MaterialApp(
title: 'EffectLine Demo',
theme: _theme,
routes: <String, RouteBuilder>{
'/': _buildColumn
}
);
}
Column _buildColumn(RouteArguments args) {
return new Column(<Widget>[
new Flexible(child: _buildSpriteWidget()),
_buildTabBar()
]);
}
TabBar _buildTabBar() {
return new TabBar(
labels: _buildTabLabels(),
selectedIndex: _selectedLine,
onChanged: (int selectedLine) {
setState(() {
_selectedLine = selectedLine;
});
}
);
}
List<TabLabel> _buildTabLabels() {
List<TabLabel> labels = <TabLabel>[];
for (String text in _labelTexts)
labels.add(new TabLabel(text: text));
return labels;
}
SpriteWidget _buildSpriteWidget() {
_testBed.setupLine(_labelTexts[_selectedLine]);
return new SpriteWidget(
_testBed,
SpriteBoxTransformMode.letterbox
);
}
}
class TestBed extends NodeWithSize {
EffectLine _line;
TestBed(String lineType) : super(new Size(1024.0, 1024.0)) {
userInteractionEnabled = true;
setupLine(lineType);
}
void setupLine(String lineType) {
if (_line != null) {
_line.removeFromParent();
}
if (lineType == "Colored") {
// Create a line with no texture and a color sequence
_line = new EffectLine(
texture: null,
colorSequence: new ColorSequence.fromStartAndEndColor(new Color(0xaaffff00), new Color(0xaaff9900)),
widthMode: EffectLineWidthMode.barrel,
minWidth: 10.0,
maxWidth: 15.0,
fadeAfterDelay: 1.0,
fadeDuration: 1.0
);
} else if (lineType == "Smoke") {
Texture baseTexture = new Texture(_images['assets/line_effects.png']);
Texture smokyLineTexture = baseTexture.textureFromRect(new Rect.fromLTRB(0.0, 0.0, 1024.0, 128.0));
_line = new EffectLine(
texture: smokyLineTexture,
textureLoopLength: 300.0,
colorSequence: new ColorSequence.fromStartAndEndColor(new Color(0xffffffff), new Color(0x00ffffff)),
widthMode: EffectLineWidthMode.barrel,
minWidth: 20.0,
maxWidth: 80.0,
animationMode: EffectLineAnimationMode.scroll
);
} else if (lineType == "Electric") {
Texture baseTexture = new Texture(_images['assets/line_effects.png']);
Texture electricLineTexture = baseTexture.textureFromRect(new Rect.fromLTRB(0.0, 384.0, 1024.0, 512.0));
_line = new EffectLine(
texture: electricLineTexture,
textureLoopLength: 300.0,
widthMode: EffectLineWidthMode.barrel,
minWidth: 20.0,
maxWidth: 100.0,
animationMode: EffectLineAnimationMode.random
);
} else if (lineType == "Rocket Trail") {
Texture baseTexture = new Texture(_images['assets/line_effects.png']);
Texture trailLineTexture = baseTexture.textureFromRect(new Rect.fromLTRB(0.0, 896.0, 1024.0, 1024.0));
_line = new EffectLine(
texture: trailLineTexture,
textureLoopLength: 300.0,
widthMode: EffectLineWidthMode.barrel,
minWidth: 20.0,
maxWidth: 40.0,
widthGrowthSpeed: 40.0,
fadeAfterDelay: 0.5,
fadeDuration: 1.5
);
}
addChild(_line);
}
bool handleEvent(SpriteBoxEvent event) {
if (event.type == "pointerdown")
_line.points = <Point>[];
if (event.type == "pointerdown" || event.type == "pointermove") {
Point pos = convertPointToNodeSpace(event.boxPosition);
_line.addPoint(pos);
}
return true;
}
}
assets:
- assets/nebula.png
- assets/sprites.json
- assets/sprites.png
- assets/starfield.png
- assets/game_ui.png
- assets/game_ui.json
- assets/explosion.wav
- assets/laser.wav
- assets/levelup.wav
- assets/pickup.wav
- assets/temp_music.aac
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<!--
This executable name must match the name of the app provided to the
ios_app GN template
-->
<key>CFBundleExecutable</key>
<string>game_app</string>
<key>CFBundleDisplayName</key>
<string>Asteroids</string>
<key>UILaunchStoryboardName</key>
<string>LaunchScreen</string>
<key>CFBundleIdentifier</key>
<string>com.google.sky.game</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>game_app</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>UIRequiredDeviceCapabilities</key>
<array>
<string>armv7</string>
</array>
<key>DTPlatformName</key>
<string>iphonesimulator</string>
<key>DTSDKName</key>
<string>iphonesimulator8.3</string>
<key>LSRequiresIPhoneOS</key>
<true/>
<key>MinimumOSVersion</key>
<string>8.3</string>
<key>UIDeviceFamily</key>
<array>
<integer>1</integer>
<integer>2</integer>
</array>
<key>CFBundleSupportedPlatforms</key>
<array>
<string>iPhoneSimulator</string>
</array>
</dict>
</plist>
part of game;
typedef void PointSetterCallback(Point value);
class ActionCircularMove extends ActionInterval {
ActionCircularMove(this.setter, this.center, this.radius, this.startAngle, this.clockWise, double duration) : super (duration);
final PointSetterCallback setter;
final Point center;
final double radius;
final double startAngle;
final bool clockWise;
void update(double t) {
if (!clockWise) t = -t;
double rad = radians(startAngle + t * 360.0);
Offset offset = new Offset(math.cos(rad) * radius, math.sin(rad) * radius);
Point pos = center + offset;
setter(pos);
}
}
class ActionOscillate extends ActionInterval {
ActionOscillate(this.setter, this.center, this.radius, double duration) : super(duration);
final PointSetterCallback setter;
final Point center;
final double radius;
void update(double t) {
double rad = radians(t * 360.0);
Offset offset = new Offset(math.sin(rad) * radius, 0.0);
setter(center + offset);
}
}
part of game;
class Explosion extends Node {
Explosion() {
zPosition = 10.0;
}
}
class ExplosionBig extends Explosion {
ExplosionBig(SpriteSheet sheet) {
// Add particles
ParticleSystem particlesDebris = new ParticleSystem(
sheet["explosion_particle.png"],
rotateToMovement: true,
startRotation:90.0,
startRotationVar: 0.0,
endRotation: 90.0,
startSize: 0.3,
startSizeVar: 0.1,
endSize: 0.3,
endSizeVar: 0.1,
numParticlesToEmit: 25,
emissionRate:1000.0,
greenVar: 127,
redVar: 127,
life: 0.75,
lifeVar: 0.5
);
particlesDebris.zPosition = 1010.0;
addChild(particlesDebris);
ParticleSystem particlesFire = new ParticleSystem(
sheet["fire_particle.png"],
colorSequence: new ColorSequence(<Color>[new Color(0xffffff33), new Color(0xffff3333), new Color(0x00ff3333)], <double>[0.0, 0.5, 1.0]),
numParticlesToEmit: 25,
emissionRate: 1000.0,
startSize: 0.5,
startSizeVar: 0.1,
endSize: 0.5,
endSizeVar: 0.1,
posVar: new Point(10.0, 10.0),
speed: 10.0,
speedVar: 5.0,
life: 0.75,
lifeVar: 0.5
);
particlesFire.zPosition = 1011.0;
addChild(particlesFire);
// Add ring
Sprite spriteRing = new Sprite(sheet["explosion_ring.png"]);
spriteRing.transferMode = ui.TransferMode.plus;
addChild(spriteRing);
Action scale = new ActionTween((double a) { spriteRing.scale = a; }, 0.2, 1.0, 0.75);
Action scaleAndRemove = new ActionSequence(<Action>[scale, new ActionRemoveNode(spriteRing)]);
Action fade = new ActionTween((double a) { spriteRing.opacity = a; }, 1.0, 0.0, 0.75);
actions.run(scaleAndRemove);
actions.run(fade);
// Add streaks
for (int i = 0; i < 5; i++) {
Sprite spriteFlare = new Sprite(sheet["explosion_flare.png"]);
spriteFlare.pivot = new Point(0.3, 1.0);
spriteFlare.scaleX = 0.3;
spriteFlare.transferMode = ui.TransferMode.plus;
spriteFlare.rotation = randomDouble() * 360.0;
addChild(spriteFlare);
double multiplier = randomDouble() * 0.3 + 1.0;
Action scale = new ActionTween((double a) { spriteFlare.scaleY = a; }, 0.3 * multiplier, 0.8, 0.75 * multiplier);
Action scaleAndRemove = new ActionSequence(<Action>[scale, new ActionRemoveNode(spriteFlare)]);
Action fadeIn = new ActionTween((double a) { spriteFlare.opacity = a; }, 0.0, 1.0, 0.25 * multiplier);
Action fadeOut = new ActionTween((double a) { spriteFlare.opacity = a; }, 1.0, 0.0, 0.5 * multiplier);
Action fadeInOut = new ActionSequence(<Action>[fadeIn, fadeOut]);
actions.run(scaleAndRemove);
actions.run(fadeInOut);
}
}
}
class ExplosionMini extends Explosion {
ExplosionMini(SpriteSheet sheet) {
for (int i = 0; i < 2; i++) {
Sprite star = new Sprite(sheet["star_0.png"]);
star.scale = 0.5;
star.colorOverlay = new Color(0xff95f4fb);
star.transferMode = ui.TransferMode.plus;
addChild(star);
double rotationStart = randomDouble() * 90.0;
double rotationEnd = 180.0 + randomDouble() * 90.0;
if (i == 0) {
rotationStart = -rotationStart;
rotationEnd = -rotationEnd;
}
ActionTween rotate = new ActionTween((double a) { star.rotation = a; }, rotationStart, rotationEnd, 0.2);
actions.run(rotate);
ActionTween fade = new ActionTween((double a) { star.opacity = a; }, 1.0, 0.0, 0.2);
actions.run(fade);
}
ActionSequence seq = new ActionSequence(<Action>[new ActionDelay(0.2), new ActionRemoveNode(this)]);
actions.run(seq);
}
}
part of game;
class Flash extends NodeWithSize {
Flash(Size size, this.duration) : super(size) {
ActionTween fade = new ActionTween((double a) { _opacity = a; }, 1.0, 0.0, duration);
ActionSequence seq = new ActionSequence(<Action>[fade, new ActionRemoveNode(this)]);
actions.run(seq);
}
double duration;
double _opacity = 1.0;
Paint _cachedPaint = new Paint();
void paint(PaintingCanvas canvas) {
// Update the color
_cachedPaint.color = new Color.fromARGB((255.0 * _opacity).toInt(),
255, 255, 255);
// Fill the area
applyTransformForPivot(canvas);
canvas.drawRect(new Rect.fromLTRB(0.0, 0.0, size.width, size.height),
_cachedPaint);
}
}
library game;
import 'dart:async';
import 'dart:math' as math;
import 'dart:ui' as ui;
import 'package:flutter/rendering.dart';
import 'package:flutter_sprites/flutter_sprites.dart';
import 'package:vector_math/vector_math_64.dart';
part 'custom_actions.dart';
part 'explosions.dart';
part 'flash.dart';
part 'game_demo_node.dart';
part 'game_objects.dart';
part 'game_object_factory.dart';
part 'player_state.dart';
part 'power_bar.dart';
part 'repeated_image.dart';
part 'star_field.dart';
part of game;
final double _gameSizeWidth = 320.0;
double _gameSizeHeight = 320.0;
final double _chunkSpacing = 640.0;
final int _chunksPerLevel = 9;
final bool _drawDebug = false;
typedef void GameOverCallback(int score);
class GameDemoNode extends NodeWithSize {
GameDemoNode(
this._images,
this._spritesGame,
this._spritesUI,
this._sounds,
this._gameOverCallback
): super(new Size(320.0, 320.0)) {
// Add background
_background = new RepeatedImage(_images["assets/starfield.png"]);
addChild(_background);
// Create starfield
_starField = new StarField(_spritesGame, 200);
addChild(_starField);
// Add nebula
_nebula = new RepeatedImage(_images["assets/nebula.png"], ui.TransferMode.plus);
addChild(_nebula);
// Setup game screen, it will always be anchored to the bottom of the screen
_gameScreen = new Node();
addChild(_gameScreen);
// Setup the level and add it to the screen, the level is the node where
// all our game objects live. It is moved to scroll the game
_level = new Level();
_gameScreen.addChild(_level);
// Add heads up display
_playerState = new PlayerState(_spritesUI, _spritesGame);
addChild(_playerState);
_objectFactory = new GameObjectFactory(_spritesGame, _sounds, _level, _playerState);
_level.ship = new Ship(_objectFactory);
_level.ship.setupActions();
_level.addChild(_level.ship);
// Add the joystick
_joystick = new VirtualJoystick();
_gameScreen.addChild(_joystick);
// Add initial game objects
addObjects();
}
// Resources
ImageMap _images;
Map<String, SoundEffect> _sounds;
SpriteSheet _spritesGame;
SpriteSheet _spritesUI;
// Sounds
SoundEffectPlayer _effectPlayer = SoundEffectPlayer.sharedInstance();
// Callback
GameOverCallback _gameOverCallback;
// Game screen nodes
Node _gameScreen;
VirtualJoystick _joystick;
GameObjectFactory _objectFactory;
Level _level;
StarField _starField;
RepeatedImage _background;
RepeatedImage _nebula;
PlayerState _playerState;
// Game properties
double _scroll = 0.0;
int _framesToFire = 0;
int _framesBetweenShots = 20;
bool _gameOver = false;
void spriteBoxPerformedLayout() {
_gameSizeHeight = spriteBox.visibleArea.height;
_gameScreen.position = new Point(0.0, _gameSizeHeight);
}
void update(double dt) {
// Scroll the level
_scroll = _level.scroll(_playerState.scrollSpeed);
_starField.move(0.0, _playerState.scrollSpeed);
_background.move(_playerState.scrollSpeed * 0.1);
_nebula.move(_playerState.scrollSpeed);
// Add objects
addObjects();
// Move the ship
if (!_gameOver) {
_level.ship.applyThrust(_joystick.value, _scroll);
}
// Add shots
if (_framesToFire == 0 && _joystick.isDown && !_gameOver) {
fire();
_framesToFire = (_playerState.speedLaserActive) ? _framesBetweenShots ~/ 2 : _framesBetweenShots;
}
if (_framesToFire > 0) _framesToFire--;
// Move game objects
for (Node node in _level.children) {
if (node is GameObject) {
node.move();
}
}
// Remove offscreen game objects
for (int i = _level.children.length - 1; i >= 0; i--) {
Node node = _level.children[i];
if (node is GameObject) {
node.removeIfOffscreen(_scroll);
}
}
if (_gameOver) return;
// Check for collisions between lasers and objects that can take damage
List<Laser> lasers = <Laser>[];
for (Node node in _level.children) {
if (node is Laser) lasers.add(node);
}
List<GameObject> damageables = <GameObject>[];
for (Node node in _level.children) {
if (node is GameObject && node.canBeDamaged) damageables.add(node);
}
for (Laser laser in lasers) {
for (GameObject damageable in damageables) {
if (laser.collidingWith(damageable)) {
// Hit something that can take damage
damageable.addDamage(laser.impact);
laser.destroy();
}
}
}
// Check for collsions between ship and objects that can damage the ship
List<Node> nodes = new List<Node>.from(_level.children);
for (Node node in nodes) {
if (node is GameObject && node.canDamageShip) {
if (node.collidingWith(_level.ship)) {
if (_playerState.shieldActive) {
// Hit, but saved by the shield!
node.destroy();
} else {
// The ship was hit :(
killShip();
}
}
} else if (node is GameObject && node.canBeCollected) {
if (node.collidingWith(_level.ship)) {
// The ship ran over something collectable
node.collect();
}
}
}
}
int _chunk = 0;
void addObjects() {
while (_scroll + _chunkSpacing >= _chunk * _chunkSpacing) {
addLevelChunk(
_chunk,
-_chunk * _chunkSpacing - _chunkSpacing);
_chunk += 1;
}
}
void addLevelChunk(int chunk, double yPos) {
int level = chunk ~/ _chunksPerLevel;
int part = chunk % _chunksPerLevel;
if (part == 0) {
LevelLabel lbl = new LevelLabel(_objectFactory, level + 1);
lbl.position = new Point(0.0, yPos + _chunkSpacing / 2.0 - 150.0);
_level.addChild(lbl);
} else if (part == 1) {
_objectFactory.addAsteroids(10 + level * 2, yPos, 0.0 + (level * 0.2).clamp(0.0, 0.7));
} else if (part == 2) {
_objectFactory.addEnemyScoutSwarm(4 + level * 2, yPos);
} else if (part == 3) {
_objectFactory.addAsteroids(10 + level * 2, yPos, 0.0 + (level * 0.2).clamp(0.0, 0.7));
} else if (part == 4) {
_objectFactory.addEnemyDestroyerSwarm(2 + level, yPos);
} else if (part == 5) {
_objectFactory.addAsteroids(10 + level * 2, yPos, 0.0 + (level * 0.2).clamp(0.0, 0.7));
} else if (part == 6) {
_objectFactory.addEnemyScoutSwarm(4 + level * 2, yPos);
} else if (part == 7) {
_objectFactory.addAsteroids(10 + level * 2, yPos, 0.0 + (level * 0.2).clamp(0.0, 0.7));
} else if (part == 8) {
_objectFactory.addBossFight(level, yPos);
}
}
void fire() {
int laserLevel = _objectFactory.playerState.laserLevel;
Laser shot0 = new Laser(_objectFactory, laserLevel, -90.0);
shot0.position = _level.ship.position + new Offset(17.0, -10.0);
_level.addChild(shot0);
Laser shot1 = new Laser(_objectFactory, laserLevel, -90.0);
shot1.position = _level.ship.position + new Offset(-17.0, -10.0);
_level.addChild(shot1);
if (_playerState.sideLaserActive) {
Laser shot2 = new Laser(_objectFactory, laserLevel, -45.0);
shot2.position = _level.ship.position + new Offset(17.0, -10.0);
_level.addChild(shot2);
Laser shot3 = new Laser(_objectFactory, laserLevel, -135.0);
shot3.position = _level.ship.position + new Offset(-17.0, -10.0);
_level.addChild(shot3);
}
_effectPlayer.play(_sounds["laser"]);
}
void killShip() {
// Hide ship
_level.ship.visible = false;
_effectPlayer.play(_sounds["explosion"]);
// Add explosion
ExplosionBig explo = new ExplosionBig(_spritesGame);
explo.scale = 1.5;
explo.position = _level.ship.position;
_level.addChild(explo);
// Add flash
Flash flash = new Flash(size, 1.0);
addChild(flash);
// Set the state to game over
_gameOver = true;
// Return to main scene and report the score back in 2 seconds
new Timer(new Duration(seconds: 2), () { _gameOverCallback(_playerState.score); });
}
}
class Level extends Node {
Level() {
position = new Point(160.0, 0.0);
}
Ship ship;
double scroll(double scrollSpeed) {
position += new Offset(0.0, scrollSpeed);
return position.y;
}
}
part of game;
class GameObjectFactory {
GameObjectFactory(this.sheet, this.sounds, this.level, this.playerState);
SpriteSheet sheet;
Map<String,SoundEffect> sounds;
Level level;
PlayerState playerState;
void addAsteroids(int numAsteroids, double yPos, double distribution) {
for (int i = 0; i < numAsteroids; i++) {
GameObject obj;
if (i == 0)
obj = new AsteroidPowerUp(this);
else if (randomDouble() < distribution)
obj = new AsteroidBig(this);
else
obj = new AsteroidSmall(this);
Point pos = new Point(randomSignedDouble() * 160.0,
yPos + _chunkSpacing * randomDouble());
addGameObject(obj, pos);
}
}
void addEnemyScoutSwarm(int numEnemies, double yPos) {
for (int i = 0; i < numEnemies; i++) {
double spacing = math.max(_chunkSpacing / (numEnemies + 1.0), 80.0);
double y = yPos + _chunkSpacing / 2.0 - (numEnemies - 1) * spacing / 2.0 + i * spacing;
addGameObject(new EnemyScout(this), new Point(0.0, y));
}
}
void addEnemyDestroyerSwarm(int numEnemies, double yPos) {
for (int i = 0; i < numEnemies; i++) {
addGameObject(new EnemyDestroyer(this), new Point(randomSignedDouble() * 120.0 , yPos + _chunkSpacing * randomDouble()));
}
}
void addGameObject(GameObject obj, Point pos) {
obj.position = pos;
obj.setupActions();
level.addChild(obj);
}
void addBossFight(int l, double yPos) {
// Add boss
EnemyBoss boss = new EnemyBoss(this);
Point pos = new Point(0.0, yPos + _chunkSpacing / 2.0);
addGameObject(boss, pos);
playerState.boss = boss;
// Add boss's helpers
if (l >= 1) {
EnemyDestroyer destroyer0 = new EnemyDestroyer(this);
addGameObject(destroyer0, new Point(-80.0, yPos + _chunkSpacing / 2.0 + 70.0));
EnemyDestroyer destroyer1 = new EnemyDestroyer(this);
addGameObject(destroyer1, new Point(80.0, yPos + _chunkSpacing / 2.0 + 70.0));
if (l >= 2) {
EnemyDestroyer destroyer0 = new EnemyDestroyer(this);
addGameObject(destroyer0, new Point(-80.0, yPos + _chunkSpacing / 2.0 - 70.0));
EnemyDestroyer destroyer1 = new EnemyDestroyer(this);
addGameObject(destroyer1, new Point(80.0, yPos + _chunkSpacing / 2.0 - 70.0));
}
}
}
}
This diff is collapsed.
// 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:flutter/material.dart';
import 'package:flutter/painting.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter_sprites/flutter_sprites.dart';
import 'game_demo.dart';
AssetBundle _initBundle() {
if (rootBundle != null)
return rootBundle;
return new NetworkAssetBundle(new Uri.directory(Uri.base.origin));
}
final AssetBundle _bundle = _initBundle();
ImageMap _imageMap;
SpriteSheet _spriteSheet;
SpriteSheet _spriteSheetUI;
Map<String, SoundEffect> _sounds = <String, SoundEffect>{};
main() async {
_imageMap = new ImageMap(_bundle);
// Use a list to wait on all loads in parallel just before starting the app.
List loads = [];
loads.add(_imageMap.load(<String>[
'assets/nebula.png',
'assets/sprites.png',
'assets/starfield.png',
'assets/game_ui.png',
]));
// TODO(eseidel): SoundEffect doesn't really do anything except hold a future.
_sounds['explosion'] = new SoundEffect(_bundle.load('assets/explosion.wav'));
_sounds['laser'] = new SoundEffect(_bundle.load('assets/laser.wav'));
loads.addAll([
_sounds['explosion'].load(),
_sounds['laser'].load(),
]);
await Future.wait(loads);
// TODO(eseidel): These load in serial which is bad for startup!
String json = await _bundle.loadString('assets/sprites.json');
_spriteSheet = new SpriteSheet(_imageMap['assets/sprites.png'], json);
json = await _bundle.loadString('assets/game_ui.json');
_spriteSheetUI = new SpriteSheet(_imageMap['assets/game_ui.png'], json);
assert(_spriteSheet.image != null);
SoundTrackPlayer stPlayer = SoundTrackPlayer.sharedInstance();
SoundTrack music = await stPlayer.load(_bundle.load('assets/temp_music.aac'));
stPlayer.play(music);
runApp(new GameDemo());
}
// TODO(viktork): The task bar purple is the wrong purple, we may need
// a custom theme swatch to match the purples in the sprites.
final ThemeData _theme = new ThemeData(
brightness: ThemeBrightness.light,
primarySwatch: Colors.purple
);
class GameDemo extends StatefulComponent {
GameDemoState createState() => new GameDemoState();
}
class GameDemoState extends State<GameDemo> {
NodeWithSize _game;
int _lastScore = 0;
Widget build(BuildContext context) {
return new MaterialApp(
title: 'Asteroids',
theme: _theme,
routes: <String, RouteBuilder>{
'/': _buildMainScene,
'/game': _buildGameScene
}
);
}
Widget _buildGameScene(RouteArguments args) {
return new SpriteWidget(_game, SpriteBoxTransformMode.fixedWidth);
}
Widget _buildMainScene(RouteArguments args) {
NavigatorState navigatorState = Navigator.of(args.context);
return new Stack(<Widget>[
new SpriteWidget(new MainScreenBackground(), SpriteBoxTransformMode.fixedWidth),
new Column(<Widget>[
new TextureButton(
onPressed: () {
_game = new GameDemoNode(
_imageMap,
_spriteSheet,
_spriteSheetUI,
_sounds,
(int lastScore) {
setState(() { _lastScore = lastScore; });
navigatorState.pop();
}
);
navigatorState.pushNamed('/game');
},
texture: _spriteSheetUI['btn_play_up.png'],
textureDown: _spriteSheetUI['btn_play_down.png'],
width: 128.0,
height: 128.0
),
new DefaultTextStyle(
child: new Text(
"Last Score: $_lastScore"
),
style: new TextStyle(fontSize:20.0)
)
],
justifyContent: FlexJustifyContent.center
)
]);
}
}
class TextureButton extends StatefulComponent {
TextureButton({
Key key,
this.onPressed,
this.texture,
this.textureDown,
this.width: 128.0,
this.height: 128.0
}) : super(key: key);
final VoidCallback onPressed;
final Texture texture;
final Texture textureDown;
final double width;
final double height;
TextureButtonState createState() => new TextureButtonState();
}
class TextureButtonState extends State<TextureButton> {
bool _highlight = false;
Widget build(BuildContext context) {
return new GestureDetector(
child: new Container(
width: config.width,
height: config.height,
child: new CustomPaint(
onPaint: paintCallback,
token: new _TextureButtonToken(
_highlight,
config.texture,
config.textureDown,
config.width,
config.height
)
)
),
onTapDown: (_) {
setState(() {
_highlight = true;
});
},
onTap: () {
setState(() {
_highlight = false;
});
if (config.onPressed != null)
config.onPressed();
},
onTapCancel: () {
setState(() {
_highlight = false;
});
}
);
}
void paintCallback(PaintingCanvas canvas, Size size) {
if (config.texture == null)
return;
canvas.save();
if (_highlight && config.textureDown != null) {
// Draw down state
canvas.scale(size.width / config.textureDown.size.width, size.height / config.textureDown.size.height);
config.textureDown.drawTexture(canvas, Point.origin, new Paint());
} else {
// Draw up state
canvas.scale(size.width / config.texture.size.width, size.height / config.texture.size.height);
config.texture.drawTexture(canvas, Point.origin, new Paint());
}
canvas.restore();
}
}
class _TextureButtonToken {
_TextureButtonToken(
this._highlight,
this._texture,
this._textureDown,
this._width,
this._height
);
final bool _highlight;
final Texture _texture;
final Texture _textureDown;
final double _width;
final double _height;
bool operator== (other) {
return
other is _TextureButtonToken &&
_highlight == other._highlight &&
_texture == other._texture &&
_textureDown == other._textureDown &&
_width == other._width &&
_height == other._height;
}
int get hashCode {
int value = 373;
value = 37 * value * _highlight.hashCode;
value = 37 * value * _texture.hashCode;
value = 37 * value * _textureDown.hashCode;
value = 37 * value * _width.hashCode;
value = 37 * value * _height.hashCode;
return value;
}
}
class MainScreenBackground extends NodeWithSize {
MainScreenBackground() : super(new Size(320.0, 320.0)) {
assert(_spriteSheet.image != null);
StarField starField = new StarField(_spriteSheet, 200, true);
addChild(starField);
}
void paint(PaintingCanvas canvas) {
canvas.drawRect(new Rect.fromLTWH(0.0, 0.0, 320.0, 320.0), new Paint()..color=new Color(0xff000000));
super.paint(canvas);
}
}
part of game;
class PlayerState extends Node {
PlayerState(this._sheetUI, this._sheetGame) {
// Score display
_spriteBackgroundScore = new Sprite(_sheetUI["scoreboard.png"]);
_spriteBackgroundScore.pivot = new Point(1.0, 0.0);
_spriteBackgroundScore.scale = 0.35;
_spriteBackgroundScore.position = new Point(240.0, 10.0);
addChild(_spriteBackgroundScore);
_scoreDisplay = new ScoreDisplay(_sheetUI);
_scoreDisplay.position = new Point(-13.0, 49.0);
_spriteBackgroundScore.addChild(_scoreDisplay);
// Coin display
_spriteBackgroundCoins = new Sprite(_sheetUI["coinboard.png"]);
_spriteBackgroundCoins.pivot = new Point(1.0, 0.0);
_spriteBackgroundCoins.scale = 0.35;
_spriteBackgroundCoins.position = new Point(105.0, 10.0);
addChild(_spriteBackgroundCoins);
_coinDisplay = new ScoreDisplay(_sheetUI);
_coinDisplay.position = new Point(-13.0, 49.0);
_spriteBackgroundCoins.addChild(_coinDisplay);
}
final SpriteSheet _sheetUI;
final SpriteSheet _sheetGame;
int laserLevel = 0;
static const double normalScrollSpeed = 2.0;
double scrollSpeed = normalScrollSpeed;
double _scrollSpeedTarget = normalScrollSpeed;
EnemyBoss boss;
Sprite _spriteBackgroundScore;
ScoreDisplay _scoreDisplay;
Sprite _spriteBackgroundCoins;
ScoreDisplay _coinDisplay;
int get score => _scoreDisplay.score;
set score(int score) {
_scoreDisplay.score = score;
flashBackgroundSprite(_spriteBackgroundScore);
}
int get coins => _coinDisplay.score;
void addCoin(Coin c) {
// Animate coin to the top of the screen
Point startPos = convertPointFromNode(Point.origin, c);
Point finalPos = new Point(30.0, 30.0);
Point middlePos = new Point((startPos.x + finalPos.x) / 2.0 + 50.0,
(startPos.y + finalPos.y) / 2.0);
List<Point> path = <Point>[startPos, middlePos, finalPos];
Sprite sprite = new Sprite(_sheetGame["coin.png"]);
sprite.scale = 0.7;
ActionSpline spline = new ActionSpline((Point a) { sprite.position = a; }, path, 0.5);
spline.tension = 0.25;
ActionTween rotate = new ActionTween((double a) { sprite.rotation = a; }, 0.0, 360.0, 0.5);
ActionTween scale = new ActionTween((double a) { sprite.scale = a; }, 0.7, 1.2, 0.5);
ActionGroup group = new ActionGroup(<Action>[spline, rotate, scale]);
sprite.actions.run(new ActionSequence(<Action>[
group,
new ActionRemoveNode(sprite),
new ActionCallFunction(() {
_coinDisplay.score += 1;
flashBackgroundSprite(_spriteBackgroundCoins);
})
]));
addChild(sprite);
}
void activatePowerUp(PowerUpType type) {
if (type == PowerUpType.shield) {
_shieldFrames += 300;
} else if (type == PowerUpType.sideLaser) {
_sideLaserFrames += 300;
} else if (type == PowerUpType.speedLaser) {
_speedLaserFrames += 300;
} else if (type == PowerUpType.speedBoost) {
_speedBoostFrames += 150;
}
}
int _shieldFrames = 0;
bool get shieldActive => _shieldFrames > 0 || _speedBoostFrames > 0;
bool get shieldDeactivating =>
math.max(_shieldFrames, _speedBoostFrames) > 0 && math.max(_shieldFrames, _speedBoostFrames) < 60;
int _sideLaserFrames = 0;
bool get sideLaserActive => _sideLaserFrames > 0;
int _speedLaserFrames = 0;
bool get speedLaserActive => _speedLaserFrames > 0;
int _speedBoostFrames = 0;
bool get speedBoostActive => _speedBoostFrames > 0;
void flashBackgroundSprite(Sprite sprite) {
sprite.actions.stopAll();
ActionTween flash = new ActionTween(
(Color a) { sprite.colorOverlay = a; },
new Color(0x66ccfff0),
new Color(0x00ccfff0),
0.3);
sprite.actions.run(flash);
}
void update(double dt) {
if (_shieldFrames > 0)
_shieldFrames--;
if (_sideLaserFrames > 0)
_sideLaserFrames--;
if (_speedLaserFrames > 0)
_speedLaserFrames--;
if (_speedBoostFrames > 0)
_speedBoostFrames--;
// Update speed
if (boss != null) {
Point globalBossPos = boss.convertPointToBoxSpace(Point.origin);
if (globalBossPos.y > (_gameSizeHeight - 400.0))
_scrollSpeedTarget = 0.0;
else
_scrollSpeedTarget = normalScrollSpeed;
} else {
if (speedBoostActive)
_scrollSpeedTarget = normalScrollSpeed * 6.0;
else
_scrollSpeedTarget = normalScrollSpeed;
}
scrollSpeed = GameMath.filter(scrollSpeed, _scrollSpeedTarget, 0.1);
}
}
class ScoreDisplay extends Node {
ScoreDisplay(this._sheetUI);
int _score = 0;
int get score => _score;
set score(int score) {
_score = score;
_dirtyScore = true;
}
SpriteSheet _sheetUI;
bool _dirtyScore = true;
void update(double dt) {
if (_dirtyScore) {
removeAllChildren();
String scoreStr = _score.toString();
double xPos = -37.0;
for (int i = scoreStr.length - 1; i >= 0; i--) {
String numStr = scoreStr.substring(i, i + 1);
Sprite numSprite = new Sprite(_sheetUI["number_$numStr.png"]);
numSprite.position = new Point(xPos, 0.0);
addChild(numSprite);
xPos -= 37.0;
}
_dirtyScore = false;
}
}
}
part of game;
class PowerBar extends NodeWithSize {
PowerBar(Size size, [this.power = 1.0]) : super(size);
double power;
Paint _paintFill = new Paint()
..color = new Color(0xffffffff);
Paint _paintOutline = new Paint()
..color = new Color(0xffffffff)
..strokeWidth = 1.0
..style = ui.PaintingStyle.stroke;
void paint(PaintingCanvas canvas) {
applyTransformForPivot(canvas);
canvas.drawRect(new Rect.fromLTRB(0.0, 0.0, size.width - 0.0, size.height - 0.0), _paintOutline);
canvas.drawRect(new Rect.fromLTRB(2.0, 2.0, (size.width - 2.0) * power, size.height - 2.0), _paintFill);
}
}
part of game;
class RepeatedImage extends Node {
Sprite _sprite0;
Sprite _sprite1;
RepeatedImage(ui.Image image, [ui.TransferMode mode = null]) {
_sprite0 = new Sprite.fromImage(image);
_sprite0.size = new Size(1024.0, 1024.0);
_sprite0.pivot = Point.origin;
_sprite1 = new Sprite.fromImage(image);
_sprite1.size = new Size(1024.0, 1024.0);
_sprite1.pivot = Point.origin;
_sprite1.position = new Point(0.0, -1024.0);
if (mode != null) {
_sprite0.transferMode = mode;
_sprite1.transferMode = mode;
}
addChild(_sprite0);
addChild(_sprite1);
}
void move(double dy) {
double yPos = (position.y + dy) % 1024.0;
position = new Point(0.0, yPos);
}
}
part of game;
class StarField extends NodeWithSize {
ui.Image _image;
SpriteSheet _spriteSheet;
int _numStars;
bool _autoScroll;
List<Point> _starPositions;
List<double> _starScales;
List<Rect> _rects;
List<Color> _colors;
final double _padding = 50.0;
Size _paddedSize = Size.zero;
Paint _paint = new Paint()
..filterQuality = ui.FilterQuality.low
..isAntiAlias = false
..transferMode = ui.TransferMode.plus;
StarField(this._spriteSheet, this._numStars, [this._autoScroll = false]) : super(Size.zero) {
_image = _spriteSheet.image;
}
void addStars() {
_starPositions = <Point>[];
_starScales = <double>[];
_colors = <Color>[];
_rects = <Rect>[];
size = spriteBox.visibleArea.size;
_paddedSize = new Size(size.width + _padding * 2.0,
size.height + _padding * 2.0);
for (int i = 0; i < _numStars; i++) {
_starPositions.add(new Point(randomDouble() * _paddedSize.width,
randomDouble() * _paddedSize.height));
_starScales.add(randomDouble() * 0.4);
_colors.add(new Color.fromARGB((255.0 * (randomDouble() * 0.5 + 0.5)).toInt(), 255, 255, 255));
_rects.add(_spriteSheet["star_${randomInt(2)}.png"].frame);
}
}
void spriteBoxPerformedLayout() {
addStars();
}
void paint(PaintingCanvas canvas) {
// Create a transform for each star
List<ui.RSTransform> transforms = <ui.RSTransform>[];
for (int i = 0; i < _numStars; i++) {
ui.RSTransform transform = new ui.RSTransform(
_starScales[i],
0.0,
_starPositions[i].x - _padding,
_starPositions[i].y - _padding);
transforms.add(transform);
}
// Draw the stars
canvas.drawAtlas(_image, transforms, _rects, _colors, ui.TransferMode.modulate, null, _paint);
}
void move(double dx, double dy) {
for (int i = 0; i < _numStars; i++) {
double xPos = _starPositions[i].x;
double yPos = _starPositions[i].y;
double scale = _starScales[i];
xPos += dx * scale;
yPos += dy * scale;
if (xPos >= _paddedSize.width) xPos -= _paddedSize.width;
if (xPos < 0) xPos += _paddedSize.width;
if (yPos >= _paddedSize.height) yPos -= _paddedSize.height;
if (yPos < 0) yPos += _paddedSize.height;
_starPositions[i] = new Point(xPos, yPos);
}
}
void update(double dt) {
if (_autoScroll) {
move(0.0, dt * 100.0);
}
}
}
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>en</string>
<key>CFBundleExecutable</key>
<string>game_app</string>
<key>CFBundleIconFile</key>
<string></string>
<key>CFBundleIdentifier</key>
<string>org.domokit.sky.asteroids</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Asteroids</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleSignature</key>
<string>????</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
<string>10.6</string>
<key>NSHumanReadableCopyright</key>
<string>Copyright © 2015 The Chromium Authors. All rights reserved.</string>
<key>NSMainNibFile</key>
<string>sky_mac</string>
<key>NSPrincipalClass</key>
<string>SkyApplication</string>
</dict>
</plist>
This diff is collapsed.
name: asteroids
dependencies:
flutter:
'0.0.16'
sky_tools: any
flutter_sprites:
'0.0.13'
box2d: any
dependency_overrides:
material_design_icons:
path: ../../sky/packages/material_design_icons
flutter:
path: ../../sky/packages/sky
flutter_sprites:
path: ../../skysprites
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_sprites/flutter_sprites.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(<String>[
'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 MaterialApp {
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));
}
import 'dart:ui';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_sprites/flutter_sprites.dart';
AssetBundle _initBundle() {
if (rootBundle != null)
return rootBundle;
return new NetworkAssetBundle(Uri.base);
}
final AssetBundle _bundle = _initBundle();
ImageMap _images;
SpriteSheet _spriteSheet;
final ThemeData _theme = new ThemeData(
brightness: ThemeBrightness.light,
primarySwatch: Colors.purple
);
main() async {
_images = new ImageMap(_bundle);
await _images.load(<String>[
'assets/sprites.png'
]);
String json = await _bundle.loadString('assets/sprites.json');
_spriteSheet = new SpriteSheet(_images['assets/sprites.png'], json);
runApp(new MaterialApp(
title: 'Test drawAtlas',
theme: _theme,
routes: <String, RouteBuilder>{
'/': (RouteArguments args) {
return new SpriteWidget(
new TestDrawAtlas(),
SpriteBoxTransformMode.fixedWidth
);
}
}
));
}
class TestDrawAtlas extends NodeWithSize {
TestDrawAtlas() : super(new Size(1024.0, 1024.0));
void paint(PaintingCanvas canvas) {
List<RSTransform> transforms = <RSTransform>[
new RSTransform(1.0, 0.0, 100.0, 100.0)
];
List<Rect> rects = <Rect>[
_spriteSheet["ship.png"].frame
];
List<Color> colors = <Color>[
new Color(0xffffffff)
];
canvas.drawAtlas(
_spriteSheet.image,
transforms,
rects,
colors,
TransferMode.src,
null,
new Paint()
..filterQuality = FilterQuality.low
..isAntiAlias = false
);
}
}
This diff is collapsed.
This diff is collapsed.
import 'dart:ui';
import 'package:flutter/animation.dart';
import 'package:flutter/material.dart';
import 'package:flutter/rendering.dart';
import 'package:flutter/services.dart';
import 'package:flutter/widgets.dart';
import 'package:flutter_sprites/flutter_sprites.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(<String>[
'assets/sprites.png'
]);
String json = await _bundle.loadString('assets/sprites.json');
_spriteSheet = new SpriteSheet(_images['assets/sprites.png'], json);
runApp(new MaterialApp(
title: 'Test Physics',
theme: new ThemeData(
brightness: ThemeBrightness.light,
primarySwatch: Colors.purple
),
routes: <String, RouteBuilder>{
'/': (RouteArguments args) {
return new SpriteWidget(
new TestBed(),
SpriteBoxTransformMode.letterbox
);
}
}
));
}
class TestBed extends NodeWithSize {
Sprite _obstacle;
PhysicsWorld _world;
PhysicsGroup _group;
PhysicsGroup _group2;
TestBed() : super(new Size(1024.0, 1024.0)) {
_world = new PhysicsWorld(new Offset(0.0, 100.0));
_world.drawDebug = true;
_group = new PhysicsGroup();
_group2 = new PhysicsGroup();
_group2.position = new Point(50.0, 50.0);
_world.addChild(_group);
_world.addChild(_group2);
_obstacle = new Sprite(_spriteSheet["ship.png"]);
_obstacle.position = new Point(512.0, 800.0);
_obstacle.size = new Size(64.0, 64.0);
_obstacle.scale = 2.0;
_obstacle.physicsBody = new PhysicsBody(
new PhysicsShapeCircle(Point.origin, 32.0),
type: PhysicsBodyType.static,
friction: 0.5,
tag: "obstacle"
);
_group.addChild(_obstacle);
_world.addContactCallback(myCallback, "obstacle", "ship", PhysicsContactType.begin);
// Animate group
ActionSequence seq = new ActionSequence(<Action>[
new ActionTween((Point a) { _group.position = a; }, new Point(-256.0, 0.0), new Point(256.0, 0.0), 1.0, Curves.easeInOut),
new ActionTween((Point a) { _group.position = a; }, new Point(256.0, 0.0), new Point(-256.0, 0.0), 1.0, Curves.easeInOut)
]);
_group.actions.run(new ActionRepeatForever(seq));
addChild(_world);
userInteractionEnabled = true;
}
void myCallback(PhysicsContactType type, PhysicsContact contact) {
}
bool handleEvent(SpriteBoxEvent event) {
if (event.type == "pointerdown") {
Point pos = convertPointToNodeSpace(event.boxPosition);
PhysicsGroup group = new PhysicsGroup();
group.position = pos;
_world.addChild(group);
Sprite shipA;
shipA = new Sprite(_spriteSheet["ship.png"]);
shipA.opacity = 0.3;
shipA.position = new Point(-40.0, 0.0);
shipA.size = new Size(64.0, 64.0);
shipA.physicsBody = new PhysicsBody(new PhysicsShapeCircle(Point.origin, 32.0),
friction: 0.5,
restitution: 0.5,
tag: "ship"
);
group.addChild(shipA);
Sprite shipB;
shipB = new Sprite(_spriteSheet["ship.png"]);
shipB.opacity = 0.3;
shipB.position = new Point(40.0, 0.0);
shipB.size = new Size(64.0, 64.0);
shipB.physicsBody = new PhysicsBody(new PhysicsShapePolygon(<Point>[new Point(-25.0, -25.0), new Point(25.0, -25.0), new Point(25.0, 25.0), new Point(-25.0, 25.0)]),
friction: 0.5,
restitution: 0.5,
tag: "ship"
);
group.addChild(shipB);
}
return true;
}
}
// 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:flutter/widgets.dart';
void main() => runApp(new Center(child: new Text('Hello, world!')));
name: hello_world
dependencies:
flutter:
'0.0.16'
sky_tools: any
dependency_overrides:
material_design_icons:
path: ../../sky/packages/material_design_icons
flutter:
path: ../../sky/packages/sky
# 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("//sky/build/sky_app.gni")
sky_app("mine_digger") {
main_dart = "lib/main.dart"
if (is_android) {
apk_name = "MineDigger"
}
}
<?xml version="1.0" encoding="utf-8"?>
<!-- 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="org.domokit.mine_digger">
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="21" />
<uses-permission android:name="android.permission.INTERNET"/>
<application android:name="org.domokit.sky.shell.SkyApplication" android:label="Mine Digger">
<activity android:name="org.domokit.sky.shell.SkyActivity"
android:launchMode="singleTask"
android:theme="@android:style/Theme.Black.NoTitleBar"
android:configChanges="orientation|keyboardHidden|keyboard|screenSize"
android:hardwareAccelerated="true">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>
This diff is collapsed.
name: mine_digger
dependencies:
flutter:
'0.0.16'
sky_tools: any
dependency_overrides:
material_design_icons:
path: ../../sky/packages/material_design_icons
flutter:
path: ../../sky/packages/sky
This diff is collapsed.
This diff is collapsed.
name: sky_raw_examples
dependencies:
flutter:
'0.0.16'
sky_tools: any
dependency_overrides:
material_design_icons:
path: ../../sky/packages/material_design_icons
flutter:
path: ../../sky/packages/sky
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
// 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:math' as math;
import 'dart:ui' as ui;
import 'dart:typed_data';
Duration timeBase = null;
void beginFrame(Duration timeStamp) {
ui.tracing.begin('beginFrame');
if (timeBase == null)
timeBase = timeStamp;
double delta = (timeStamp - timeBase).inMicroseconds / Duration.MICROSECONDS_PER_MILLISECOND;
// paint
ui.Rect paintBounds = ui.Point.origin & ui.window.size;
ui.PictureRecorder recorder = new ui.PictureRecorder();
ui.Canvas canvas = new ui.Canvas(recorder, paintBounds);
canvas.translate(paintBounds.width / 2.0, paintBounds.height / 2.0);
canvas.rotate(math.PI * delta / 1800);
canvas.drawRect(new ui.Rect.fromLTRB(-100.0, -100.0, 100.0, 100.0),
new ui.Paint()..color = const ui.Color.fromARGB(255, 0, 255, 0));
ui.Picture picture = recorder.endRecording();
// composite
final double devicePixelRatio = ui.window.devicePixelRatio;
ui.Rect sceneBounds = new ui.Rect.fromLTWH(0.0, 0.0, ui.window.size.width * devicePixelRatio, ui.window.size.height * devicePixelRatio);
Float64List deviceTransform = new Float64List(16)
..[0] = devicePixelRatio
..[5] = devicePixelRatio
..[10] = 1.0
..[15] = 1.0;
ui.SceneBuilder sceneBuilder = new ui.SceneBuilder(sceneBounds)
..pushTransform(deviceTransform)
..addPicture(ui.Offset.zero, picture, paintBounds)
..pop();
ui.window.render(sceneBuilder.build());
ui.tracing.end('beginFrame');
ui.window.scheduleFrame();
}
void main() {
ui.window.onBeginFrame = beginFrame;
ui.window.scheduleFrame();
}
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
This diff is collapsed.
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment