Merge pull request #202 from vlidholt/master
Fixes issues with invalidating matrix for SpriteBox & adds HUD to demo game
diff --git a/packages/flutter/README.md b/packages/flutter/README.md
index a70daae..fddf171 100644
--- a/packages/flutter/README.md
+++ b/packages/flutter/README.md
@@ -3,7 +3,9 @@
Sky apps are written in Dart. To get started, we need to set up Dart SDK:
- - Install the [Dart SDK](https://www.dartlang.org/downloads/).
+ - Install the [Dart SDK](https://www.dartlang.org/downloads/):
+ - Mac: `brew tap dart-lang/dart && brew install dart`
+ - Linux: See [https://www.dartlang.org/downloads/linux.html](https://www.dartlang.org/downloads/linux.html)
- Ensure that `$DART_SDK` is set to the path of your Dart SDK and that the
`dart` and `pub` executables are on your `$PATH`.
@@ -44,20 +46,20 @@
string and centers it on the screen using a `Center` widget. To learn more about
the widget system, please see the [widgets tutorial](lib/widgets/README.md).
-Setup your Android device
+Setting up your Android device
-------------------------
Currently Sky requires an Android device running the Lollipop (or newer) version
of the Android operating system.
- - Install the `adb` tool from the [Android SDK](https://developer.android.com/sdk/installing/index.html)
- and ensure that `adb` (inside `platform-tools` in the Android SDK) is in your
- `$PATH`.
+ - Install the `adb` tool from the [Android SDK](https://developer.android.com/sdk/installing/index.html?pkg=tools):
+ - Mac: `brew install android-platform-tools`
+ - Linux: `sudo apt-get install android-tools-adb`
- Enable developer mode on your device by visiting `Settings > About phone`
and tapping the `Build number` field five times.
- - Enable `USB debugging` in `Settings > Developer options`.
+ - Enable `Android debugging` in `Settings > Developer options`.
- Using a USB cable, plug your phone into your computer. If prompted on your
device, authorize your computer to access your device.
diff --git a/packages/flutter/example/mine_digger/lib/main.dart b/packages/flutter/example/mine_digger/lib/main.dart
index 0db72e2..3219837 100644
--- a/packages/flutter/example/mine_digger/lib/main.dart
+++ b/packages/flutter/example/mine_digger/lib/main.dart
@@ -9,125 +9,152 @@
import 'package:sky/rendering/flex.dart';
import 'package:sky/theme/colors.dart' as colors;
import 'package:sky/widgets/basic.dart';
+import 'package:sky/widgets/widget.dart';
import 'package:sky/widgets/scaffold.dart';
import 'package:sky/widgets/task_description.dart';
import 'package:sky/widgets/theme.dart';
import 'package:sky/widgets/tool_bar.dart';
-// Classic minesweeper-inspired game. The mouse controls are standard except
-// for left + right combo which is not implemented. For touch, the duration of
-// the pointer determines probing versus flagging.
+// Classic minesweeper-inspired game. The mouse controls are standard
+// except for left + right combo which is not implemented. For touch,
+// the duration of the pointer determines probing versus flagging.
//
-// There are only 3 classes to understand. Game, which is contains all the
-// logic and two UI classes: CoveredMineNode and ExposedMineNode, none of them
-// holding state.
+// There are only 3 classes to understand. MineDiggerApp, which is
+// contains all the logic and two classes that describe the mines:
+// CoveredMineNode and ExposedMineNode, none of them holding state.
-class Game {
+// Colors for each mine count (0-8):
+const List<TextStyle> textStyles = const <TextStyle>[
+ const TextStyle(color: const Color(0xFF555555), fontWeight: bold),
+ const TextStyle(color: const Color(0xFF0094FF), fontWeight: bold), // blue
+ const TextStyle(color: const Color(0xFF13A023), fontWeight: bold), // green
+ const TextStyle(color: const Color(0xFFDA1414), fontWeight: bold), // red
+ const TextStyle(color: const Color(0xFF1E2347), fontWeight: bold), // black
+ const TextStyle(color: const Color(0xFF7F0037), fontWeight: bold), // dark red
+ const TextStyle(color: const Color(0xFF000000), fontWeight: bold),
+ const TextStyle(color: const Color(0xFF000000), fontWeight: bold),
+ const TextStyle(color: const Color(0xFF000000), fontWeight: bold),
+];
+
+enum CellState { covered, exploded, cleared, flagged, shown }
+
+class MineDiggerApp extends App {
+
+ void initState() {
+ resetGame();
+ }
+
static const int rows = 9;
static const int cols = 9;
static const int totalMineCount = 11;
- static const int coveredCell = 0;
- static const int explodedCell = 1;
- static const int clearedCell = 2;
- static const int flaggedCell = 3;
- static const int shownCell = 4;
-
- static final List<TextStyle> textStyles = new List<TextStyle>();
-
- final App app;
-
bool alive;
bool hasWon;
int detectedCount;
- int randomSeed;
// |cells| keeps track of the positions of the mines.
List<List<bool>> cells;
// |uiState| keeps track of the visible player progess.
- List<List<int>> uiState;
+ List<List<CellState>> uiState;
- Game(this.app) {
- randomSeed = 22;
- // Colors for each mine count:
- // 0 - none, 1 - blue, 2-green, 3-red, 4-black, 5-dark red .. etc.
- textStyles.add(
- new TextStyle(color: const Color(0xFF555555), fontWeight: bold));
- textStyles.add(
- new TextStyle(color: const Color(0xFF0094FF), fontWeight: bold));
- textStyles.add(
- new TextStyle(color: const Color(0xFF13A023), fontWeight: bold));
- textStyles.add(
- new TextStyle(color: const Color(0xFFDA1414), fontWeight: bold));
- textStyles.add(
- new TextStyle(color: const Color(0xFF1E2347), fontWeight: bold));
- textStyles.add(
- new TextStyle(color: const Color(0xFF7F0037), fontWeight: bold));
- textStyles.add(
- new TextStyle(color: const Color(0xFFE93BE9), fontWeight: bold));
- initialize();
- }
-
- void initialize() {
+ void resetGame() {
alive = true;
hasWon = false;
detectedCount = 0;
// Build the arrays.
cells = new List<List<bool>>();
- uiState = new List<List<int>>();
+ uiState = new List<List<CellState>>();
for (int iy = 0; iy != rows; iy++) {
cells.add(new List<bool>());
- uiState.add(new List<int>());
+ uiState.add(new List<CellState>());
for (int ix = 0; ix != cols; ix++) {
cells[iy].add(false);
- uiState[iy].add(coveredCell);
+ uiState[iy].add(CellState.covered);
}
}
// Place the mines.
- Random random = new Random(++randomSeed);
- for (int mc = 0; mc != totalMineCount; mc++) {
- int rx = random.nextInt(rows);
- int ry = random.nextInt(cols);
- if (cells[ry][rx]) {
- // Mine already there. Try again.
- --mc;
- } else {
- cells[ry][rx] = true;
+ Random random = new Random();
+ int cellsRemaining = rows * cols;
+ int minesRemaining = totalMineCount;
+ for (int x = 0; x < cols; x += 1) {
+ for (int y = 0; y < rows; y += 1) {
+ if (random.nextInt(cellsRemaining) < minesRemaining) {
+ cells[y][x] = true;
+ minesRemaining -= 1;
+ if (minesRemaining <= 0)
+ return;
+ }
+ cellsRemaining -= 1;
}
}
+ assert(false);
+ }
+
+ Stopwatch longPressStopwatch;
+
+ PointerEventListener _pointerDownHandlerFor(int posX, int posY) {
+ return (sky.PointerEvent event) {
+ if (event.buttons == 1) {
+ probe(posX, posY);
+ } else if (event.buttons == 2) {
+ flag(posX, posY);
+ } else {
+ // Touch event.
+ longPressStopwatch = new Stopwatch()..start();
+ }
+ };
+ }
+
+ PointerEventListener _pointerUpHandlerFor(int posX, int posY) {
+ return (sky.PointerEvent event) {
+ if (longPressStopwatch == null)
+ return;
+ // Pointer down was a touch event.
+ if (longPressStopwatch.elapsedMilliseconds < 250) {
+ probe(posX, posY);
+ } else {
+ // Long press flags.
+ flag(posX, posY);
+ }
+ longPressStopwatch = null;
+ };
}
Widget buildBoard() {
bool hasCoveredCell = false;
- List<Flex> flexRows = new List<Flex>();
+ List<Flex> flexRows = <Flex>[];
for (int iy = 0; iy != 9; iy++) {
- List<Component> row = new List<Component>();
+ List<Widget> row = <Widget>[];
for (int ix = 0; ix != 9; ix++) {
- int state = uiState[iy][ix];
+ CellState state = uiState[iy][ix];
int count = mineCount(ix, iy);
-
if (!alive) {
- if (state != explodedCell)
- state = cells[iy][ix] ? shownCell : state;
+ if (state != CellState.exploded)
+ state = cells[iy][ix] ? CellState.shown : state;
}
-
- if (state == coveredCell) {
+ if (state == CellState.covered) {
+ row.add(new Listener(
+ onPointerDown: _pointerDownHandlerFor(ix, iy),
+ onPointerUp: _pointerUpHandlerFor(ix, iy),
+ child: new CoveredMineNode(
+ flagged: false,
+ posX: ix,
+ posY: iy
+ )
+ ));
+ // Mutating |hasCoveredCell| here is hacky, but convenient, same
+ // goes for mutating |hasWon| below.
+ hasCoveredCell = true;
+ } else if (state == CellState.flagged) {
row.add(new CoveredMineNode(
- this,
- flagged: false,
- posX: ix, posY: iy));
- // Mutating |hasCoveredCell| here is hacky, but convenient, same
- // goes for mutating |hasWon| below.
- hasCoveredCell = true;
- } else if (state == flaggedCell) {
- row.add(new CoveredMineNode(
- this,
flagged: true,
- posX: ix, posY: iy));
+ posX: ix, posY: iy)
+ );
} else {
row.add(new ExposedMineNode(
state: state,
- count: count));
+ count: count)
+ );
}
}
flexRows.add(
@@ -147,33 +174,32 @@
}
return new Container(
- key: 'minefield',
padding: new EdgeDims.all(10.0),
margin: new EdgeDims.all(10.0),
decoration: new BoxDecoration(backgroundColor: const Color(0xFF6B6B6B)),
child: new Flex(
flexRows,
- direction: FlexDirection.vertical,
- key: 'flxv'));
+ direction: FlexDirection.vertical
+ )
+ );
}
Widget buildToolBar() {
- String banner = hasWon ?
+ String toolbarCaption = hasWon ?
'Awesome!!' : alive ?
'Mine Digger [$detectedCount-$totalMineCount]': 'Kaboom! [press here]';
return new ToolBar(
// FIXME: Strange to have the toolbar be tapable.
center: new Listener(
- onPointerDown: handleBannerPointerDown,
- child: new Text(banner, style: Theme.of(this.app).text.title)
+ onPointerDown: handleToolbarPointerDown,
+ child: new Text(toolbarCaption, style: Theme.of(this).text.title)
)
);
}
- Widget buildUI() {
- // FIXME: We need to build the board before we build the toolbar because
- // we compute the win state during build step.
+ Widget build() {
+ // We build the board before we build the toolbar because we compute the win state during build step.
Widget board = buildBoard();
return new TaskDescription(
label: 'Mine Digger',
@@ -187,39 +213,42 @@
);
}
- void handleBannerPointerDown(sky.PointerEvent event) {
- initialize();
- app.scheduleBuild();
+ void handleToolbarPointerDown(sky.PointerEvent event) {
+ setState(() {
+ resetGame();
+ });
}
// User action. The user uncovers the cell which can cause losing the game.
void probe(int x, int y) {
if (!alive)
return;
- if (uiState[y][x] == flaggedCell)
+ if (uiState[y][x] == CellState.flagged)
return;
- // Allowed to probe.
- if (cells[y][x]) {
- // Probed on a mine --> dead!!
- uiState[y][x] = explodedCell;
- alive = false;
- } else {
- // No mine, uncover nearby if possible.
- cull(x, y);
- }
- app.scheduleBuild();
+ setState(() {
+ // Allowed to probe.
+ if (cells[y][x]) {
+ // Probed on a mine --> dead!!
+ uiState[y][x] = CellState.exploded;
+ alive = false;
+ } else {
+ // No mine, uncover nearby if possible.
+ cull(x, y);
+ }
+ });
}
// User action. The user is sure a mine is at this location.
void flag(int x, int y) {
- if (uiState[y][x] == flaggedCell) {
- uiState[y][x] = coveredCell;
- --detectedCount;
- } else {
- uiState[y][x] = flaggedCell;
- ++detectedCount;
- }
- app.scheduleBuild();
+ setState(() {
+ if (uiState[y][x] == CellState.flagged) {
+ uiState[y][x] = CellState.covered;
+ --detectedCount;
+ } else {
+ uiState[y][x] = CellState.flagged;
+ ++detectedCount;
+ }
+ });
}
// Recursively uncovers cells whose totalMineCount is zero.
@@ -229,9 +258,9 @@
if ((y < 0) || (y > cols - 1))
return;
- if (uiState[y][x] == clearedCell)
+ if (uiState[y][x] == CellState.cleared)
return;
- uiState[y][x] = clearedCell;
+ uiState[y][x] = CellState.cleared;
if (mineCount(x, y) > 0)
return;
@@ -269,106 +298,70 @@
}
}
-Widget makeCell(Widget widget) {
+Widget buildCell(Widget child) {
return new Container(
padding: new EdgeDims.all(1.0),
height: 27.0, width: 27.0,
decoration: new BoxDecoration(backgroundColor: const Color(0xFFC0C0C0)),
margin: new EdgeDims.all(2.0),
- child: widget);
+ child: child
+ );
}
-Widget makeInnerCell(Widget widget) {
+Widget buildInnerCell(Widget child) {
return new Container(
padding: new EdgeDims.all(1.0),
margin: new EdgeDims.all(3.0),
height: 17.0, width: 17.0,
- child: widget);
+ child: child
+ );
}
class CoveredMineNode extends Component {
- final Game game;
+
+ CoveredMineNode({ this.flagged, this.posX, this.posY });
+
final bool flagged;
final int posX;
final int posY;
- Stopwatch stopwatch;
-
- CoveredMineNode(this.game, {this.flagged, this.posX, this.posY});
-
- void _handlePointerDown(sky.PointerEvent event) {
- if (event.buttons == 1) {
- game.probe(posX, posY);
- } else if (event.buttons == 2) {
- game.flag(posX, posY);
- } else {
- // Touch event.
- stopwatch = new Stopwatch()..start();
- }
- }
-
- void _handlePointerUp(sky.PointerEvent event) {
- if (stopwatch == null)
- return;
- // Pointer down was a touch event.
- if (stopwatch.elapsedMilliseconds < 250) {
- game.probe(posX, posY);
- } else {
- // Long press flags.
- game.flag(posX, posY);
- }
- stopwatch = null;
- }
Widget build() {
- Widget text = flagged ?
- makeInnerCell(new StyledText(elements : [Game.textStyles[5], '\u2691'])) :
- null;
+ Widget text;
+ if (flagged)
+ text = buildInnerCell(new StyledText(elements : [textStyles[5], '\u2691']));
Container inner = new Container(
margin: new EdgeDims.all(2.0),
height: 17.0, width: 17.0,
decoration: new BoxDecoration(backgroundColor: const Color(0xFFD9D9D9)),
- child: text);
+ child: text
+ );
- return makeCell(new Listener(
- child: inner,
- onPointerDown: _handlePointerDown,
- onPointerUp: _handlePointerUp));
+ return buildCell(inner);
}
}
class ExposedMineNode extends Component {
- final int state;
- final int count;
- ExposedMineNode({this.state, this.count});
+ ExposedMineNode({ this.state, this.count });
+
+ final CellState state;
+ final int count;
Widget build() {
StyledText text;
- if (state == Game.clearedCell) {
+ if (state == CellState.cleared) {
// Uncovered cell with nearby mine count.
if (count != 0)
- text = new StyledText(elements : [Game.textStyles[count], '$count']);
+ text = new StyledText(elements : [textStyles[count], '$count']);
} else {
// Exploded mine or shown mine for 'game over'.
- int color = state == Game.explodedCell ? 3 : 0;
- text = new StyledText(elements : [Game.textStyles[color], '\u2600']);
+ int color = state == CellState.exploded ? 3 : 0;
+ text = new StyledText(elements : [textStyles[color], '\u2600']);
}
-
- return makeCell(makeInnerCell(text));
- }
-}
-
-class MineDiggerApp extends App {
- Game game;
-
- MineDiggerApp() {
- game = new Game(this);
+ return buildCell(buildInnerCell(text));
}
- Widget build() {
- return game.buildUI();
- }
}
void main() {
diff --git a/packages/flutter/example/raw/mutating-dom.dart b/packages/flutter/example/raw/mutating-dom.dart
index 0d27a3f..6de893c 100644
--- a/packages/flutter/example/raw/mutating-dom.dart
+++ b/packages/flutter/example/raw/mutating-dom.dart
@@ -48,9 +48,12 @@
node = root;
} else if (node != root && other != null && pickThis(0.1)) {
report("insertBefore()");
- node.insertBefore([other]);
+ try {
+ node.insertBefore([other]);
+ } catch (_) {
+ }
break;
- } else if (pickThis(0.001)) {
+ } else if (node != root && pickThis(0.001)) {
report("remove()");
node.remove();
} else if (node is sky.Element) {
@@ -148,7 +151,7 @@
break;
}
} else {
- assert(node is sky.Text); //
+ assert(node is sky.Text);
final sky.Text text = node;
if (pickThis(0.1)) {
report("appending a new text node (ASCII)");
diff --git a/packages/flutter/lib/sky_tool b/packages/flutter/lib/sky_tool
index e57159c..8152b8a 100755
--- a/packages/flutter/lib/sky_tool
+++ b/packages/flutter/lib/sky_tool
@@ -23,6 +23,7 @@
OBSERVATORY_PORT = 8181
APK_NAME = 'SkyDemo.apk'
ANDROID_PACKAGE = "org.domokit.sky.demo"
+ANDROID_COMPONENT = '%s/%s.SkyDemoActivity' % (ANDROID_PACKAGE, ANDROID_PACKAGE)
# FIXME: This assumes adb is in $PATH, we could look for ANDROID_HOME, etc?
ADB_PATH = 'adb'
# FIXME: Do we need to look in $DART_SDK?
@@ -111,7 +112,7 @@
def _url_for_path(port, root, path):
relative_path = os.path.relpath(path, root)
- return 'sky://localhost:%s/%s' % (port, relative_path)
+ return 'http://localhost:%s/%s' % (port, relative_path)
class StartSky(object):
@@ -193,13 +194,14 @@
pids['remote_sky_server_port'] = sky_server_port
# The load happens on the remote device, use the remote port.
- sky_url = _url_for_path(pids['remote_sky_server_port'], sky_server_root,
+ url = _url_for_path(pids['remote_sky_server_port'], sky_server_root,
main_dart)
subprocess.check_call([ADB_PATH, 'shell',
'am', 'start',
'-a', 'android.intent.action.VIEW',
- '-d', sky_url])
+ '-d', url,
+ ANDROID_COMPONENT])
class StopSky(object):
diff --git a/packages/flutter/lib/widgets/widget.dart b/packages/flutter/lib/widgets/widget.dart
index 6f96239..cc9e396 100644
--- a/packages/flutter/lib/widgets/widget.dart
+++ b/packages/flutter/lib/widgets/widget.dart
@@ -585,7 +585,7 @@
return super.syncChild(node, oldNode, slot);
}
- void setState(Function fn()) {
+ void setState(void fn()) {
assert(!_disqualifiedFromEverAppearingAgain);
fn();
scheduleBuild();
diff --git a/packages/flutter/pubspec.yaml b/packages/flutter/pubspec.yaml
index 7ef9c6b..4efba0c 100644
--- a/packages/flutter/pubspec.yaml
+++ b/packages/flutter/pubspec.yaml
@@ -11,4 +11,4 @@
sdk: '>=1.8.0 <2.0.0'
homepage: https://github.com/domokit/mojo/tree/master/sky
name: sky
-version: 0.0.20
+version: 0.0.21