blob: 6ede30dd22a7e872a37c61f54ef96f0de5c738af [file] [log] [blame]
// 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 'dart:math' as math;
import 'package:flutter/material.dart';
enum CardCollectionOrientation { 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.
}
typedef double PosComputer(int index);
typedef void AcceptCb(dynamic data, List<logic_card.Card> cards);
const double DEFAULT_WIDTH = 200.0;
const double DEFAULT_HEIGHT = 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 {
final List<logic_card.Card> cards;
final CardCollectionOrientation orientation;
final bool faceUp;
final AcceptCb acceptCallback;
final bool dragChildren;
final DropType _acceptType;
final Comparator<logic_card.Card> comparator;
final double width;
final double height;
final double widthCard;
final double heightCard;
final Color _backgroundColor;
final Color _altColor;
final double rotation; // This angle is in radians.
final bool useKeys; // If set, every Card created in this collection will be keyed.
final component_card.CardAnimationType animationType;
DropType get acceptType => _acceptType ?? DropType.none;
Color get backgroundColor => _backgroundColor ?? Colors.grey[500];
Color get altColor => _altColor ?? Colors.grey[500];
CardCollectionComponent(this.cards, this.faceUp, this.orientation,
{this.dragChildren: false,
DropType acceptType,
this.acceptCallback: null,
this.comparator: null,
this.width: DEFAULT_WIDTH,
this.height: DEFAULT_HEIGHT,
this.widthCard: DEFAULT_CARD_WIDTH,
this.heightCard: DEFAULT_CARD_HEIGHT,
Color backgroundColor,
Color altColor,
this.rotation: 0.0,
this.useKeys: false,
this.animationType: component_card.CardAnimationType.NORMAL})
: _acceptType = acceptType,
_backgroundColor = backgroundColor,
_altColor = altColor;
CardCollectionComponentState createState() =>
new CardCollectionComponentState();
}
class CardCollectionComponentState extends State<CardCollectionComponent> {
String status = 'bar';
bool _handleWillAccept(component_card.Card data) {
return !config.cards.contains(data.card);
}
bool _handleWillAcceptMultiple(CardCollectionComponent data) {
return data != config; // don't accept your own self.
}
void _handleAccept(component_card.Card data) {
print('accept');
setState(() {
status = 'ACCEPT ${data.card.toString()}';
config.acceptCallback(data.card, config.cards);
});
}
void _handleAcceptMultiple(CardCollectionComponent data) {
print('acceptMulti');
setState(() {
status = 'ACCEPT multi: ${data.cards.toString()}';
config.acceptCallback(data.cards, config.cards);
});
}
List<logic_card.Card> get _sortedCards {
assert(config.comparator != null);
List<logic_card.Card> cs = new List<logic_card.Card>();
cs.addAll(config.cards);
cs.sort(config.comparator);
return cs;
}
// returns null if it's up to the container (like a Flex) to figure this out.
double get desiredHeight {
switch (config.orientation) {
case CardCollectionOrientation.vert:
return config.height;
case CardCollectionOrientation.horz:
case CardCollectionOrientation.fan:
case CardCollectionOrientation.show1:
return _produceRowHeight;
case CardCollectionOrientation.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 (config.orientation) {
case CardCollectionOrientation.vert:
case CardCollectionOrientation.show1:
return _produceColumnWidth;
case CardCollectionOrientation.horz:
case CardCollectionOrientation.fan:
case CardCollectionOrientation.suit:
return config.width;
default:
assert(false);
return null;
}
}
List<Widget> _makeDraggableAndPositioned(
List<component_card.Card> cardWidgets,
PosComputer topComputer,
PosComputer leftComputer) {
List<Widget> ret = new List<Widget>();
for (int i = 0; i < cardWidgets.length; i++) {
Point p = new Point(leftComputer(i), topComputer(i));
component_card.Card w = cardWidgets[i];
Widget widgetToAdd = w;
if (config.dragChildren) {
widgetToAdd = new Draggable(
child: w,
data: w,
feedback: new Opacity(child: w.clone(visible: true), opacity: 0.5));
}
widgetToAdd = new Positioned(left: p.x, top: p.y, child: widgetToAdd);
ret.add(widgetToAdd);
}
return ret;
}
double get _produceColumnWidth => config.widthCard + CARD_MARGIN * 2;
Widget _produceColumn(List<component_card.Card> cardWidgets) {
double h = config.height ?? config.heightCard * 5;
double spacing = math.min(config.heightCard + CARD_MARGIN * 2,
(h - config.heightCard - 2 * CARD_MARGIN) / (cardWidgets.length - 1));
PosComputer topComputer = (int i) => CARD_MARGIN + spacing * i;
PosComputer leftComputer = (int i) => CARD_MARGIN;
List<Widget> draggableKids =
_makeDraggableAndPositioned(cardWidgets, topComputer, leftComputer);
return new Container(
decoration: new BoxDecoration(backgroundColor: config.backgroundColor),
height: config.height,
width: _produceColumnWidth,
child: new Stack(draggableKids));
}
double get _produceRowHeight => config.heightCard + CARD_MARGIN * 2;
Widget _produceRow(List<component_card.Card> cardWidgets,
{emptyBackgroundImage: ""}) {
if (cardWidgets.length == 0) {
// Just return a centered background image.
return new Container(
decoration:
new BoxDecoration(backgroundColor: config.backgroundColor),
height: _produceRowHeight,
width: config.width,
child: new Center(
child: new Opacity(
opacity: 0.45,
child: emptyBackgroundImage == ""
? null
: new AssetImage(
name: emptyBackgroundImage,
fit: ImageFit.scaleDown,
height: config.heightCard))));
}
double w = config.width ?? config.widthCard * 5;
double spacing = math.min(config.widthCard + CARD_MARGIN * 2,
(w - config.widthCard - 2 * CARD_MARGIN) / (cardWidgets.length - 1));
PosComputer topComputer = (int i) => CARD_MARGIN;
PosComputer leftComputer = (int i) => CARD_MARGIN + spacing * i;
List<Widget> draggableKids =
_makeDraggableAndPositioned(cardWidgets, topComputer, leftComputer);
return new Container(
decoration: new BoxDecoration(backgroundColor: config.backgroundColor),
height: _produceRowHeight,
width: config.width,
child: new Stack(draggableKids));
}
Widget _produceSingle(List<component_card.Card> cardWidgets) {
PosComputer topComputer = (int i) => CARD_MARGIN;
PosComputer leftComputer = (int i) => CARD_MARGIN;
List<Widget> draggableKids =
_makeDraggableAndPositioned(cardWidgets, topComputer, leftComputer);
return new Container(
decoration: new BoxDecoration(backgroundColor: config.backgroundColor),
height: _produceRowHeight,
width: _produceColumnWidth,
child: new Stack(draggableKids));
}
double get _whiteLineHeight => WHITE_LINE_HEIGHT;
Widget wrapCards(List<component_card.Card> cardWidgets) {
switch (config.orientation) {
case CardCollectionOrientation.vert:
return _produceColumn(cardWidgets);
case CardCollectionOrientation.horz:
return _produceRow(cardWidgets);
case CardCollectionOrientation.fan:
// unimplemented, so we'll fall through to show1, for now.
// Probably a Stack + Positioned
case CardCollectionOrientation.show1:
return _produceSingle(cardWidgets);
case CardCollectionOrientation.suit:
List<component_card.Card> cs = new List<component_card.Card>();
List<component_card.Card> ds = new List<component_card.Card>();
List<component_card.Card> hs = new List<component_card.Card>();
List<component_card.Card> ss = new List<component_card.Card>();
List<logic_card.Card> theCards =
config.comparator != null ? this._sortedCards : config.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: Colors.white),
child: new Stack(<Widget>[
new Positioned(
top: 2 * _produceRowHeight + 2 * _whiteLineHeight,
child: _produceRow(ss,
emptyBackgroundImage: "images/suits/Spade.png")),
new Positioned(
top: 3 * _produceRowHeight + 3 * _whiteLineHeight,
child: _produceRow(hs,
emptyBackgroundImage: "images/suits/Heart.png")),
new Positioned(
top: 0.0,
child: _produceRow(cs,
emptyBackgroundImage: "images/suits/Club.png")),
new Positioned(
top: _produceRowHeight + _whiteLineHeight,
child: _produceRow(ds,
emptyBackgroundImage: "images/suits/Diamond.png"))
]));
default:
assert(false);
return null;
}
}
Widget build(BuildContext context) {
return _buildCollection();
}
Widget _buildCollection() {
List<component_card.Card> cardComponents = new List<component_card.Card>();
List<logic_card.Card> cs =
config.comparator != null ? this._sortedCards : config.cards;
for (int i = 0; i < cs.length; i++) {
component_card.Card c = new component_card.Card(cs[i], config.faceUp,
width: config.widthCard,
height: config.heightCard,
rotation: config.rotation,
visible:
!config.useKeys, // TODO(alexfandrianto): Is there a case where you want an invisible card and a key?
useKey: config.useKeys,
z: 0.0 + i,
animationType: config.animationType);
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 (config.acceptType) {
case DropType.none:
return new Container(
decoration:
new BoxDecoration(backgroundColor: config.backgroundColor),
height: this.desiredHeight,
width: this.desiredWidth,
child: wrapCards(cardComponents));
case DropType.card:
return new DragTarget<component_card.Card>(
onWillAccept: _handleWillAccept, onAccept: _handleAccept,
builder: (BuildContext context, List<component_card.Card> data, _) {
return new Container(
decoration: new BoxDecoration(
backgroundColor:
data.isEmpty ? config.backgroundColor : config.altColor),
height: this.desiredHeight,
width: this.desiredWidth,
child: wrapCards(cardComponents));
});
case DropType.card_collection:
return new DragTarget<CardCollectionComponent>(
onWillAccept: _handleWillAcceptMultiple,
onAccept: _handleAcceptMultiple, builder:
(BuildContext context, List<CardCollectionComponent> data, _) {
return new Container(
decoration: new BoxDecoration(
backgroundColor:
data.isEmpty ? config.backgroundColor : config.altColor),
height: this.desiredHeight,
width: this.desiredWidth,
child: wrapCards(cardComponents));
});
default:
assert(false);
return null;
}
}
}