| // Copyright 2015 The Vanadium 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 '../logic/card.dart' as logic_card; |
| import 'card.dart' as component_card; |
| import 'draggable.dart' show Draggable; |
| import 'util.dart' as util; |
| |
| import 'dart:math' as math; |
| import 'package:sky/widgets.dart'; |
| import 'package:sky/material.dart' as material; |
| |
| enum Orientation { vert, horz, fan, show1, suit } |
| enum DropType { |
| none, |
| card, |
| card_collection |
| // I can see that both would be nice, but I'm not sure how to do that yet. |
| } |
| |
| const double DEFAULT_WIDTH = 200.0; |
| const double DEFAULT_CARD_HEIGHT = 60.0; |
| const double DEFAULT_CARD_WIDTH = 60.0; |
| |
| const double CARD_MARGIN = 3.0; // transparent |
| const double WHITE_LINE_HEIGHT = 2.0; // white |
| const double WHITE_LINE_MARGIN = 4.0; // each side |
| |
| class CardCollectionComponent extends StatefulComponent { |
| List<logic_card.Card> cards; |
| Orientation orientation; |
| bool faceUp; |
| Function parentCallback; |
| bool dragChildren; |
| DropType acceptType; |
| Function comparator; |
| double width; |
| double widthCard; |
| double heightCard; |
| var backgroundColor; |
| var altColor; |
| |
| String status = 'bar'; |
| |
| CardCollectionComponent( |
| this.cards, this.faceUp, this.orientation, this.parentCallback, |
| {this.dragChildren: false, |
| this.acceptType: DropType.none, |
| this.comparator: null, |
| this.width: DEFAULT_WIDTH, |
| this.widthCard: DEFAULT_CARD_WIDTH, |
| this.heightCard: DEFAULT_CARD_HEIGHT, |
| this.backgroundColor, |
| this.altColor}) { |
| if (this.backgroundColor == null) { |
| backgroundColor = material.Colors.grey[500]; |
| } |
| if (this.altColor == null) { |
| altColor = material.Colors.grey[600]; |
| } |
| } |
| |
| void syncConstructorArguments(CardCollectionComponent other) { |
| cards = other.cards; |
| orientation = other.orientation; |
| faceUp = other.faceUp; |
| parentCallback = other.parentCallback; |
| dragChildren = other.dragChildren; |
| acceptType = other.acceptType; |
| comparator = other.comparator; |
| width = other.width; |
| widthCard = other.widthCard; |
| heightCard = other.heightCard; |
| backgroundColor = other.backgroundColor; |
| altColor = other.altColor; |
| } |
| |
| bool _handleWillAccept(dynamic data) { |
| print('will accept?'); |
| print(data); |
| return true; |
| } |
| |
| void _handleAccept(component_card.Card data) { |
| print('accept'); |
| setState(() { |
| status = 'ACCEPT ${data.card.toString()}'; |
| parentCallback(data.card, this.cards); |
| }); |
| } |
| |
| void _handleAcceptMultiple(CardCollectionComponent data) { |
| print('acceptMulti'); |
| setState(() { |
| status = 'ACCEPT multi: ${data.cards.toString()}'; |
| parentCallback(data.cards, this.cards); |
| }); |
| } |
| |
| List<logic_card.Card> get _sortedCards { |
| assert(this.comparator != null); |
| List<logic_card.Card> cs = new List<logic_card.Card>(); |
| cs.addAll(this.cards); |
| cs.sort(comparator); |
| return cs; |
| } |
| |
| // returns null if it's up to the container (like a Flex) to figure this out. |
| double get desiredHeight { |
| switch (this.orientation) { |
| case Orientation.vert: |
| return null; |
| case Orientation.horz: |
| case Orientation.fan: |
| case Orientation.show1: |
| return _produceRowHeight; |
| case Orientation.suit: |
| return _produceRowHeight * 4 + _whiteLineHeight * 3; |
| default: |
| assert(false); |
| return null; |
| } |
| } |
| |
| // returns null if it's up to the container (like a Flex) to figure this out. |
| double get desiredWidth { |
| switch (this.orientation) { |
| case Orientation.vert: |
| case Orientation.show1: |
| return widthCard; |
| case Orientation.horz: |
| case Orientation.fan: |
| case Orientation.suit: |
| return this.width; |
| default: |
| assert(false); |
| return null; |
| } |
| } |
| |
| double get _produceRowHeight => heightCard + CARD_MARGIN * 2; |
| Widget _produceRow(List<Widget> cardWidgets, {emptyBackgroundImage: ""}) { |
| if (cardWidgets.length == 0) { |
| // Just return a centered background image. |
| return new Container( |
| decoration: new BoxDecoration(backgroundColor: this.backgroundColor), |
| height: _produceRowHeight, |
| width: this.width, |
| child: new Center( |
| child: new Opacity( |
| opacity: 0.45, |
| child: new Container( |
| height: heightCard, |
| child: emptyBackgroundImage == "" |
| ? null |
| : new NetworkImage(src: emptyBackgroundImage))))); |
| } |
| |
| // Let's do a stack of positioned cards! |
| List<Widget> kids = new List<Widget>(); |
| |
| double w = this.width ?? widthCard * 5; |
| double spacing = math.min(widthCard + CARD_MARGIN * 2, |
| (w - widthCard - 2 * CARD_MARGIN) / (cardWidgets.length - 1)); |
| |
| for (int i = 0; i < cardWidgets.length; i++) { |
| kids.add(new Positioned( |
| top: CARD_MARGIN, |
| left: CARD_MARGIN + spacing * i, |
| child: cardWidgets[i])); |
| } |
| return new Container( |
| decoration: new BoxDecoration(backgroundColor: this.backgroundColor), |
| height: _produceRowHeight, |
| width: this.width, |
| child: new Stack(kids)); |
| } |
| |
| double get _whiteLineHeight => WHITE_LINE_HEIGHT; |
| |
| Widget wrapCards(List<Widget> cardWidgets) { |
| switch (this.orientation) { |
| case Orientation.vert: |
| return new Flex(util.flexChildren(cardWidgets), |
| direction: FlexDirection.vertical); |
| case Orientation.horz: |
| return _produceRow(cardWidgets); |
| case Orientation.fan: |
| // unimplemented, so we'll fall through to show1, for now. |
| // Probably a Stack + Positioned |
| case Orientation.show1: |
| return new Stack(cardWidgets); |
| case Orientation.suit: |
| List<Widget> cs = new List<Widget>(); |
| List<Widget> ds = new List<Widget>(); |
| List<Widget> hs = new List<Widget>(); |
| List<Widget> ss = new List<Widget>(); |
| |
| List<logic_card.Card> theCards = |
| this.comparator != null ? this._sortedCards : this.cards; |
| for (int i = 0; i < theCards.length; i++) { |
| // Group by suit. Then sort. |
| logic_card.Card c = theCards[i]; |
| switch (c.identifier[0]) { |
| case 'c': |
| cs.add(cardWidgets[i]); |
| break; |
| case 'd': |
| ds.add(cardWidgets[i]); |
| break; |
| case 'h': |
| hs.add(cardWidgets[i]); |
| break; |
| case 's': |
| ss.add(cardWidgets[i]); |
| break; |
| default: |
| assert(false); |
| } |
| } |
| return new Container( |
| decoration: |
| new BoxDecoration(backgroundColor: material.Colors.white), |
| child: new Stack(<Widget>[ |
| new Positioned( |
| top: 0.0, |
| child: _produceRow(ss, |
| emptyBackgroundImage: "images/suits/Spade.png")), |
| new Positioned( |
| top: _produceRowHeight + _whiteLineHeight, |
| child: _produceRow(hs, |
| emptyBackgroundImage: "images/suits/Heart.png")), |
| new Positioned( |
| top: 2 * _produceRowHeight + 2 * _whiteLineHeight, |
| child: _produceRow(cs, |
| emptyBackgroundImage: "images/suits/Club.png")), |
| new Positioned( |
| top: 3 * _produceRowHeight + 3 * _whiteLineHeight, |
| child: _produceRow(ds, |
| emptyBackgroundImage: "images/suits/Diamond.png")) |
| ])); |
| default: |
| assert(false); |
| return null; |
| } |
| } |
| |
| Widget build() { |
| return _buildCollection(); |
| } |
| |
| Widget _buildCollection() { |
| List<Widget> cardComponents = new List<Widget>(); |
| List<logic_card.Card> cs = |
| this.comparator != null ? this._sortedCards : this.cards; |
| |
| for (int i = 0; i < cs.length; i++) { |
| component_card.Card c = new component_card.Card(cs[i], faceUp, |
| width: widthCard, height: heightCard); |
| |
| if (dragChildren) { |
| cardComponents.add(new Draggable<component_card.Card>(c)); |
| } else { |
| cardComponents.add(c); |
| } |
| } |
| |
| // Let's draw a stack of cards with DragTargets. |
| // TODO(alexfandrianto): In many cases, card collections shouldn't have draggable cards. |
| // Additionally, it may be worthwhile to restrict it to 1 at a time. |
| switch (this.acceptType) { |
| case DropType.none: |
| return new Container( |
| decoration: |
| new BoxDecoration(backgroundColor: this.backgroundColor), |
| height: this.desiredHeight, |
| width: this.desiredWidth, |
| child: wrapCards(cardComponents)); |
| case DropType.card: |
| return new DragTarget<component_card.Card>( |
| onWillAccept: _handleWillAccept, onAccept: _handleAccept, |
| builder: (List<component_card.Card> data, _) { |
| return new Container( |
| decoration: new BoxDecoration( |
| backgroundColor: |
| data.isEmpty ? this.backgroundColor : this.altColor), |
| height: this.desiredHeight, |
| width: this.desiredWidth, |
| child: wrapCards(cardComponents)); |
| }); |
| case DropType.card_collection: |
| return new DragTarget<CardCollectionComponent>( |
| onWillAccept: _handleWillAccept, onAccept: _handleAcceptMultiple, |
| builder: (List<CardCollectionComponent> data, _) { |
| return new Container( |
| decoration: new BoxDecoration( |
| backgroundColor: |
| data.isEmpty ? this.backgroundColor : this.altColor), |
| height: this.desiredHeight, |
| width: this.desiredWidth, |
| child: wrapCards(cardComponents)); |
| }); |
| } |
| } |
| } |