mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-04-27 07:48:01 -07:00
* Make cards rounded Magic cards have rounded corners, and playing cards tend to have rounded corners as well, but Cockatrice currently displays rectangular cards. This can cause visual glitches when using image scans where the border does not extend in the corner, and for this reason Cockatrice always draws a (rectangular) border around the card to try and make it look a bit better. In this patch I take a different approach: rather than try to make rounded pegs, er, cards, go into a square hole, the hole is now rounded. More precisely, the AbstractCardItem now has a rounded rectangular shape (with a corner of 5% of the width of the card, identical to that of modern M:TG physical cards). As a side effect, the card drawing gets a bit simplified by getting rid of transformPainter() when drawing the card outline and using the QPainter::drawPixmap overloads that takes a target QRectF instead. This means we no longer have to bother about card rotation when painting since that's taken care of by the Graphics View framework (which transformPainter() undoes). * format * Also give PileZone rounded corners * Forgot untap status + bits of CardDragItem * fix deckviewcard calculations * Rounded CardInfoPicture
520 lines
17 KiB
C++
520 lines
17 KiB
C++
#include "deckview.h"
|
|
|
|
#include "carddatabase.h"
|
|
#include "decklist.h"
|
|
#include "main.h"
|
|
#include "settingscache.h"
|
|
#include "thememanager.h"
|
|
|
|
#include <QApplication>
|
|
#include <QGraphicsSceneMouseEvent>
|
|
#include <QMouseEvent>
|
|
#include <QtMath>
|
|
#include <algorithm>
|
|
|
|
DeckViewCardDragItem::DeckViewCardDragItem(DeckViewCard *_item,
|
|
const QPointF &_hotSpot,
|
|
AbstractCardDragItem *parentDrag)
|
|
: AbstractCardDragItem(_item, _hotSpot, parentDrag), currentZone(0)
|
|
{
|
|
}
|
|
|
|
void DeckViewCardDragItem::updatePosition(const QPointF &cursorScenePos)
|
|
{
|
|
QList<QGraphicsItem *> colliding = scene()->items(cursorScenePos);
|
|
|
|
DeckViewCardContainer *cursorZone = 0;
|
|
for (int i = colliding.size() - 1; i >= 0; i--)
|
|
if ((cursorZone = qgraphicsitem_cast<DeckViewCardContainer *>(colliding.at(i))))
|
|
break;
|
|
if (!cursorZone)
|
|
return;
|
|
currentZone = cursorZone;
|
|
|
|
QPointF newPos = cursorScenePos;
|
|
if (newPos != pos()) {
|
|
for (int i = 0; i < childDrags.size(); i++)
|
|
childDrags[i]->setPos(newPos + childDrags[i]->getHotSpot());
|
|
setPos(newPos);
|
|
}
|
|
}
|
|
|
|
void DeckViewCardDragItem::handleDrop(DeckViewCardContainer *target)
|
|
{
|
|
DeckViewCard *card = static_cast<DeckViewCard *>(item);
|
|
DeckViewCardContainer *start = static_cast<DeckViewCardContainer *>(item->parentItem());
|
|
start->removeCard(card);
|
|
target->addCard(card);
|
|
card->setParentItem(target);
|
|
}
|
|
|
|
void DeckViewCardDragItem::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
|
{
|
|
setCursor(Qt::OpenHandCursor);
|
|
DeckViewScene *sc = static_cast<DeckViewScene *>(scene());
|
|
sc->removeItem(this);
|
|
|
|
if (currentZone) {
|
|
handleDrop(currentZone);
|
|
for (int i = 0; i < childDrags.size(); i++) {
|
|
DeckViewCardDragItem *c = static_cast<DeckViewCardDragItem *>(childDrags[i]);
|
|
c->handleDrop(currentZone);
|
|
sc->removeItem(c);
|
|
}
|
|
|
|
sc->updateContents();
|
|
}
|
|
|
|
event->accept();
|
|
}
|
|
|
|
DeckViewCard::DeckViewCard(const QString &_name, const QString &_originZone, QGraphicsItem *parent)
|
|
: AbstractCardItem(_name, 0, -1, parent), originZone(_originZone), dragItem(0)
|
|
{
|
|
setAcceptHoverEvents(true);
|
|
}
|
|
|
|
DeckViewCard::~DeckViewCard()
|
|
{
|
|
delete dragItem;
|
|
}
|
|
|
|
void DeckViewCard::paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget)
|
|
{
|
|
AbstractCardItem::paint(painter, option, widget);
|
|
|
|
painter->save();
|
|
QPen pen;
|
|
pen.setWidth(3);
|
|
pen.setJoinStyle(Qt::MiterJoin);
|
|
pen.setColor(originZone == DECK_ZONE_MAIN ? Qt::green : Qt::red);
|
|
painter->setPen(pen);
|
|
qreal cardRadius = 0.05 * (CARD_WIDTH - 3);
|
|
painter->drawRoundedRect(QRectF(1.5, 1.5, CARD_WIDTH - 3., CARD_HEIGHT - 3.), cardRadius, cardRadius);
|
|
painter->restore();
|
|
}
|
|
|
|
void DeckViewCard::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
|
{
|
|
if ((event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton)).manhattanLength() <
|
|
2 * QApplication::startDragDistance())
|
|
return;
|
|
|
|
if (static_cast<DeckViewScene *>(scene())->getLocked())
|
|
return;
|
|
|
|
delete dragItem;
|
|
dragItem = new DeckViewCardDragItem(this, event->pos());
|
|
scene()->addItem(dragItem);
|
|
dragItem->updatePosition(event->scenePos());
|
|
dragItem->grabMouse();
|
|
|
|
QList<QGraphicsItem *> sel = scene()->selectedItems();
|
|
int j = 0;
|
|
for (int i = 0; i < sel.size(); i++) {
|
|
DeckViewCard *c = static_cast<DeckViewCard *>(sel.at(i));
|
|
if (c == this)
|
|
continue;
|
|
++j;
|
|
QPointF childPos = QPointF(j * CARD_WIDTH / 2, 0);
|
|
DeckViewCardDragItem *drag = new DeckViewCardDragItem(c, childPos, dragItem);
|
|
drag->setPos(dragItem->pos() + childPos);
|
|
scene()->addItem(drag);
|
|
}
|
|
setCursor(Qt::OpenHandCursor);
|
|
}
|
|
|
|
void DeckView::mouseDoubleClickEvent(QMouseEvent *event)
|
|
{
|
|
if (static_cast<DeckViewScene *>(scene())->getLocked())
|
|
return;
|
|
|
|
if (event->button() == Qt::LeftButton) {
|
|
QList<MoveCard_ToZone> result;
|
|
QList<QGraphicsItem *> sel = scene()->selectedItems();
|
|
|
|
for (int i = 0; i < sel.size(); i++) {
|
|
DeckViewCard *c = static_cast<DeckViewCard *>(sel.at(i));
|
|
DeckViewCardContainer *zone = static_cast<DeckViewCardContainer *>(c->parentItem());
|
|
MoveCard_ToZone m;
|
|
m.set_card_name(c->getName().toStdString());
|
|
m.set_start_zone(zone->getName().toStdString());
|
|
|
|
if (zone->getName() == DECK_ZONE_MAIN)
|
|
m.set_target_zone(DECK_ZONE_SIDE);
|
|
else if (zone->getName() == DECK_ZONE_SIDE)
|
|
m.set_target_zone(DECK_ZONE_MAIN);
|
|
else // Trying to move from another zone
|
|
m.set_target_zone(zone->getName().toStdString());
|
|
|
|
result.append(m);
|
|
}
|
|
|
|
deckViewScene->applySideboardPlan(result);
|
|
deckViewScene->rearrangeItems();
|
|
emit deckViewScene->sideboardPlanChanged();
|
|
}
|
|
}
|
|
|
|
void DeckViewCard::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
|
|
{
|
|
event->accept();
|
|
processHoverEvent();
|
|
}
|
|
|
|
DeckViewCardContainer::DeckViewCardContainer(const QString &_name) : QGraphicsItem(), name(_name), width(0), height(0)
|
|
{
|
|
setCacheMode(DeviceCoordinateCache);
|
|
}
|
|
|
|
QRectF DeckViewCardContainer::boundingRect() const
|
|
{
|
|
return QRectF(0, 0, width, height);
|
|
}
|
|
|
|
void DeckViewCardContainer::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
|
{
|
|
qreal totalTextWidth = getCardTypeTextWidth();
|
|
|
|
painter->fillRect(boundingRect(), themeManager->getTableBgBrush());
|
|
painter->setPen(QColor(255, 255, 255, 100));
|
|
painter->drawLine(QPointF(0, separatorY), QPointF(width, separatorY));
|
|
|
|
painter->setPen(QColor(Qt::white));
|
|
QFont f("Serif");
|
|
f.setStyleHint(QFont::Serif);
|
|
f.setPixelSize(24);
|
|
f.setWeight(QFont::Bold);
|
|
painter->setFont(f);
|
|
painter->drawText(10, 0, width - 20, separatorY, Qt::AlignLeft | Qt::AlignVCenter | Qt::TextSingleLine,
|
|
InnerDecklistNode::visibleNameFromName(name) + QString(": %1").arg(cards.size()));
|
|
|
|
f.setPixelSize(16);
|
|
painter->setFont(f);
|
|
QList<QString> cardTypeList = cardsByType.uniqueKeys();
|
|
qreal yUntilNow = separatorY + paddingY;
|
|
for (int i = 0; i < cardTypeList.size(); ++i) {
|
|
if (i != 0) {
|
|
painter->setPen(QColor(255, 255, 255, 100));
|
|
painter->drawLine(QPointF(0, yUntilNow - paddingY / 2), QPointF(width, yUntilNow - paddingY / 2));
|
|
}
|
|
qreal thisRowHeight = CARD_HEIGHT * currentRowsAndCols[i].first;
|
|
QRectF textRect(0, yUntilNow, totalTextWidth, thisRowHeight);
|
|
yUntilNow += thisRowHeight + paddingY;
|
|
|
|
QString displayString = QString("%1\n(%2)").arg(cardTypeList[i]).arg(cardsByType.count(cardTypeList[i]));
|
|
|
|
painter->setPen(Qt::white);
|
|
painter->drawText(textRect, Qt::AlignHCenter | Qt::AlignVCenter, displayString);
|
|
}
|
|
}
|
|
|
|
void DeckViewCardContainer::addCard(DeckViewCard *card)
|
|
{
|
|
cards.append(card);
|
|
cardsByType.insert(card->getInfo() ? card->getInfo()->getMainCardType() : "", card);
|
|
}
|
|
|
|
void DeckViewCardContainer::removeCard(DeckViewCard *card)
|
|
{
|
|
cards.removeOne(card);
|
|
cardsByType.remove(card->getInfo() ? card->getInfo()->getMainCardType() : "", card);
|
|
}
|
|
|
|
QList<QPair<int, int>> DeckViewCardContainer::getRowsAndCols() const
|
|
{
|
|
QList<QPair<int, int>> result;
|
|
QList<QString> cardTypeList = cardsByType.uniqueKeys();
|
|
for (int i = 0; i < cardTypeList.size(); ++i)
|
|
result.append(QPair<int, int>(1, cardsByType.count(cardTypeList[i])));
|
|
return result;
|
|
}
|
|
|
|
int DeckViewCardContainer::getCardTypeTextWidth() const
|
|
{
|
|
QFont f("Serif");
|
|
f.setStyleHint(QFont::Serif);
|
|
f.setPixelSize(16);
|
|
f.setWeight(QFont::Bold);
|
|
QFontMetrics fm(f);
|
|
|
|
int maxCardTypeWidth = 0;
|
|
for (const auto &key : cardsByType.keys()) {
|
|
int cardTypeWidth = fm.size(Qt::TextSingleLine, key).width();
|
|
maxCardTypeWidth = qMax(maxCardTypeWidth, cardTypeWidth);
|
|
}
|
|
|
|
return maxCardTypeWidth + 15;
|
|
}
|
|
|
|
QSizeF DeckViewCardContainer::calculateBoundingRect(const QList<QPair<int, int>> &rowsAndCols) const
|
|
{
|
|
qreal totalHeight = 0;
|
|
qreal totalWidth = 0;
|
|
|
|
// Calculate space needed for cards
|
|
for (int i = 0; i < rowsAndCols.size(); ++i) {
|
|
totalHeight += CARD_HEIGHT * rowsAndCols[i].first + paddingY;
|
|
if (CARD_WIDTH * rowsAndCols[i].second > totalWidth)
|
|
totalWidth = CARD_WIDTH * rowsAndCols[i].second;
|
|
}
|
|
|
|
return QSizeF(getCardTypeTextWidth() + totalWidth, totalHeight + separatorY + paddingY);
|
|
}
|
|
|
|
bool DeckViewCardContainer::sortCardsByName(DeckViewCard *c1, DeckViewCard *c2)
|
|
{
|
|
if (c1 && c2)
|
|
return c1->getName() < c2->getName();
|
|
return false;
|
|
}
|
|
|
|
void DeckViewCardContainer::rearrangeItems(const QList<QPair<int, int>> &rowsAndCols)
|
|
{
|
|
currentRowsAndCols = rowsAndCols;
|
|
|
|
qreal yUntilNow = separatorY + paddingY;
|
|
qreal x = (qreal)getCardTypeTextWidth();
|
|
for (int i = 0; i < rowsAndCols.size(); ++i) {
|
|
const int tempRows = rowsAndCols[i].first;
|
|
const int tempCols = rowsAndCols[i].second;
|
|
QList<QString> cardTypeList = cardsByType.uniqueKeys();
|
|
QList<DeckViewCard *> row = cardsByType.values(cardTypeList[i]);
|
|
std::sort(row.begin(), row.end(), DeckViewCardContainer::sortCardsByName);
|
|
for (int j = 0; j < row.size(); ++j) {
|
|
DeckViewCard *card = row[j];
|
|
card->setPos(x + (j % tempCols) * CARD_WIDTH, yUntilNow + (j / tempCols) * CARD_HEIGHT);
|
|
}
|
|
yUntilNow += tempRows * CARD_HEIGHT + paddingY;
|
|
}
|
|
|
|
prepareGeometryChange();
|
|
QSizeF bRect = calculateBoundingRect(rowsAndCols);
|
|
width = bRect.width();
|
|
height = bRect.height();
|
|
}
|
|
|
|
void DeckViewCardContainer::setWidth(qreal _width)
|
|
{
|
|
prepareGeometryChange();
|
|
width = _width;
|
|
update();
|
|
}
|
|
|
|
DeckViewScene::DeckViewScene(QObject *parent) : QGraphicsScene(parent), locked(true), deck(0), optimalAspectRatio(1.0)
|
|
{
|
|
}
|
|
|
|
DeckViewScene::~DeckViewScene()
|
|
{
|
|
clearContents();
|
|
delete deck;
|
|
}
|
|
|
|
void DeckViewScene::clearContents()
|
|
{
|
|
QMapIterator<QString, DeckViewCardContainer *> i(cardContainers);
|
|
while (i.hasNext())
|
|
delete i.next().value();
|
|
cardContainers.clear();
|
|
}
|
|
|
|
void DeckViewScene::setDeck(const DeckList &_deck)
|
|
{
|
|
if (deck)
|
|
delete deck;
|
|
|
|
deck = new DeckList(_deck);
|
|
rebuildTree();
|
|
applySideboardPlan(deck->getCurrentSideboardPlan());
|
|
rearrangeItems();
|
|
}
|
|
|
|
void DeckViewScene::rebuildTree()
|
|
{
|
|
clearContents();
|
|
|
|
if (!deck)
|
|
return;
|
|
|
|
InnerDecklistNode *listRoot = deck->getRoot();
|
|
for (int i = 0; i < listRoot->size(); i++) {
|
|
InnerDecklistNode *currentZone = dynamic_cast<InnerDecklistNode *>(listRoot->at(i));
|
|
|
|
DeckViewCardContainer *container = cardContainers.value(currentZone->getName(), 0);
|
|
if (!container) {
|
|
container = new DeckViewCardContainer(currentZone->getName());
|
|
cardContainers.insert(currentZone->getName(), container);
|
|
addItem(container);
|
|
}
|
|
|
|
for (int j = 0; j < currentZone->size(); j++) {
|
|
DecklistCardNode *currentCard = dynamic_cast<DecklistCardNode *>(currentZone->at(j));
|
|
if (!currentCard)
|
|
continue;
|
|
|
|
for (int k = 0; k < currentCard->getNumber(); ++k) {
|
|
DeckViewCard *newCard = new DeckViewCard(currentCard->getName(), currentZone->getName(), container);
|
|
container->addCard(newCard);
|
|
emit newCardAdded(newCard);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
void DeckViewScene::applySideboardPlan(const QList<MoveCard_ToZone> &plan)
|
|
{
|
|
for (int i = 0; i < plan.size(); ++i) {
|
|
const MoveCard_ToZone &m = plan[i];
|
|
DeckViewCardContainer *start = cardContainers.value(QString::fromStdString(m.start_zone()));
|
|
DeckViewCardContainer *target = cardContainers.value(QString::fromStdString(m.target_zone()));
|
|
if (!start || !target)
|
|
continue;
|
|
|
|
DeckViewCard *card = 0;
|
|
const QList<DeckViewCard *> &cardList = start->getCards();
|
|
for (int j = 0; j < cardList.size(); ++j)
|
|
if (cardList[j]->getName() == QString::fromStdString(m.card_name())) {
|
|
card = cardList[j];
|
|
break;
|
|
}
|
|
if (!card)
|
|
continue;
|
|
|
|
start->removeCard(card);
|
|
target->addCard(card);
|
|
card->setParentItem(target);
|
|
}
|
|
}
|
|
|
|
void DeckViewScene::rearrangeItems()
|
|
{
|
|
const int spacing = CARD_HEIGHT / 3;
|
|
QList<DeckViewCardContainer *> contList = cardContainers.values();
|
|
|
|
// Initialize space requirements
|
|
QList<QList<QPair<int, int>>> rowsAndColsList;
|
|
QList<QList<int>> cardCountList;
|
|
for (int i = 0; i < contList.size(); ++i) {
|
|
QList<QPair<int, int>> rowsAndCols = contList[i]->getRowsAndCols();
|
|
rowsAndColsList.append(rowsAndCols);
|
|
|
|
cardCountList.append(QList<int>());
|
|
for (int j = 0; j < rowsAndCols.size(); ++j)
|
|
cardCountList[i].append(rowsAndCols[j].second);
|
|
}
|
|
|
|
qreal totalHeight, totalWidth;
|
|
for (;;) {
|
|
// Calculate total size before this iteration
|
|
totalHeight = -spacing;
|
|
totalWidth = 0;
|
|
for (int i = 0; i < contList.size(); ++i) {
|
|
QSizeF contSize = contList[i]->calculateBoundingRect(rowsAndColsList[i]);
|
|
totalHeight += contSize.height() + spacing;
|
|
if (contSize.width() > totalWidth)
|
|
totalWidth = contSize.width();
|
|
}
|
|
|
|
// We're done when the aspect ratio shifts from too high to too low.
|
|
if (totalWidth / totalHeight <= optimalAspectRatio)
|
|
break;
|
|
|
|
// Find category with highest column count
|
|
int maxIndex1 = -1, maxIndex2 = -1, maxCols = 0;
|
|
for (int i = 0; i < rowsAndColsList.size(); ++i)
|
|
for (int j = 0; j < rowsAndColsList[i].size(); ++j)
|
|
if (rowsAndColsList[i][j].second > maxCols) {
|
|
maxIndex1 = i;
|
|
maxIndex2 = j;
|
|
maxCols = rowsAndColsList[i][j].second;
|
|
}
|
|
|
|
// Add row to category
|
|
const int maxRows = rowsAndColsList[maxIndex1][maxIndex2].first;
|
|
const int maxCardCount = cardCountList[maxIndex1][maxIndex2];
|
|
rowsAndColsList[maxIndex1][maxIndex2] =
|
|
QPair<int, int>(maxRows + 1, (int)qCeil((qreal)maxCardCount / (qreal)(maxRows + 1)));
|
|
}
|
|
|
|
totalHeight = -spacing;
|
|
for (int i = 0; i < contList.size(); ++i) {
|
|
DeckViewCardContainer *c = contList[i];
|
|
c->rearrangeItems(rowsAndColsList[i]);
|
|
c->setPos(0, totalHeight + spacing);
|
|
totalHeight += c->boundingRect().height() + spacing;
|
|
}
|
|
|
|
totalWidth = totalHeight * optimalAspectRatio;
|
|
for (int i = 0; i < contList.size(); ++i)
|
|
contList[i]->setWidth(totalWidth);
|
|
|
|
setSceneRect(QRectF(0, 0, totalWidth, totalHeight));
|
|
}
|
|
|
|
void DeckViewScene::updateContents()
|
|
{
|
|
rearrangeItems();
|
|
emit sideboardPlanChanged();
|
|
}
|
|
|
|
QList<MoveCard_ToZone> DeckViewScene::getSideboardPlan() const
|
|
{
|
|
QList<MoveCard_ToZone> result;
|
|
QMapIterator<QString, DeckViewCardContainer *> containerIterator(cardContainers);
|
|
while (containerIterator.hasNext()) {
|
|
DeckViewCardContainer *cont = containerIterator.next().value();
|
|
const QList<DeckViewCard *> cardList = cont->getCards();
|
|
for (int i = 0; i < cardList.size(); ++i)
|
|
if (cardList[i]->getOriginZone() != cont->getName()) {
|
|
MoveCard_ToZone m;
|
|
m.set_card_name(cardList[i]->getName().toStdString());
|
|
m.set_start_zone(cardList[i]->getOriginZone().toStdString());
|
|
m.set_target_zone(cont->getName().toStdString());
|
|
result.append(m);
|
|
}
|
|
}
|
|
return result;
|
|
}
|
|
|
|
void DeckViewScene::resetSideboardPlan()
|
|
{
|
|
rebuildTree();
|
|
rearrangeItems();
|
|
}
|
|
|
|
DeckView::DeckView(QWidget *parent) : QGraphicsView(parent)
|
|
{
|
|
deckViewScene = new DeckViewScene(this);
|
|
|
|
setBackgroundBrush(QBrush(QColor(0, 0, 0)));
|
|
setDragMode(RubberBandDrag);
|
|
setRenderHints(QPainter::TextAntialiasing | QPainter::Antialiasing /* | QPainter::SmoothPixmapTransform*/);
|
|
setScene(deckViewScene);
|
|
|
|
connect(deckViewScene, SIGNAL(sceneRectChanged(const QRectF &)), this, SLOT(updateSceneRect(const QRectF &)));
|
|
connect(deckViewScene, SIGNAL(newCardAdded(AbstractCardItem *)), this, SIGNAL(newCardAdded(AbstractCardItem *)));
|
|
connect(deckViewScene, SIGNAL(sideboardPlanChanged()), this, SIGNAL(sideboardPlanChanged()));
|
|
}
|
|
|
|
void DeckView::resizeEvent(QResizeEvent *event)
|
|
{
|
|
QGraphicsView::resizeEvent(event);
|
|
deckViewScene->setOptimalAspectRatio((qreal)width() / (qreal)height());
|
|
deckViewScene->rearrangeItems();
|
|
}
|
|
|
|
void DeckView::updateSceneRect(const QRectF &rect)
|
|
{
|
|
fitInView(rect, Qt::KeepAspectRatio);
|
|
}
|
|
|
|
void DeckView::setDeck(const DeckList &_deck)
|
|
{
|
|
deckViewScene->setDeck(_deck);
|
|
}
|
|
|
|
void DeckView::resetSideboardPlan()
|
|
{
|
|
deckViewScene->resetSideboardPlan();
|
|
}
|