I tried to get Cards to be drag and droppable, but it doesn't seem
like Stack + Transform comprehends the positions of the elements inside.
Stack + Position absolutely refuses to render though.
(To debug that, run with mojo_shell)
diff --git a/lib/components/card.dart b/lib/components/card.dart
new file mode 100644
index 0000000..8fb5c7b
--- /dev/null
+++ b/lib/components/card.dart
@@ -0,0 +1,106 @@
+import '../logic/card.dart' show Card;
+import 'card_constants.dart' as card_constants;
+import 'package:sky/widgets.dart' as widgets;
+import 'package:sky/theme/colors.dart' as colors;
+import 'dart:sky' as sky;
+
+class CardComponent extends widgets.StatefulComponent {
+ final Card card;
+ final bool faceUp;
+
+ widgets.DragController dragController;
+ widgets.Offset displacement = widgets.Offset.zero;
+ String status = 'foo';
+
+ CardComponent(this.card, this.faceUp);
+
+ void syncFields(CardComponent other) {
+ //assert(false); // Why do we need to do this?
+ //dragController = other.dragController;
+ //displacement = other.displacement;
+ }
+
+ widgets.Widget build() {
+ return new widgets.Listener(
+ onPointerDown: _startDrag,
+ onPointerMove: _updateDrag,
+ onPointerCancel: _cancelDrag,
+ onPointerUp: _drop,
+ child: new widgets.Container(
+ width: card_constants.CARD_WIDTH,
+ height: card_constants.CARD_HEIGHT,
+ child: new widgets.Container(
+ decoration: new widgets.BoxDecoration(
+ border: new widgets.Border.all(
+ width: 3.0,
+ color: dragController == null ? colors.Yellow[500] : colors.Red[500]
+ ),
+ backgroundColor: dragController == null ? colors.Orange[500] : colors.Brown[500]
+ ),
+ child: new widgets.Flex([
+ imageFromCard(card, faceUp),
+ new widgets.Text(status)
+ ], direction: widgets.FlexDirection.vertical)
+ )
+ )
+ );
+ }
+
+ static widgets.Widget imageFromCard(Card c, bool faceUp) {
+ String imageName = "${c.deck}/${faceUp ? 'up' : 'down'}/${c.identifier}.png";
+ return new widgets.NetworkImage(src: imageName);
+ }
+
+ widgets.EventDisposition _startDrag(sky.PointerEvent event) {
+ setState(() {
+ dragController = new widgets.DragController(new CardDragData(this.card));
+ dragController.update(new widgets.Point(event.x, event.y));
+ displacement = widgets.Offset.zero;
+ status = 'dragS ${event.x.toStringAsFixed(0)} ${event.y.toStringAsFixed(0)}';
+ //debug3 = "START ${event.x.toStringAsFixed(3)} ${event.y.toStringAsFixed(3)}";
+ });
+ return widgets.EventDisposition.consumed;
+ }
+
+ widgets.EventDisposition _updateDrag(sky.PointerEvent event) {
+ setState(() {
+ dragController = new widgets.DragController(new CardDragData(this.card));
+ dragController.update(new widgets.Point(event.x, event.y));
+ displacement += new widgets.Offset(event.dx, event.dy);
+ status = 'dragU ${event.x.toStringAsFixed(0)} ${event.y.toStringAsFixed(0)}';
+ //debug3 = "DRAG ${event.x.toStringAsFixed(3)} ${event.y.toStringAsFixed(3)}";
+ });
+ return widgets.EventDisposition.consumed;
+ }
+
+ widgets.EventDisposition _cancelDrag(sky.PointerEvent event) {
+ setState(() {
+ dragController.cancel();
+ dragController = null;
+ //debug3 = "CANCELED";
+ status = 'CNCL ${event.x.toStringAsFixed(0)} ${event.y.toStringAsFixed(0)}';
+ });
+ return widgets.EventDisposition.consumed;
+ }
+
+ widgets.EventDisposition _drop(sky.PointerEvent event) {
+ setState(() {
+ dragController.update(new widgets.Point(event.x, event.y));
+ dragController.drop();
+ dragController = null;
+
+ //dotX += _displacement.dx;
+ //dotY += _displacement.dy;
+ displacement = widgets.Offset.zero;
+ status = 'DROP ${event.x.toStringAsFixed(0)} ${event.y.toStringAsFixed(0)}';
+ //debug3 = "DROP ${event.x.toStringAsFixed(3)} ${event.y.toStringAsFixed(3)}";
+ });
+ return widgets.EventDisposition.consumed;
+ }
+}
+
+class CardDragData {
+ final Card card;
+
+ CardDragData(this.card);
+}
\ No newline at end of file
diff --git a/lib/components/card_collection.dart b/lib/components/card_collection.dart
new file mode 100644
index 0000000..056cded
--- /dev/null
+++ b/lib/components/card_collection.dart
@@ -0,0 +1,88 @@
+import '../logic/card.dart' show Card;
+import 'card.dart' show CardComponent, CardDragData;
+import 'package:sky/widgets/basic.dart';
+import 'package:sky/widgets.dart' show DragTarget;
+import 'card_constants.dart' as card_constants;
+import 'package:sky/theme/colors.dart' as colors;
+import 'package:vector_math/vector_math.dart' as vector_math;
+
+enum Orientation {
+ vert, horz, fan, show1
+}
+
+class CardCollectionComponent extends StatefulComponent {
+ final List<Card> cards;
+ final Orientation orientation;
+ final bool faceUp;
+ final Function parentHandleAccept;
+
+ String status = 'bar';
+
+ CardCollectionComponent(this.cards, this.faceUp, this.orientation, this.parentHandleAccept);
+
+ void syncFields(CardCollectionComponent other) {
+ //assert(false); // Why do we need to do this?
+ }
+
+ void _handleAccept(CardDragData data) {
+ setState(() {
+ status = 'ACCEPT';
+ });
+ this.parentHandleAccept(data.card, this.cards);
+ }
+
+ Widget build() {
+ // Let's just do horizontal for now, it's too complicated otherwise.
+
+ double cardDelta = card_constants.CARD_WIDTH;
+ if (cards.length > 6) {
+ //cardDelta = card_constants.CARD_WIDTH / cards.length; // just make it tiny
+ cardDelta -= card_constants.CARD_WIDTH * (cards.length - 6) / cards.length;
+ }
+
+ List<Widget> cardComponents = new List<Widget>();
+ cardComponents.add(new Text(status));
+ for (int i = 0; i < cards.length; i++) {
+ // Positioned seems correct, but it causes an error when rendering. Constraints aren't matched?
+ /*cardComponents.add(new Positioned(
+ top: 0.0,
+ // left: i * cardDelta,
+ child: new CardComponent(cards[i], faceUp)
+ ));*/
+ cardComponents.add(new Transform(
+ transform: new vector_math.Matrix4.identity().translate(i * cardDelta, 40.0),
+ child: new CardComponent(cards[i], faceUp)
+ ));
+ }
+
+
+ // Just draw a stack of cards...
+ //return new Stack(cardComponents);
+
+
+ /*List<Widget> cardComponents = new List<Widget>();
+ for (int i = 0; i < cards.length; i++) {
+ cardComponents.add(new CardComponent(cards[i], faceUp));
+ }
+ return new Flex(cardComponents);*/
+
+ // Let's draw a stack of cards with DragTargets.
+ return new DragTarget<CardDragData>(
+ onAccept: _handleAccept,
+ builder: (List<CardDragData> data, _) {
+ return new Container(
+ decoration: new BoxDecoration(
+ border: new Border.all(
+ width: 3.0,
+ color: data.isEmpty ? colors.white : colors.Blue[500]
+ ),
+ backgroundColor: data.isEmpty ? colors.Grey[500] : colors.Green[500]
+ ),
+ height: 150.0,
+ margin: new EdgeDims.all(10.0),
+ child: new Stack(cardComponents)
+ );
+ }
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/components/card_constants.dart b/lib/components/card_constants.dart
new file mode 100644
index 0000000..380718c
--- /dev/null
+++ b/lib/components/card_constants.dart
@@ -0,0 +1,2 @@
+const double CARD_HEIGHT = 120.0;
+const double CARD_WIDTH = 90.0;
\ No newline at end of file
diff --git a/lib/components/game.dart b/lib/components/game.dart
new file mode 100644
index 0000000..626434c
--- /dev/null
+++ b/lib/components/game.dart
@@ -0,0 +1,72 @@
+import '../logic/card.dart' show Card;
+import '../logic/game.dart' show Game, GameType;
+import 'card_collection.dart' show CardCollectionComponent, Orientation;
+import 'package:sky/widgets/basic.dart';
+import 'package:sky/theme/colors.dart' as colors;
+import 'card_constants.dart' as card_constants;
+import 'package:vector_math/vector_math.dart' as vector_math;
+
+class GameComponent extends StatefulComponent {
+ final Game game;
+
+ GameComponent(this.game);
+
+ void syncFields(GameComponent other) {}
+
+ Widget build() {
+ switch (game.gameType) {
+ case GameType.Hearts:
+ return buildHearts();
+ default:
+ return null; // unsupported
+ }
+ }
+
+ void _parentHandleAccept(Card card, List<Card> toList) {
+ // That means that this card was dragged to this other Card collection component.
+ setState(() {
+ game.move(card, toList);
+ });
+ }
+
+ Widget buildHearts() {
+ List<Widget> cardCollections = new List<Widget>();
+ for (int i = 0; i < 4; i++) {
+ List<Card> cards = game.cardCollections[i];
+ CardCollectionComponent c = new CardCollectionComponent(cards, true, Orientation.horz, _parentHandleAccept);
+
+ cardCollections.add(new Positioned(
+ top: i * (card_constants.CARD_HEIGHT + 20.0),
+ child: c
+ ));
+
+ /*cardCollections.add(new Transform(
+ transform: new vector_math.Matrix4.identity().translate(0.0, i * (card_constants.CARD_HEIGHT + 20.0)),
+ child: c
+ ));*/
+ }
+
+ // game.cardCollections[4] is a discard pile
+ cardCollections.add(new Transform(
+ transform: new vector_math.Matrix4.identity().translate(0.0, 4 * (card_constants.CARD_HEIGHT + 20.0)),
+ child: new Container(
+ decoration: new BoxDecoration(backgroundColor: colors.Green[500], borderRadius: 5.0),
+ child: new CardCollectionComponent(game.cardCollections[4], true, Orientation.horz, _parentHandleAccept)
+ )
+ ));
+ /*cardCollections.add(new Positioned(
+ top: 4 * (card_constants.CARD_HEIGHT + 20.0),
+ child: new Container(
+ decoration: new BoxDecoration(backgroundColor: colors.Green[500], borderRadius: 5.0),
+ child: new CardCollectionComponent(game.cardCollections[4], true, Orientation.horz)
+ )
+ ));*/
+
+ // game.cardCollections[5] is just not shown
+
+ return new Container(
+ decoration: new BoxDecoration(backgroundColor: colors.Pink[500]),
+ child: new Stack(cardCollections)
+ );
+ }
+}
\ No newline at end of file
diff --git a/lib/logic/card.dart b/lib/logic/card.dart
new file mode 100644
index 0000000..021495f
--- /dev/null
+++ b/lib/logic/card.dart
@@ -0,0 +1,10 @@
+class Card {
+ final String deck;
+ final String identifier;
+
+ const Card(this.deck, this.identifier);
+
+ toString() => "${deck} ${identifier}";
+
+ get string => toString();
+}
diff --git a/lib/logic/game.dart b/lib/logic/game.dart
new file mode 100644
index 0000000..8329ec9
--- /dev/null
+++ b/lib/logic/game.dart
@@ -0,0 +1,108 @@
+import 'card.dart' show Card;
+import 'dart:math' show Random;
+
+enum GameType {
+ Hearts
+}
+
+/// A game consists of multiple decks and tracks a single deck of cards.
+/// It also handles events; when cards are dragged to and from decks.
+class Game {
+ final GameType gameType;
+ final List<List<Card>> cardCollections = new List<List<Card>>();
+ final List<Card> deck = <Card>[
+ const Card("classic", "c1"),
+ const Card("classic", "c2"),
+ const Card("classic", "c3"),
+ const Card("classic", "c4"),
+ const Card("classic", "c5"),
+ const Card("classic", "c6"),
+ const Card("classic", "c7"),
+ const Card("classic", "c8"),
+ const Card("classic", "c9"),
+ const Card("classic", "c10"),
+ const Card("classic", "cj"),
+ const Card("classic", "cq"),
+ const Card("classic", "ck"),
+ const Card("classic", "d1"),
+ const Card("classic", "d2"),
+ const Card("classic", "d3"),
+ const Card("classic", "d4"),
+ const Card("classic", "d5"),
+ const Card("classic", "d6"),
+ const Card("classic", "d7"),
+ const Card("classic", "d8"),
+ const Card("classic", "d9"),
+ const Card("classic", "d10"),
+ const Card("classic", "dj"),
+ const Card("classic", "dq"),
+ const Card("classic", "dk"),
+ const Card("classic", "h1"),
+ const Card("classic", "h2"),
+ const Card("classic", "h3"),
+ const Card("classic", "h4"),
+ const Card("classic", "h5"),
+ const Card("classic", "h6"),
+ const Card("classic", "h7"),
+ const Card("classic", "h8"),
+ const Card("classic", "h9"),
+ const Card("classic", "h10"),
+ const Card("classic", "hj"),
+ const Card("classic", "hq"),
+ const Card("classic", "hk"),
+ const Card("classic", "s1"),
+ const Card("classic", "s2"),
+ const Card("classic", "s3"),
+ const Card("classic", "s4"),
+ const Card("classic", "s5"),
+ const Card("classic", "s6"),
+ const Card("classic", "s7"),
+ const Card("classic", "s8"),
+ const Card("classic", "s9"),
+ const Card("classic", "s10"),
+ const Card("classic", "sj"),
+ const Card("classic", "sq"),
+ const Card("classic", "sk"),
+ ];
+
+ final Random random = new Random();
+
+ Game.hearts(int playerNumber) : gameType = GameType.Hearts {
+ // playerNumber would be used in a real game, but I have to ignore it for debugging.
+ // It would determine faceUp/faceDown status.
+
+ deck.shuffle();
+ cardCollections.add(this.deal(10));
+ cardCollections.add(this.deal(5));
+ cardCollections.add(this.deal(1));
+ cardCollections.add(this.deal(1));
+ cardCollections.add(new List<Card>()); // an empty pile TODO(alexfandrianto): Why can't I just have an empty stack?
+ cardCollections.add(new List<Card>()); // a hidden pile!
+ }
+
+ List<Card> deal(int numCards) {
+ assert(deck.length >= numCards);
+ List<Card> cards = new List<Card>.from(deck.take(numCards));
+ deck.removeRange(0, numCards);
+ return cards;
+ }
+
+ void move(Card card, List<Card> dest) {
+ // The first step is to find the card. Where is it?
+ // then we can remove it and add to the dest.
+ int i = findCard(card);
+ assert(i != -1);
+ cardCollections[i].remove(card);
+ dest.add(card);
+ }
+
+ // Which card collection has the card?
+ int findCard(Card card) {
+ for (var i = 0; i < cardCollections; i++) {
+ if (cardCollections[i].contains(card)) {
+ return i;
+ }
+ }
+ return -1;
+ }
+}
\ No newline at end of file
diff --git a/lib/main.dart b/lib/main.dart
index 84943fa..4a6f279 100644
--- a/lib/main.dart
+++ b/lib/main.dart
@@ -1,266 +1,27 @@
//import 'package:sky/widgets/basic.dart';
-import 'card.dart' as card;
-import 'my_button.dart';
-import 'dart:sky' as sky;
-import 'package:vector_math/vector_math.dart' as vector_math;
-import 'package:sky/theme/colors.dart' as colors;
+//import 'card.dart' as card;
+//import 'my_button.dart';
+//import 'dart:sky' as sky;
+//import 'package:vector_math/vector_math.dart' as vector_math;
+//import 'package:sky/theme/colors.dart' as colors;
import 'package:sky/widgets.dart';
-class HelloWorldApp extends App {
- int counter = 0;
- String debug = '';
- int counter2 = 0;
- // String debug2 = '';
- // Accumulators for the current scroll.
- double dx = 0.0;
- double dy = 0.0;
+import 'logic/game.dart' show Game;
+import 'components/game.dart' show GameComponent;
- // Used for drag/drop of the square
- DragController _dragController;
- Offset _displacement = Offset.zero;
+class CroupierApp extends App {
+ Game game;
- // Positioning of the dotX and dotY
- double dotX = 10.0;
- double dotY = 10.0 + sky.view.paddingTop;
-
- String debug3 = '';
-
- Container makeTransform() {
- return new Container(
- child: new MyButton(
- child: new Text('Engage'),
- onPressed: _handleOnPressedCallback,
- onPointerDown: _handleOnPointerDownCallback,
- onPointerMove: _handleOnPointerMoveCallback
- ),
- padding: const EdgeDims.all(8.0),
- //margin: const EdgeDims.symmetric(horizontal: 8.0),
- decoration: new BoxDecoration(
- backgroundColor: const Color(0xFF0000FF),
- borderRadius: 5.0
- ),
- transform: new vector_math.Matrix4.identity().translate(dx, dy)
- );
- }
-
- void _handleOnPressedCallback(sky.Event e) {
- setState(() {
- counter++;
- sky.PointerEvent ge = e as sky.PointerEvent;
- debug = '(${ge.x.toStringAsFixed(3)}, ${ge.y.toStringAsFixed(3)})';//ge.toString();
- });
- }
-
- void _handleOnPointerDownCallback(sky.Event e) {
- setState(() {
- counter2++;
- //dx = 0.0;
- //dy = 0.0;
- //sky.PointerEvent ge = e as sky.PointerEvent;
- //debug2 = '${ge.x} ${ge.y}';
- // The field names were found from here: https://github.com/domokit/sky_engine/blob/01ff5c383fc88647c08f11a0d3392238b8bc99de/sky/engine/core/events/GestureEvent.h
- //'${ge.x} ${ge.y}';
- //'${ge.dx} ${ge.dy}'; I didn't see this on scroll either though... T_T
- //'${ge.velocityX} ${ge.velocityY}'; Is this for fling?
- });
- }
-
- void _handleOnPointerMoveCallback(sky.Event e) {
- setState(() {
- sky.PointerEvent ge = e as sky.PointerEvent;
- dx += ge.dx;
- dy += ge.dy;
- // debug3 = '${ge.dx} ${ge.dy}'; // Was this one for scroll update then?
- // The field names were found from here: https://github.com/domokit/sky_engine/blob/01ff5c383fc88647c08f11a0d3392238b8bc99de/sky/engine/core/events/GestureEvent.h
- //'${ge.x} ${ge.y}';
- //'${ge.dx} ${ge.dy}'; I didn't see this on scroll either though... T_T
- //'${ge.velocityX} ${ge.velocityY}'; Is this for fling?
-
- //this.rt.transform.translate(ge.dx, ge.dy);
- //this.rt.setIdentity();
- //this.translate(dx, dy);
- });
- }
-
- EventDisposition _startDrag(sky.PointerEvent event) {
- setState(() {
- _dragController = new DragController(new DragData("Orange"));
- _dragController.update(new Point(event.x, event.y));
- _displacement = Offset.zero;
- debug3 = "START ${event.x.toStringAsFixed(3)} ${event.y.toStringAsFixed(3)}";
- });
- return EventDisposition.consumed;
- }
-
- EventDisposition _updateDrag(sky.PointerEvent event) {
- setState(() {
- _dragController.update(new Point(event.x, event.y));
- _displacement += new Offset(event.dx, event.dy);
- debug3 = "DRAG ${event.x.toStringAsFixed(3)} ${event.y.toStringAsFixed(3)}";
- });
- return EventDisposition.consumed;
- }
-
- EventDisposition _cancelDrag(sky.PointerEvent event) {
- setState(() {
- _dragController.cancel();
- _dragController = null;
- debug3 = "CANCELED";
- });
- return EventDisposition.consumed;
- }
-
- EventDisposition _drop(sky.PointerEvent event) {
- setState(() {
- _dragController.update(new Point(event.x, event.y));
- _dragController.drop();
- _dragController = null;
-
- dotX += _displacement.dx;
- dotY += _displacement.dy;
- _displacement = Offset.zero;
- debug3 = "DROP ${event.x.toStringAsFixed(3)} ${event.y.toStringAsFixed(3)}";
- });
- return EventDisposition.consumed;
+ CroupierApp() : super() {
+ this.game = new Game.hearts(0);
}
Widget build() {
- return new Center(child: new Flex([
- new Center(child: new Text('Hello, world!')),
- new Center(child: new Text('Tap #${counter}: ${debug}')),
- new Center(child: new Text('Scroll #${counter2}: (${dx.toStringAsFixed(3)}, ${dy.toStringAsFixed(3)})')),
- new Center(child: new Text('We did it!')),
- new Center(child: new MyToolBar()),
- makeTransform(),
- new Flex([
- new card.CardComponent(new card.Card.fromString("classic h1"), true),
- new card.CardComponent(new card.Card.fromString("classic sk"), true),
- new card.CardComponent(new card.Card.fromString("classic d5"), true)
- ]),
- new Center(child: new Text('Drag: ${debug3}')),
- new Center(child: new Text('X, Y: ${dotX} ${dotY}')),
- new Container(
- decoration: new BoxDecoration(backgroundColor: colors.Pink[500]),
- child: new Stack([
- new Flex([
- new ExampleDragTarget(),
- new ExampleDragTarget()
- ]),
- new Positioned(
- top: _dragController != null ? dotY + _displacement.dy : -1000.0,
- left: _dragController != null ? dotX + _displacement.dx : -1000.0,
- child: new IgnorePointer(
- child: new Opacity(
- opacity: 1.0,
- child: new Dot()
- )
- )
- ),
- new Listener(
- onPointerDown: _startDrag,
- onPointerMove: _updateDrag,
- onPointerCancel: _cancelDrag,
- onPointerUp: _drop,
- child: new Positioned(
- top: dotY,
- left: dotX,
- child: new Opacity(
- opacity: _dragController != null ? 0.0 : 1.0,
- child: new Dot()
- )
- )
- )
- ])
- )
- ], direction: FlexDirection.vertical));
- }
-}
-
-class Dot extends Component {
- Widget build() {
- return new Container(
- width: 50.0,
- height: 50.0,
- decoration: new BoxDecoration(
- backgroundColor: colors.DeepOrange[500]
- )
- );
- }
-}
-
-class DragData {
- DragData(this.text);
-
- final String text;
-}
-
-class ExampleDragTarget extends StatefulComponent {
- String _text = 'ready';
-
- void syncFields(ExampleDragTarget source) {
- }
-
- void _handleAccept(DragData data) {
- setState(() {
- _text = data.text;
- });
- }
-
- Widget build() {
- return new DragTarget<DragData>(
- onAccept: _handleAccept,
- builder: (List<DragData> data, _) {
- return new Container(
- width: 100.0,
- height: 100.0,
- margin: new EdgeDims.all(10.0),
- decoration: new BoxDecoration(
- border: new Border.all(
- width: 3.0,
- color: data.isEmpty ? colors.white : colors.Blue[500]
- ),
- backgroundColor: data.isEmpty ? colors.Grey[500] : colors.Green[500]
- ),
- child: new Center(
- child: new Text(_text)
- )
- );
- }
- );
+ return new GameComponent(this.game);
}
}
void main() {
- runApp(new HelloWorldApp());
-}
-
-class MyToolBar extends Component {
- Widget build() {
- return new Container(
- decoration: const BoxDecoration(
- backgroundColor: const Color(0xFF00FFFF)
- ),
- height: 56.0,
- padding: const EdgeDims.symmetric(horizontal: 8.0),
- child: new Flex([
- new NetworkImage(src: 'menu.png', width: 25.0, height: 25.0),
- new Flexible(child: new Text('My awesome toolbar')),
- new NetworkImage(src: 'search.png', width: 25.0, height: 25.0),
- ])
- );
- }
-}
-
-// A component must build its widget in order to be a Widget.
-// Widgets, however, just are what they are.
-// Everything extends off Widget, but some are Component and some aren't, like
-// the RenderObjectWrapper's. How do they manage to draw themselves? syncChild is the thing that is generally called.
-
-// sky/widgets/basic.dart has all of these...
-// So if I want to position something on my own... I could use Padding/Center in terms of widgets.
-// But which one takes a Point or Offset?
-// Hm... So I think we want to just position, we could use a Container or Transform
-// Transform uses a matrix4 which is really inconvenient...
-// new Matrix4.identity().scale? .rotate? .translate?
-// RenderTransform works too instead of Transform. I guess.
\ No newline at end of file
+ runApp(new CroupierApp());
+}
\ No newline at end of file