Cockatrice/cockatrice/src/game/deckview/deck_view.cpp
Basile Clement 2739550087
Use enum for ThemeManager brushes (#5730)
* Use enum for ThemeManager brushes

This patch introduces an enum to distinguish the different brushes that
can be set by the theme (hand, stack, etc.) and generic functions taking
the enum rather than having one copy of each function for each brush.

This is preliminary work before merging StackZone and HandZone to
simplify #4974.

* Include <array> header

* Header spacing
2025-03-16 23:01:25 +00:00

522 lines
No EOL
17 KiB
C++

#include "deck_view.h"
#include "../../client/ui/theme_manager.h"
#include "../../game/cards/card_database.h"
#include "../../settings/cache_settings.h"
#include "decklist.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(QGraphicsItem *parent,
const QString &_name,
const QString &_providerId,
const QString &_originZone)
: AbstractCardItem(parent, _name, _providerId, 0, -1), 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();
}
}
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->getBgBrush(ThemeManager::Table));
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(container, currentCard->getName(),
currentCard->getCardProviderId(), currentZone->getName());
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, &DeckViewScene::sceneRectChanged, this, &DeckView::updateSceneRect);
connect(deckViewScene, &DeckViewScene::newCardAdded, this, &DeckView::newCardAdded);
connect(deckViewScene, &DeckViewScene::sideboardPlanChanged, this, &DeckView::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::clearDeck()
{
deckViewScene->clearContents();
}
void DeckView::resetSideboardPlan()
{
deckViewScene->resetSideboardPlan();
}