mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-06-15 11:38:49 -07:00
[Game/Zones] Simple move refactor to differentiate between logic and graphics for zones (#6903)
* [Game/Zones] Simple move refactor to differentiate between logic and graphics for zones Took 21 minutes * Clean up game/zones/logic folder. Took 6 minutes * Adjust tests. Took 3 minutes --------- Co-authored-by: Lukas Brübach <Bruebach.Lukas@bdosecurity.de>
This commit is contained in:
parent
bb1a5b33a1
commit
cba9ce2b2b
47 changed files with 107 additions and 107 deletions
65
cockatrice/src/game_graphics/zones/card_zone.cpp
Normal file
65
cockatrice/src/game_graphics/zones/card_zone.cpp
Normal file
|
|
@ -0,0 +1,65 @@
|
|||
#include "card_zone.h"
|
||||
|
||||
#include "../../game/board/card_item.h"
|
||||
#include "view_zone.h"
|
||||
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QMenu>
|
||||
|
||||
CardZone::CardZone(CardZoneLogic *_logic, QGraphicsItem *parent)
|
||||
: AbstractGraphicsItem(parent), menu(nullptr), doubleClickAction(0), logic(_logic)
|
||||
{
|
||||
connect(logic, &CardZoneLogic::retranslateUi, this, &CardZone::retranslateUi);
|
||||
connect(logic, &CardZoneLogic::cardAdded, this, &CardZone::onCardAdded);
|
||||
connect(logic, &CardZoneLogic::setGraphicsVisibility, this, [this](bool v) { this->setVisible(v); });
|
||||
connect(logic, &CardZoneLogic::updateGraphics, this, [this]() { update(); });
|
||||
connect(logic, &CardZoneLogic::reorganizeCards, this, &CardZone::reorganizeCards);
|
||||
}
|
||||
|
||||
void CardZone::onCardAdded(CardItem *addedCard)
|
||||
{
|
||||
addedCard->setParentItem(this);
|
||||
addedCard->setVisible(true);
|
||||
addedCard->update();
|
||||
}
|
||||
|
||||
void CardZone::retranslateUi()
|
||||
{
|
||||
for (int i = 0; i < getLogic()->getCards().size(); ++i) {
|
||||
getLogic()->getCards()[i]->retranslateUi();
|
||||
}
|
||||
}
|
||||
|
||||
void CardZone::mouseDoubleClickEvent(QGraphicsSceneMouseEvent * /*event*/)
|
||||
{
|
||||
if (doubleClickAction) {
|
||||
doubleClickAction->trigger();
|
||||
}
|
||||
}
|
||||
|
||||
bool CardZone::showContextMenu(const QPoint &screenPos)
|
||||
{
|
||||
if (menu) {
|
||||
menu->exec(screenPos);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
void CardZone::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::RightButton) {
|
||||
if (showContextMenu(event->screenPos())) {
|
||||
event->accept();
|
||||
} else {
|
||||
event->ignore();
|
||||
}
|
||||
} else {
|
||||
event->ignore();
|
||||
}
|
||||
}
|
||||
|
||||
QPointF CardZone::closestGridPoint(const QPointF &point)
|
||||
{
|
||||
return point;
|
||||
}
|
||||
77
cockatrice/src/game_graphics/zones/card_zone.h
Normal file
77
cockatrice/src/game_graphics/zones/card_zone.h
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
/**
|
||||
* @file card_zone.h
|
||||
* @ingroup GameGraphicsZones
|
||||
* @brief Base graphics item for zones that contain cards.
|
||||
*/
|
||||
|
||||
#ifndef CARDZONE_H
|
||||
#define CARDZONE_H
|
||||
|
||||
#include "../../game/zones/card_zone_logic.h"
|
||||
#include "../board/abstract_graphics_item.h"
|
||||
#include "../board/graphics_item_type.h"
|
||||
|
||||
#include <QLoggingCategory>
|
||||
#include <QString>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(CardZoneLog, "card_zone");
|
||||
|
||||
/**
|
||||
* A zone in the game that can contain cards.
|
||||
* This class contains methods to get and modify the cards that are contained inside this zone.
|
||||
*
|
||||
* The cards are stored as a list of `CardItem*`.
|
||||
*/
|
||||
class CardZone : public AbstractGraphicsItem
|
||||
{
|
||||
Q_OBJECT
|
||||
protected:
|
||||
QMenu *menu;
|
||||
QAction *doubleClickAction;
|
||||
|
||||
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
virtual void reorganizeCards() = 0;
|
||||
virtual QPointF closestGridPoint(const QPointF &point);
|
||||
|
||||
QMenu *getMenu() const
|
||||
{
|
||||
return menu;
|
||||
}
|
||||
public slots:
|
||||
bool showContextMenu(const QPoint &screenPos);
|
||||
/// @brief Called when a card is added to this zone. Default: reparents card to this item.
|
||||
/// Virtual so subclasses (e.g. SelectZone) can override parenting behavior — the Qt signal
|
||||
/// connection in CardZone's constructor dispatches through the vtable.
|
||||
virtual void onCardAdded(CardItem *addedCard);
|
||||
|
||||
public:
|
||||
enum
|
||||
{
|
||||
Type = typeZone
|
||||
};
|
||||
int type() const override
|
||||
{
|
||||
return Type;
|
||||
}
|
||||
virtual void
|
||||
handleDropEvent(const QList<CardDragItem *> &dragItem, CardZoneLogic *startZone, const QPoint &dropPoint) = 0;
|
||||
CardZone(CardZoneLogic *logic, QGraphicsItem *parent = nullptr);
|
||||
void retranslateUi();
|
||||
|
||||
CardZoneLogic *getLogic() const
|
||||
{
|
||||
return logic;
|
||||
}
|
||||
|
||||
void setMenu(QMenu *_menu, QAction *_doubleClickAction = 0)
|
||||
{
|
||||
menu = _menu;
|
||||
doubleClickAction = _doubleClickAction;
|
||||
}
|
||||
|
||||
private:
|
||||
CardZoneLogic *logic;
|
||||
};
|
||||
|
||||
#endif
|
||||
140
cockatrice/src/game_graphics/zones/hand_zone.cpp
Normal file
140
cockatrice/src/game_graphics/zones/hand_zone.cpp
Normal file
|
|
@ -0,0 +1,140 @@
|
|||
#include "hand_zone.h"
|
||||
|
||||
#include "../../client/settings/cache_settings.h"
|
||||
#include "../../game/board/card_drag_item.h"
|
||||
#include "../../game/board/card_item.h"
|
||||
#include "../../game/player/player.h"
|
||||
#include "../../game/player/player_actions.h"
|
||||
#include "../../interface/theme_manager.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <libcockatrice/protocol/pb/command_move_card.pb.h>
|
||||
|
||||
HandZone::HandZone(HandZoneLogic *_logic, int _zoneHeight, QGraphicsItem *parent)
|
||||
: SelectZone(_logic, parent), zoneHeight(_zoneHeight)
|
||||
{
|
||||
connect(themeManager, &ThemeManager::themeChanged, this, &HandZone::updateBg);
|
||||
updateBg();
|
||||
setCacheMode(DeviceCoordinateCache);
|
||||
}
|
||||
|
||||
void HandZone::updateBg()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
void HandZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
|
||||
CardZoneLogic *startZone,
|
||||
const QPoint &dropPoint)
|
||||
{
|
||||
if (startZone == nullptr || startZone->getPlayer() == nullptr || dragItems.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
QPoint point = dropPoint + scenePos().toPoint();
|
||||
int x = -1;
|
||||
if (SettingsCache::instance().getHorizontalHand()) {
|
||||
for (x = 0; x < getLogic()->getCards().size(); x++) {
|
||||
if (point.x() < static_cast<CardItem *>(getLogic()->getCards().at(x))->scenePos().x()) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
x = calcDropIndexFromY(dropPoint.y());
|
||||
}
|
||||
|
||||
Command_MoveCard cmd;
|
||||
cmd.set_start_player_id(startZone->getPlayer()->getPlayerInfo()->getId());
|
||||
cmd.set_start_zone(startZone->getName().toStdString());
|
||||
cmd.set_target_player_id(getLogic()->getPlayer()->getPlayerInfo()->getId());
|
||||
cmd.set_target_zone(getLogic()->getName().toStdString());
|
||||
cmd.set_x(x);
|
||||
cmd.set_y(-1);
|
||||
|
||||
for (int i = 0; i < dragItems.size(); ++i) {
|
||||
cmd.mutable_cards_to_move()->add_card()->set_card_id(dragItems[i]->getId());
|
||||
}
|
||||
|
||||
getLogic()->getPlayer()->getPlayerActions()->sendGameCommand(cmd);
|
||||
}
|
||||
|
||||
QRectF HandZone::boundingRect() const
|
||||
{
|
||||
if (SettingsCache::instance().getHorizontalHand()) {
|
||||
return QRectF(0, 0, width, CardDimensions::HEIGHT_F + 10);
|
||||
} else {
|
||||
return QRectF(0, 0, CardDimensions::WIDTH_F * 1.5, zoneHeight);
|
||||
}
|
||||
}
|
||||
|
||||
void HandZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
||||
{
|
||||
QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Hand, getLogic()->getPlayer()->getZoneId());
|
||||
painter->fillRect(boundingRect(), brush);
|
||||
}
|
||||
|
||||
void HandZone::reorganizeCards()
|
||||
{
|
||||
if (!getLogic()->getCards().isEmpty()) {
|
||||
const int cardCount = getLogic()->getCards().size();
|
||||
if (SettingsCache::instance().getHorizontalHand()) {
|
||||
bool leftJustified = SettingsCache::instance().getLeftJustified();
|
||||
qreal cardWidth = getLogic()->getCards().at(0)->boundingRect().width();
|
||||
const int xPadding = leftJustified ? cardWidth * 1.4 : 5;
|
||||
qreal totalWidth =
|
||||
leftJustified ? boundingRect().width() - (1 * xPadding) - 5 : boundingRect().width() - 2 * xPadding;
|
||||
|
||||
if (cardCount == 1) {
|
||||
CardItem *c = getLogic()->getCards().at(0);
|
||||
qreal xPosition = leftJustified ? xPadding : xPadding + (totalWidth - cardWidth) / 2;
|
||||
c->setPos(xPosition, 5);
|
||||
c->setRealZValue(0);
|
||||
} else {
|
||||
for (int i = 0; i < cardCount; i++) {
|
||||
CardItem *c = getLogic()->getCards().at(i);
|
||||
// If the total width of the cards is smaller than the available width,
|
||||
// the cards do not need to overlap and are displayed in the center of the area.
|
||||
if (cardWidth * cardCount > totalWidth) {
|
||||
c->setPos(xPadding + ((qreal)i) * (totalWidth - cardWidth) / (cardCount - 1), 5);
|
||||
} else {
|
||||
qreal xPosition = leftJustified ? xPadding + ((qreal)i) * cardWidth
|
||||
: xPadding + ((qreal)i) * cardWidth +
|
||||
(totalWidth - cardCount * cardWidth) / 2;
|
||||
c->setPos(xPosition, 5);
|
||||
}
|
||||
c->setRealZValue(i);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// No clip container: hand cards should always be visible to the player.
|
||||
const auto params = buildStackParams();
|
||||
layoutCardsVertically(params);
|
||||
}
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void HandZone::sortHand(const QList<CardList::SortOption> &options)
|
||||
{
|
||||
if (getLogic()->getCards().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
getLogic()->sortCards(options);
|
||||
reorganizeCards();
|
||||
}
|
||||
|
||||
void HandZone::setWidth(qreal _width)
|
||||
{
|
||||
if (SettingsCache::instance().getHorizontalHand()) {
|
||||
prepareGeometryChange();
|
||||
width = _width;
|
||||
reorganizeCards();
|
||||
}
|
||||
}
|
||||
|
||||
void HandZone::updateOrientation()
|
||||
{
|
||||
prepareGeometryChange();
|
||||
reorganizeCards();
|
||||
}
|
||||
35
cockatrice/src/game_graphics/zones/hand_zone.h
Normal file
35
cockatrice/src/game_graphics/zones/hand_zone.h
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
/**
|
||||
* @file hand_zone.h
|
||||
* @ingroup GameGraphicsZones
|
||||
* @brief Graphical zone for the player's hand, supporting horizontal and vertical layouts.
|
||||
*/
|
||||
|
||||
#ifndef HANDZONE_H
|
||||
#define HANDZONE_H
|
||||
|
||||
#include "../../game/zones/hand_zone_logic.h"
|
||||
#include "select_zone.h"
|
||||
|
||||
class HandZone : public SelectZone
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
qreal width = 0.0;
|
||||
qreal zoneHeight;
|
||||
private slots:
|
||||
void updateBg();
|
||||
public slots:
|
||||
void updateOrientation();
|
||||
|
||||
public:
|
||||
HandZone(HandZoneLogic *_logic, int _zoneHeight, QGraphicsItem *parent = nullptr);
|
||||
void
|
||||
handleDropEvent(const QList<CardDragItem *> &dragItems, CardZoneLogic *startZone, const QPoint &dropPoint) override;
|
||||
QRectF boundingRect() const override;
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
|
||||
void reorganizeCards() override;
|
||||
void sortHand(const QList<CardList::SortOption> &options);
|
||||
void setWidth(qreal _width);
|
||||
};
|
||||
|
||||
#endif
|
||||
135
cockatrice/src/game_graphics/zones/pile_zone.cpp
Normal file
135
cockatrice/src/game_graphics/zones/pile_zone.cpp
Normal file
|
|
@ -0,0 +1,135 @@
|
|||
#include "pile_zone.h"
|
||||
|
||||
#include "../../game/board/card_drag_item.h"
|
||||
#include "../../game/board/card_item.h"
|
||||
#include "../../game/player/player.h"
|
||||
#include "../../game/player/player_actions.h"
|
||||
#include "../../game/zones/pile_zone_logic.h"
|
||||
#include "view_zone.h"
|
||||
|
||||
#include <QApplication>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QPainter>
|
||||
#include <libcockatrice/protocol/pb/command_move_card.pb.h>
|
||||
|
||||
PileZone::PileZone(PileZoneLogic *_logic, QGraphicsItem *parent) : CardZone(_logic, parent)
|
||||
{
|
||||
setCacheMode(DeviceCoordinateCache); // Do not move this line to the parent constructor!
|
||||
setAcceptHoverEvents(true);
|
||||
setCursor(Qt::OpenHandCursor);
|
||||
|
||||
setTransform(QTransform()
|
||||
.translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F)
|
||||
.rotate(90)
|
||||
.translate(-CardDimensions::WIDTH_HALF_F, -CardDimensions::HEIGHT_HALF_F));
|
||||
|
||||
connect(&SettingsCache::instance(), &SettingsCache::roundCardCornersChanged, this, [this](bool _roundCardCorners) {
|
||||
Q_UNUSED(_roundCardCorners);
|
||||
|
||||
prepareGeometryChange();
|
||||
update();
|
||||
});
|
||||
}
|
||||
|
||||
QRectF PileZone::boundingRect() const
|
||||
{
|
||||
return QRectF(0, 0, CardDimensions::WIDTH_F, CardDimensions::HEIGHT_F);
|
||||
}
|
||||
|
||||
QPainterPath PileZone::shape() const
|
||||
{
|
||||
QPainterPath shape;
|
||||
qreal cardCornerRadius = SettingsCache::instance().getRoundCardCorners() ? 0.05 * CardDimensions::WIDTH_F : 0.0;
|
||||
shape.addRoundedRect(boundingRect(), cardCornerRadius, cardCornerRadius);
|
||||
return shape;
|
||||
}
|
||||
|
||||
void PileZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
||||
{
|
||||
painter->drawPath(shape());
|
||||
|
||||
if (!getLogic()->getCards().isEmpty()) {
|
||||
getLogic()->getCards().at(0)->paintPicture(painter, getLogic()->getCards().at(0)->getTranslatedSize(painter),
|
||||
90);
|
||||
}
|
||||
|
||||
painter->translate(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F);
|
||||
painter->rotate(-90);
|
||||
painter->translate(-CardDimensions::WIDTH_HALF_F, -CardDimensions::HEIGHT_HALF_F);
|
||||
paintNumberEllipse(getLogic()->getCards().size(), 28, Qt::white, -1, -1, painter);
|
||||
}
|
||||
|
||||
void PileZone::handleDropEvent(const QList<CardDragItem *> &dragItems, CardZoneLogic *startZone, const QPoint &)
|
||||
{
|
||||
Command_MoveCard cmd;
|
||||
cmd.set_start_player_id(startZone->getPlayer()->getPlayerInfo()->getId());
|
||||
cmd.set_start_zone(startZone->getName().toStdString());
|
||||
cmd.set_target_player_id(getLogic()->getPlayer()->getPlayerInfo()->getId());
|
||||
cmd.set_target_zone(getLogic()->getName().toStdString());
|
||||
cmd.set_x(0);
|
||||
cmd.set_y(0);
|
||||
|
||||
for (int i = 0; i < dragItems.size(); ++i) {
|
||||
auto cardToMove = cmd.mutable_cards_to_move()->add_card();
|
||||
cardToMove->set_card_id(dragItems[i]->getId());
|
||||
if (dragItems[i]->isForceFaceDown()) {
|
||||
cardToMove->set_face_down(true);
|
||||
}
|
||||
}
|
||||
|
||||
getLogic()->getPlayer()->getPlayerActions()->sendGameCommand(cmd);
|
||||
}
|
||||
|
||||
void PileZone::reorganizeCards()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
void PileZone::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
CardZone::mousePressEvent(event);
|
||||
if (event->isAccepted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
setCursor(Qt::ClosedHandCursor);
|
||||
event->accept();
|
||||
} else {
|
||||
event->ignore();
|
||||
}
|
||||
}
|
||||
|
||||
void PileZone::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if ((event->screenPos() - event->buttonDownScreenPos(Qt::LeftButton)).manhattanLength() <
|
||||
QApplication::startDragDistance()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (getLogic()->getCards().isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
bool forceFaceDown = event->modifiers().testFlag(Qt::ShiftModifier);
|
||||
bool bottomCard = event->modifiers().testFlag(Qt::ControlModifier);
|
||||
CardItem *card = bottomCard ? getLogic()->getCards().last() : getLogic()->getCards().first();
|
||||
const int cardid =
|
||||
getLogic()->contentsKnown() ? card->getId() : (bottomCard ? getLogic()->getCards().size() - 1 : 0);
|
||||
CardDragItem *drag = card->createDragItem(cardid, event->pos(), event->scenePos(), forceFaceDown);
|
||||
drag->grabMouse();
|
||||
setCursor(Qt::OpenHandCursor);
|
||||
}
|
||||
|
||||
void PileZone::mouseReleaseEvent(QGraphicsSceneMouseEvent * /*event*/)
|
||||
{
|
||||
setCursor(Qt::OpenHandCursor);
|
||||
}
|
||||
|
||||
void PileZone::hoverEnterEvent(QGraphicsSceneHoverEvent *event)
|
||||
{
|
||||
if (!getLogic()->getCards().isEmpty()) {
|
||||
getLogic()->getCards()[0]->processHoverEvent();
|
||||
}
|
||||
QGraphicsItem::hoverEnterEvent(event);
|
||||
}
|
||||
42
cockatrice/src/game_graphics/zones/pile_zone.h
Normal file
42
cockatrice/src/game_graphics/zones/pile_zone.h
Normal file
|
|
@ -0,0 +1,42 @@
|
|||
/**
|
||||
* @file pile_zone.h
|
||||
* @ingroup GameGraphicsZones
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef PILEZONE_H
|
||||
#define PILEZONE_H
|
||||
|
||||
#include "../../game/zones/pile_zone_logic.h"
|
||||
#include "card_zone.h"
|
||||
|
||||
/**
|
||||
* A CardZone where the cards are in a single pile instead of being laid out.
|
||||
* Usually only top card is accessible by clicking.
|
||||
*/
|
||||
class PileZone : public CardZone
|
||||
{
|
||||
Q_OBJECT
|
||||
private slots:
|
||||
void callUpdate()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
public:
|
||||
PileZone(PileZoneLogic *_logic, QGraphicsItem *parent);
|
||||
[[nodiscard]] QRectF boundingRect() const override;
|
||||
[[nodiscard]] QPainterPath shape() const override;
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
|
||||
void reorganizeCards() override;
|
||||
void
|
||||
handleDropEvent(const QList<CardDragItem *> &dragItems, CardZoneLogic *startZone, const QPoint &dropPoint) override;
|
||||
|
||||
protected:
|
||||
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void hoverEnterEvent(QGraphicsSceneHoverEvent *event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
276
cockatrice/src/game_graphics/zones/select_zone.cpp
Normal file
276
cockatrice/src/game_graphics/zones/select_zone.cpp
Normal file
|
|
@ -0,0 +1,276 @@
|
|||
#include "select_zone.h"
|
||||
|
||||
#include "../../client/settings/cache_settings.h"
|
||||
#include "../../game/board/card_item.h"
|
||||
#include "../../game/game_scene.h"
|
||||
|
||||
#include <QGraphicsRectItem>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QtMath>
|
||||
|
||||
static qreal stackingOffset(qreal cardHeight)
|
||||
{
|
||||
const qreal overlapPercent = SettingsCache::instance().getStackCardOverlapPercent();
|
||||
return cardHeight * (100.0 - overlapPercent) / 100.0;
|
||||
}
|
||||
|
||||
SelectZone::ZoneLayout SelectZone::computeZoneLayout(const StackLayoutParams ¶ms)
|
||||
{
|
||||
if (params.cardCount <= 0) {
|
||||
return {0.0, 0.0};
|
||||
}
|
||||
qreal effectiveOffset = params.desiredOffset;
|
||||
if (params.cardCount > 1) {
|
||||
qreal fitOffset;
|
||||
if (params.totalHeight < params.cardHeight && params.minOffset > 0.0) {
|
||||
// Zone is shorter than a card (e.g. minimized). Compress offsets so
|
||||
// every card has at least minOffset pixels of its top visible.
|
||||
fitOffset = (params.totalHeight - params.minOffset) / (params.cardCount - 1);
|
||||
effectiveOffset = qMax(0.0, qMin(params.desiredOffset, fitOffset));
|
||||
} else {
|
||||
qreal reservedForBottomCard;
|
||||
if (params.allowBottomOverflow) {
|
||||
// Allow the bottom card to partially overflow in tight zones, scaling the
|
||||
// overflow allowance by sqrt(cardCount-1) so offsets decrease smoothly
|
||||
// as cards are added rather than dropping by 1/(n-1) each time.
|
||||
// The 0.75 ratio was tuned experimentally to balance card visibility vs. overflow.
|
||||
constexpr qreal bottomCardZoneRatio = 0.75;
|
||||
const qreal adjustedRatio = bottomCardZoneRatio / qSqrt(static_cast<qreal>(params.cardCount - 1));
|
||||
reservedForBottomCard = qMin(params.cardHeight, params.totalHeight * adjustedRatio);
|
||||
} else {
|
||||
// No overflow: reserve full card height for the bottom card
|
||||
reservedForBottomCard = params.cardHeight;
|
||||
}
|
||||
fitOffset = (params.totalHeight - reservedForBottomCard) / (params.cardCount - 1);
|
||||
effectiveOffset = qMax(params.minOffset, qMin(params.desiredOffset, fitOffset));
|
||||
}
|
||||
}
|
||||
qreal stackHeight = (params.cardCount - 1) * effectiveOffset + params.cardHeight;
|
||||
qreal start = (stackHeight <= params.totalHeight) ? (params.totalHeight - stackHeight) / 2.0 : 0.0;
|
||||
return {effectiveOffset, start};
|
||||
}
|
||||
|
||||
SelectZone *SelectZone::findOwningSelectZone(const QGraphicsItem *card)
|
||||
{
|
||||
QGraphicsItem *parent = card ? card->parentItem() : nullptr;
|
||||
if (!parent) {
|
||||
return nullptr;
|
||||
}
|
||||
// Card may be direct child of zone (escaped for hover) or child of clip container.
|
||||
if (auto *zone = dynamic_cast<SelectZone *>(parent)) {
|
||||
return zone;
|
||||
}
|
||||
if (auto *zone = dynamic_cast<SelectZone *>(parent->parentItem())) {
|
||||
return zone;
|
||||
}
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
SelectZone::StackLayoutParams SelectZone::buildStackParams(qreal minOffset) const
|
||||
{
|
||||
const auto &cards = getLogic()->getCards();
|
||||
if (cards.isEmpty()) {
|
||||
return {0, boundingRect().height(), 0.0, 0.0, minOffset};
|
||||
}
|
||||
const auto cardCount = static_cast<int>(cards.size());
|
||||
const qreal cardHeight = cards.at(0)->boundingRect().height();
|
||||
const qreal offset = stackingOffset(cardHeight);
|
||||
return {cardCount, boundingRect().height(), cardHeight, offset, minOffset};
|
||||
}
|
||||
|
||||
int SelectZone::calcDropIndexFromY(qreal dropY, qreal minOffset) const
|
||||
{
|
||||
const auto &cards = getLogic()->getCards();
|
||||
if (cards.isEmpty()) {
|
||||
return 0;
|
||||
}
|
||||
const auto params = buildStackParams(minOffset);
|
||||
auto [effectiveOffset, start] = computeZoneLayout(params);
|
||||
if (effectiveOffset <= 0.0) {
|
||||
return 0;
|
||||
}
|
||||
return qBound(0, qRound((dropY - start) / effectiveOffset), params.cardCount - 1);
|
||||
}
|
||||
|
||||
void SelectZone::restoreStaleEscapedCards()
|
||||
{
|
||||
if (!cardClipContainer) {
|
||||
return;
|
||||
}
|
||||
for (auto *card : getLogic()->getCards()) {
|
||||
// A card parented to the zone (instead of the clip container) should
|
||||
// only occur while it is actively hovered. If hover cleanup was
|
||||
// missed, reparent it back so clipping resumes.
|
||||
if (card && card->parentItem() == this && !card->getIsHovered()) {
|
||||
card->setParentItem(cardClipContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SelectZone::layoutCardsVertically(const StackLayoutParams ¶ms)
|
||||
{
|
||||
const auto &cards = getLogic()->getCards();
|
||||
if (cards.isEmpty() || params.cardCount <= 0) {
|
||||
return;
|
||||
}
|
||||
if (params.cardCount > cards.size()) {
|
||||
return;
|
||||
}
|
||||
|
||||
constexpr qreal xspace = 5;
|
||||
const qreal cardWidth = cards.at(0)->boundingRect().width();
|
||||
const qreal totalWidth = boundingRect().width();
|
||||
const qreal x1 = xspace;
|
||||
const qreal x2 = totalWidth - xspace - cardWidth;
|
||||
const qreal xCentered = (totalWidth - cardWidth) / 2.0;
|
||||
|
||||
auto [effectiveOffset, start] = computeZoneLayout(params);
|
||||
for (int i = 0; i < params.cardCount; i++) {
|
||||
CardItem *card = cards.at(i);
|
||||
qreal y = start + i * effectiveOffset;
|
||||
// Center single card; alternate left/right for multiple cards
|
||||
qreal x = (params.cardCount == 1) ? xCentered : ((i % 2) ? x2 : x1);
|
||||
card->setPos(x, y);
|
||||
card->setRealZValue(i);
|
||||
}
|
||||
}
|
||||
|
||||
SelectZone::SelectZone(CardZoneLogic *_logic, QGraphicsItem *parent) : CardZone(_logic, parent)
|
||||
{
|
||||
}
|
||||
|
||||
SelectZone::~SelectZone()
|
||||
{
|
||||
if (cardClipContainer) {
|
||||
// Reparent any hover-escaped cards back to the clip container so Qt's
|
||||
// parent-child tree is consistent for destruction. setParentItem() does
|
||||
// not invalidate getLogic()->getCards() (it modifies the graphics tree,
|
||||
// not the zone's logical card list).
|
||||
for (auto *card : getLogic()->getCards()) {
|
||||
if (card && card->parentItem() == this) {
|
||||
card->setParentItem(cardClipContainer);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void SelectZone::onCardAdded(CardItem *addedCard)
|
||||
{
|
||||
if (cardClipContainer && addedCard) {
|
||||
addedCard->setParentItem(cardClipContainer);
|
||||
addedCard->setVisible(true);
|
||||
addedCard->update();
|
||||
} else {
|
||||
CardZone::onCardAdded(addedCard);
|
||||
}
|
||||
}
|
||||
|
||||
void SelectZone::setupClipContainer(std::optional<qreal> zValue)
|
||||
{
|
||||
if (cardClipContainer) {
|
||||
return;
|
||||
}
|
||||
|
||||
setFlag(QGraphicsItem::ItemClipsChildrenToShape, false);
|
||||
|
||||
cardClipContainer = new QGraphicsRectItem(this); // Owned by Qt parent-child tree; deleted with this zone.
|
||||
cardClipContainer->setFlag(QGraphicsItem::ItemClipsChildrenToShape, true);
|
||||
cardClipContainer->setPen(Qt::NoPen);
|
||||
cardClipContainer->setBrush(Qt::NoBrush);
|
||||
cardClipContainer->setRect(boundingRect());
|
||||
if (zValue.has_value()) {
|
||||
cardClipContainer->setZValue(*zValue);
|
||||
}
|
||||
}
|
||||
|
||||
void SelectZone::escapeClipForHover(QGraphicsItem *card)
|
||||
{
|
||||
// Reparent from clip container to zone so the hover-scaled card is visible
|
||||
// beyond clip bounds. Coordinates are identical because the clip container
|
||||
// is at (0,0) with no transform relative to this zone.
|
||||
if (cardClipContainer && card && card->parentItem() == cardClipContainer) {
|
||||
card->setParentItem(this);
|
||||
cardClipContainer->update();
|
||||
}
|
||||
}
|
||||
|
||||
void SelectZone::restoreClipAfterHover(QGraphicsItem *card)
|
||||
{
|
||||
// Restore card to clip container. If card's parent is not this zone,
|
||||
// a zone transition already reparented it via onCardAdded — skip.
|
||||
if (cardClipContainer && card && card->parentItem() == this) {
|
||||
card->setParentItem(cardClipContainer);
|
||||
}
|
||||
}
|
||||
|
||||
void SelectZone::updateClipRect()
|
||||
{
|
||||
if (cardClipContainer) {
|
||||
cardClipContainer->setRect(boundingRect());
|
||||
}
|
||||
}
|
||||
|
||||
void SelectZone::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (event->buttons().testFlag(Qt::LeftButton)) {
|
||||
QPointF pos = event->pos();
|
||||
if (pos.x() < 0) {
|
||||
pos.setX(0);
|
||||
}
|
||||
QRectF br = boundingRect();
|
||||
if (pos.x() > br.width()) {
|
||||
pos.setX(br.width());
|
||||
}
|
||||
if (pos.y() < 0) {
|
||||
pos.setY(0);
|
||||
}
|
||||
if (pos.y() > br.height()) {
|
||||
pos.setY(br.height());
|
||||
}
|
||||
|
||||
QRectF selectionRect = QRectF(selectionOrigin, pos).normalized();
|
||||
for (auto card : getLogic()->getCards()) {
|
||||
if (card->getAttachedTo() && card->getAttachedTo()->getZone() != getLogic()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
bool inRect = selectionRect.intersects(card->mapRectToItem(this, card->boundingRect()));
|
||||
if (inRect && !cardsInSelectionRect.contains(card)) {
|
||||
// selection has just expanded to cover the card
|
||||
cardsInSelectionRect.insert(card);
|
||||
card->setSelected(!card->isSelected());
|
||||
} else if (!inRect && cardsInSelectionRect.contains(card)) {
|
||||
// selection has just shrunk to no longer cover the card
|
||||
cardsInSelectionRect.remove(card);
|
||||
card->setSelected(!card->isSelected());
|
||||
}
|
||||
}
|
||||
static_cast<GameScene *>(scene())->resizeRubberBand(
|
||||
deviceTransform(static_cast<GameScene *>(scene())->getViewportTransform()).map(pos),
|
||||
cardsInSelectionRect.size());
|
||||
event->accept();
|
||||
}
|
||||
}
|
||||
|
||||
void SelectZone::mousePressEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (event->button() == Qt::LeftButton) {
|
||||
if (!event->modifiers().testFlag(Qt::ControlModifier)) {
|
||||
scene()->clearSelection();
|
||||
}
|
||||
|
||||
selectionOrigin = event->pos();
|
||||
static_cast<GameScene *>(scene())->startRubberBand(event->scenePos());
|
||||
event->accept();
|
||||
} else {
|
||||
CardZone::mousePressEvent(event);
|
||||
}
|
||||
}
|
||||
|
||||
void SelectZone::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
selectionOrigin = QPoint();
|
||||
cardsInSelectionRect.clear();
|
||||
static_cast<GameScene *>(scene())->stopRubberBand();
|
||||
event->accept();
|
||||
}
|
||||
132
cockatrice/src/game_graphics/zones/select_zone.h
Normal file
132
cockatrice/src/game_graphics/zones/select_zone.h
Normal file
|
|
@ -0,0 +1,132 @@
|
|||
/**
|
||||
* @file select_zone.h
|
||||
* @ingroup GameGraphicsZones
|
||||
* @brief Base class for zones where cards are laid out and individually interactable.
|
||||
*/
|
||||
|
||||
#ifndef SELECTZONE_H
|
||||
#define SELECTZONE_H
|
||||
|
||||
#include "card_zone.h"
|
||||
|
||||
#include <QSet>
|
||||
#include <optional>
|
||||
|
||||
class QGraphicsRectItem;
|
||||
|
||||
/**
|
||||
* A CardZone where the cards are laid out, with each card directly interactable by clicking.
|
||||
*/
|
||||
class SelectZone : public CardZone
|
||||
{
|
||||
Q_OBJECT
|
||||
public:
|
||||
/// Finds the SelectZone that owns a card, regardless of whether the card is parented
|
||||
/// to the zone directly or to its clip container. Returns nullptr if not in a SelectZone.
|
||||
static SelectZone *findOwningSelectZone(const QGraphicsItem *card);
|
||||
|
||||
SelectZone(CardZoneLogic *logic, QGraphicsItem *parent = nullptr);
|
||||
~SelectZone() override;
|
||||
void onCardAdded(CardItem *addedCard) override;
|
||||
|
||||
/// @brief Temporarily reparents a card from the clip container to this zone so hover scaling is visible beyond clip
|
||||
/// bounds. Safe no-op if no clip container exists. Coordinates are preserved (clip container is at (0,0) with no
|
||||
/// transform).
|
||||
void escapeClipForHover(QGraphicsItem *card);
|
||||
/// @brief Restores a hover-escaped card back to the clip container. Guards against zone transitions that already
|
||||
/// reparented the card.
|
||||
void restoreClipAfterHover(QGraphicsItem *card);
|
||||
|
||||
private:
|
||||
QPointF selectionOrigin;
|
||||
QSet<CardItem *> cardsInSelectionRect;
|
||||
/// Invisible clipping parent for cards; owned by Qt parent-child tree (parented to this zone).
|
||||
/// Created by setupClipContainer(); null when no clip container is active.
|
||||
QGraphicsRectItem *cardClipContainer = nullptr;
|
||||
|
||||
protected:
|
||||
// -- Layout computation --
|
||||
|
||||
/// Parameters describing a vertical card stack's geometry.
|
||||
struct StackLayoutParams
|
||||
{
|
||||
int cardCount; ///< Number of cards in the stack
|
||||
qreal totalHeight; ///< Available height for the stack (zone height)
|
||||
qreal cardHeight; ///< Height of a single card
|
||||
qreal desiredOffset; ///< Preferred vertical offset between card tops
|
||||
qreal minOffset = 0.0; ///< Minimum offset to preserve (0 allows full compression)
|
||||
/// When false (default), reserves full cardHeight for the bottom card, ensuring
|
||||
/// all cards remain within zone bounds. When true, allows the bottom card to
|
||||
/// partially overflow using sqrt-scaled allowance. Use with setupClipContainer()
|
||||
/// for zones too short to fit a full card.
|
||||
bool allowBottomOverflow = false;
|
||||
};
|
||||
|
||||
/// Result of computing a vertical stack layout.
|
||||
struct ZoneLayout
|
||||
{
|
||||
qreal effectiveOffset; ///< Actual offset between card tops (may be compressed)
|
||||
qreal start; ///< Y coordinate of the first card's top edge
|
||||
};
|
||||
|
||||
/// Minimum visible pixels of each card's top edge when stacking compresses offsets in tight zones.
|
||||
static constexpr qreal MIN_CARD_VISIBLE = 10.0;
|
||||
|
||||
/**
|
||||
* @brief Computes layout for a vertical card stack (effective offset and start position).
|
||||
*
|
||||
* Three regimes:
|
||||
* 1. Minimized zone (totalHeight < card height with minOffset > 0): offsets compress
|
||||
* so each card retains at least minOffset visible pixels of its top edge.
|
||||
* 2. Normal zone with allowBottomOverflow=false (default): the bottom card is
|
||||
* guaranteed to fit within the zone boundary. Offsets compress as needed.
|
||||
* 3. Normal zone with allowBottomOverflow=true: the bottom card may partially
|
||||
* overflow. The overflow allowance is scaled by sqrt(cardCount-1) so that
|
||||
* adding one card shifts existing cards smoothly.
|
||||
*
|
||||
* When the stack fits with room to spare, it is centered vertically.
|
||||
*/
|
||||
static ZoneLayout computeZoneLayout(const StackLayoutParams ¶ms);
|
||||
|
||||
/// Builds StackLayoutParams from the current card list and zone geometry.
|
||||
StackLayoutParams buildStackParams(qreal minOffset = 0.0) const;
|
||||
|
||||
/// Computes the card index at a given y-coordinate within the zone's vertical layout.
|
||||
/// Returns 0 if the zone has no cards or the offset is zero.
|
||||
int calcDropIndexFromY(qreal dropY, qreal minOffset = 0.0) const;
|
||||
|
||||
/**
|
||||
* @brief Positions cards vertically with alternating left/right x-offsets.
|
||||
*
|
||||
* Cards alternate between left and right margins (5px padding from zone edges):
|
||||
* even-indexed cards at left, odd-indexed at right.
|
||||
* Cards are assigned ascending z-values.
|
||||
*
|
||||
* @param params Stack layout geometry parameters (use allowBottomOverflow to control overflow)
|
||||
*/
|
||||
void layoutCardsVertically(const StackLayoutParams ¶ms);
|
||||
|
||||
// -- Clip container --
|
||||
// The clip container mechanism is available for future zones that need visual clipping
|
||||
// (e.g., zones too short to fit a full card). To enable: call setupClipContainer() in the
|
||||
// zone's constructor, and set allowBottomOverflow=true in layout params.
|
||||
|
||||
/// Restores any cards that were hover-escaped but whose hover state was not properly cleaned up.
|
||||
/// Call at the start of reorganizeCards() in zones that use a clip container.
|
||||
void restoreStaleEscapedCards();
|
||||
|
||||
/// Creates a clip container child item that clips card overflow to zone bounds.
|
||||
/// Cards entering this zone are reparented to this container by the onCardAdded override.
|
||||
/// Disables zone-level child clipping; clipping is delegated to the container.
|
||||
/// @param zValue Optional z-value for the clip container (e.g. ZValues::CARD_BASE)
|
||||
void setupClipContainer(std::optional<qreal> zValue = std::nullopt);
|
||||
|
||||
/// Updates the clip container rect to match this zone's current boundingRect().
|
||||
void updateClipRect();
|
||||
|
||||
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void mousePressEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
93
cockatrice/src/game_graphics/zones/stack_zone.cpp
Normal file
93
cockatrice/src/game_graphics/zones/stack_zone.cpp
Normal file
|
|
@ -0,0 +1,93 @@
|
|||
#include "stack_zone.h"
|
||||
|
||||
#include "../../game/board/card_drag_item.h"
|
||||
#include "../../game/board/card_item.h"
|
||||
#include "../../game/card_dimensions.h"
|
||||
#include "../../game/player/player.h"
|
||||
#include "../../game/player/player_actions.h"
|
||||
#include "../../game/zones/stack_zone_logic.h"
|
||||
#include "../../interface/theme_manager.h"
|
||||
|
||||
#include <QPainter>
|
||||
#include <libcockatrice/protocol/pb/command_move_card.pb.h>
|
||||
|
||||
StackZone::StackZone(StackZoneLogic *_logic, int _zoneHeight, QGraphicsItem *parent)
|
||||
: SelectZone(_logic, parent), zoneHeight(_zoneHeight)
|
||||
{
|
||||
connect(themeManager, &ThemeManager::themeChanged, this, &StackZone::updateBg);
|
||||
updateBg();
|
||||
setCacheMode(DeviceCoordinateCache);
|
||||
}
|
||||
|
||||
void StackZone::updateBg()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
QRectF StackZone::boundingRect() const
|
||||
{
|
||||
return {0, 0, CardDimensions::WIDTH_F * 1.5, zoneHeight};
|
||||
}
|
||||
|
||||
void StackZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
||||
{
|
||||
QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Stack, getLogic()->getPlayer()->getZoneId());
|
||||
painter->fillRect(boundingRect(), brush);
|
||||
}
|
||||
|
||||
void StackZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
|
||||
CardZoneLogic *startZone,
|
||||
const QPoint &dropPoint)
|
||||
{
|
||||
if (startZone == nullptr || startZone->getPlayer() == nullptr || dragItems.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
int index = calcDropIndexFromY(dropPoint.y(), MIN_CARD_VISIBLE);
|
||||
|
||||
// Same-zone no-op: don't move a card onto itself
|
||||
const auto &cards = getLogic()->getCards();
|
||||
if (!cards.isEmpty() && startZone == getLogic() && cards.at(index)->getId() == dragItems.at(0)->getId()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Command_MoveCard cmd;
|
||||
cmd.set_start_player_id(startZone->getPlayer()->getPlayerInfo()->getId());
|
||||
cmd.set_start_zone(startZone->getName().toStdString());
|
||||
cmd.set_target_player_id(getLogic()->getPlayer()->getPlayerInfo()->getId());
|
||||
cmd.set_target_zone(getLogic()->getName().toStdString());
|
||||
cmd.set_x(index);
|
||||
cmd.set_y(0);
|
||||
|
||||
for (const CardDragItem *item : dragItems) {
|
||||
if (item) {
|
||||
auto *cardToMove = cmd.mutable_cards_to_move()->add_card();
|
||||
cardToMove->set_card_id(item->getId());
|
||||
if (item->isForceFaceDown()) {
|
||||
cardToMove->set_face_down(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
getLogic()->getPlayer()->getPlayerActions()->sendGameCommand(cmd);
|
||||
}
|
||||
|
||||
void StackZone::setHeight(qreal newHeight)
|
||||
{
|
||||
if (qFuzzyCompare(1.0 + zoneHeight, 1.0 + newHeight)) {
|
||||
return;
|
||||
}
|
||||
prepareGeometryChange();
|
||||
zoneHeight = newHeight;
|
||||
reorganizeCards();
|
||||
update();
|
||||
}
|
||||
|
||||
void StackZone::reorganizeCards()
|
||||
{
|
||||
if (!getLogic()->getCards().isEmpty()) {
|
||||
const auto params = buildStackParams(MIN_CARD_VISIBLE);
|
||||
layoutCardsVertically(params);
|
||||
}
|
||||
update();
|
||||
}
|
||||
32
cockatrice/src/game_graphics/zones/stack_zone.h
Normal file
32
cockatrice/src/game_graphics/zones/stack_zone.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
/**
|
||||
* @file stack_zone.h
|
||||
* @ingroup GameGraphicsZones
|
||||
* @brief Graphical zone for the stack, displaying cards in a vertical pile.
|
||||
*/
|
||||
|
||||
#ifndef STACKZONE_H
|
||||
#define STACKZONE_H
|
||||
|
||||
#include "../../game/zones/stack_zone_logic.h"
|
||||
#include "select_zone.h"
|
||||
|
||||
class StackZone : public SelectZone
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
qreal zoneHeight;
|
||||
private slots:
|
||||
void updateBg();
|
||||
|
||||
public:
|
||||
StackZone(StackZoneLogic *_logic, int _zoneHeight, QGraphicsItem *parent);
|
||||
/// @brief Resizes the stack zone height, e.g. when sharing vertical space with the command zone.
|
||||
void setHeight(qreal newHeight);
|
||||
void
|
||||
handleDropEvent(const QList<CardDragItem *> &dragItems, CardZoneLogic *startZone, const QPoint &dropPoint) override;
|
||||
QRectF boundingRect() const override;
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
|
||||
void reorganizeCards() override;
|
||||
};
|
||||
|
||||
#endif
|
||||
410
cockatrice/src/game_graphics/zones/table_zone.cpp
Normal file
410
cockatrice/src/game_graphics/zones/table_zone.cpp
Normal file
|
|
@ -0,0 +1,410 @@
|
|||
#include "table_zone.h"
|
||||
|
||||
#include "../../client/settings/cache_settings.h"
|
||||
#include "../../game/board/arrow_item.h"
|
||||
#include "../../game/board/card_drag_item.h"
|
||||
#include "../../game/board/card_item.h"
|
||||
#include "../../game/player/player.h"
|
||||
#include "../../game/player/player_actions.h"
|
||||
#include "../../game/z_values.h"
|
||||
#include "../../game/zones/table_zone_logic.h"
|
||||
#include "../../interface/theme_manager.h"
|
||||
|
||||
#include <QGraphicsScene>
|
||||
#include <QPainter>
|
||||
#include <libcockatrice/card/card_info.h>
|
||||
#include <libcockatrice/protocol/pb/command_move_card.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_set_card_attr.pb.h>
|
||||
#include <libcockatrice/utility/zone_names.h>
|
||||
|
||||
const QColor TableZone::BACKGROUND_COLOR = QColor(100, 100, 100);
|
||||
const QColor TableZone::FADE_MASK = QColor(0, 0, 0, 80);
|
||||
const QColor TableZone::GRADIENT_COLOR = QColor(255, 255, 255, 150);
|
||||
const QColor TableZone::GRADIENT_COLORLESS = QColor(255, 255, 255, 0);
|
||||
|
||||
TableZone::TableZone(TableZoneLogic *_logic, QGraphicsItem *parent) : SelectZone(_logic, parent), active(false)
|
||||
{
|
||||
connect(_logic, &TableZoneLogic::contentSizeChanged, this, &TableZone::resizeToContents);
|
||||
connect(_logic, &TableZoneLogic::toggleTapped, this, &TableZone::toggleTapped);
|
||||
connect(themeManager, &ThemeManager::themeChanged, this, &TableZone::updateBg);
|
||||
connect(&SettingsCache::instance(), &SettingsCache::invertVerticalCoordinateChanged, this,
|
||||
&TableZone::reorganizeCards);
|
||||
|
||||
updateBg();
|
||||
|
||||
height = MARGIN_TOP + MARGIN_BOTTOM + TABLEROWS * CardDimensions::HEIGHT + (TABLEROWS - 1) * PADDING_Y;
|
||||
width = MIN_WIDTH;
|
||||
currentMinimumWidth = width;
|
||||
|
||||
setCacheMode(DeviceCoordinateCache);
|
||||
setAcceptHoverEvents(true);
|
||||
}
|
||||
|
||||
void TableZone::updateBg()
|
||||
{
|
||||
update();
|
||||
}
|
||||
|
||||
QRectF TableZone::boundingRect() const
|
||||
{
|
||||
return QRectF(0, 0, width, height);
|
||||
}
|
||||
|
||||
bool TableZone::isInverted() const
|
||||
{
|
||||
return ((getLogic()->getPlayer()->getGraphicsItem()->getMirrored() &&
|
||||
!SettingsCache::instance().getInvertVerticalCoordinate()) ||
|
||||
(!getLogic()->getPlayer()->getGraphicsItem()->getMirrored() &&
|
||||
SettingsCache::instance().getInvertVerticalCoordinate()));
|
||||
}
|
||||
|
||||
void TableZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
||||
{
|
||||
QBrush brush = themeManager->getExtraBgBrush(ThemeManager::Table, getLogic()->getPlayer()->getZoneId());
|
||||
painter->fillRect(boundingRect(), brush);
|
||||
|
||||
if (active) {
|
||||
paintZoneOutline(painter);
|
||||
} else {
|
||||
// inactive player gets a darker table zone with a semi transparent black mask
|
||||
// this means if the user provides a custom background it will fade
|
||||
painter->fillRect(boundingRect(), FADE_MASK);
|
||||
}
|
||||
|
||||
paintLandDivider(painter);
|
||||
}
|
||||
|
||||
/**
|
||||
Render a soft outline around the edge of the TableZone.
|
||||
|
||||
@param painter QPainter object
|
||||
*/
|
||||
void TableZone::paintZoneOutline(QPainter *painter)
|
||||
{
|
||||
QLinearGradient grad1(0, 0, 0, 1);
|
||||
grad1.setCoordinateMode(QGradient::ObjectBoundingMode);
|
||||
grad1.setColorAt(0, GRADIENT_COLOR);
|
||||
grad1.setColorAt(1, GRADIENT_COLORLESS);
|
||||
painter->fillRect(QRectF(0, 0, width, BOX_LINE_WIDTH), QBrush(grad1));
|
||||
|
||||
grad1.setFinalStop(1, 0);
|
||||
painter->fillRect(QRectF(0, 0, BOX_LINE_WIDTH, height), QBrush(grad1));
|
||||
|
||||
grad1.setStart(0, 1);
|
||||
grad1.setFinalStop(0, 0);
|
||||
painter->fillRect(QRectF(0, height - BOX_LINE_WIDTH, width, BOX_LINE_WIDTH), QBrush(grad1));
|
||||
|
||||
grad1.setStart(1, 0);
|
||||
painter->fillRect(QRectF(width - BOX_LINE_WIDTH, 0, BOX_LINE_WIDTH, height), QBrush(grad1));
|
||||
}
|
||||
|
||||
/**
|
||||
Render a division line for land placement
|
||||
|
||||
@painter QPainter object
|
||||
*/
|
||||
void TableZone::paintLandDivider(QPainter *painter)
|
||||
{
|
||||
// Place the line 2 grid heights down then back it off just enough to allow
|
||||
// some space between a 3-card stack and the land area.
|
||||
qreal separatorY = MARGIN_TOP + 2 * (CardDimensions::HEIGHT + PADDING_Y) - STACKED_CARD_OFFSET_Y / 2;
|
||||
if (isInverted()) {
|
||||
separatorY = height - separatorY;
|
||||
}
|
||||
painter->setPen(QColor(255, 255, 255, 40));
|
||||
painter->drawLine(QPointF(0, separatorY), QPointF(width, separatorY));
|
||||
}
|
||||
|
||||
void TableZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
|
||||
CardZoneLogic *startZone,
|
||||
const QPoint &dropPoint)
|
||||
{
|
||||
handleDropEventByGrid(dragItems, startZone, mapToGrid(dropPoint));
|
||||
}
|
||||
|
||||
void TableZone::handleDropEventByGrid(const QList<CardDragItem *> &dragItems,
|
||||
CardZoneLogic *startZone,
|
||||
const QPoint &gridPoint)
|
||||
{
|
||||
Command_MoveCard cmd;
|
||||
cmd.set_start_player_id(startZone->getPlayer()->getPlayerInfo()->getId());
|
||||
cmd.set_start_zone(startZone->getName().toStdString());
|
||||
cmd.set_target_player_id(getLogic()->getPlayer()->getPlayerInfo()->getId());
|
||||
cmd.set_target_zone(getLogic()->getName().toStdString());
|
||||
cmd.set_x(gridPoint.x());
|
||||
cmd.set_y(gridPoint.y());
|
||||
|
||||
for (const auto &item : dragItems) {
|
||||
CardToMove *ctm = cmd.mutable_cards_to_move()->add_card();
|
||||
ctm->set_card_id(item->getId());
|
||||
if (item->isForceFaceDown()) {
|
||||
ctm->set_face_down(true);
|
||||
}
|
||||
if (startZone->getName() != getLogic()->getName() && !item->isForceFaceDown()) {
|
||||
const auto &card = item->getItem()->getCard();
|
||||
if (card) {
|
||||
ctm->set_pt(card.getInfo().getPowTough().toStdString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
startZone->getPlayer()->getPlayerActions()->sendGameCommand(cmd);
|
||||
}
|
||||
|
||||
void TableZone::reorganizeCards()
|
||||
{
|
||||
// Calculate card stack widths so mapping functions work properly
|
||||
computeCardStackWidths();
|
||||
|
||||
for (int i = 0; i < getLogic()->getCards().size(); ++i) {
|
||||
QPoint gridPoint = getLogic()->getCards()[i]->getGridPos();
|
||||
if (gridPoint.x() == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
QPointF mapPoint = mapFromGrid(gridPoint);
|
||||
qreal x = mapPoint.x();
|
||||
qreal y = mapPoint.y();
|
||||
|
||||
int numberAttachedCards = getLogic()->getCards()[i]->getAttachedCards().size();
|
||||
qreal actualX = x + numberAttachedCards * STACKED_CARD_OFFSET_X;
|
||||
qreal actualY = y;
|
||||
if (numberAttachedCards) {
|
||||
actualY += 15;
|
||||
}
|
||||
|
||||
getLogic()->getCards()[i]->setPos(actualX, actualY);
|
||||
getLogic()->getCards()[i]->setRealZValue(ZValues::tableCardZValue(actualX, actualY));
|
||||
|
||||
QListIterator<CardItem *> attachedCardIterator(getLogic()->getCards()[i]->getAttachedCards());
|
||||
int j = 0;
|
||||
while (attachedCardIterator.hasNext()) {
|
||||
++j;
|
||||
CardItem *attachedCard = attachedCardIterator.next();
|
||||
qreal childX = actualX - j * STACKED_CARD_OFFSET_X;
|
||||
qreal childY = y + 5;
|
||||
attachedCard->setPos(childX, childY);
|
||||
attachedCard->setRealZValue(ZValues::tableCardZValue(childX, childY));
|
||||
}
|
||||
}
|
||||
|
||||
resizeToContents();
|
||||
update();
|
||||
}
|
||||
|
||||
void TableZone::toggleTapped()
|
||||
{
|
||||
QList<QGraphicsItem *> selectedItemsRaw = scene()->selectedItems();
|
||||
QList<QGraphicsItem *> selectedItems;
|
||||
|
||||
auto isCardOnTable = [](const QGraphicsItem *item) {
|
||||
if (auto card = qgraphicsitem_cast<const CardItem *>(item)) {
|
||||
return card->getZone()->getName() == ZoneNames::TABLE;
|
||||
}
|
||||
return false;
|
||||
};
|
||||
|
||||
std::copy_if(selectedItemsRaw.begin(), selectedItemsRaw.end(), std::back_inserter(selectedItems), isCardOnTable);
|
||||
|
||||
bool tapAll = std::any_of(selectedItems.begin(), selectedItems.end(), [](const QGraphicsItem *item) {
|
||||
return !qgraphicsitem_cast<const CardItem *>(item)->getTapped();
|
||||
});
|
||||
QList<const ::google::protobuf::Message *> cmdList;
|
||||
for (const auto &selectedItem : selectedItems) {
|
||||
CardItem *temp = qgraphicsitem_cast<CardItem *>(selectedItem);
|
||||
if (temp->getTapped() != tapAll) {
|
||||
Command_SetCardAttr *cmd = new Command_SetCardAttr;
|
||||
cmd->set_zone(getLogic()->getName().toStdString());
|
||||
cmd->set_card_id(temp->getId());
|
||||
cmd->set_attribute(AttrTapped);
|
||||
cmd->set_attr_value(tapAll ? "1" : "0");
|
||||
cmdList.append(cmd);
|
||||
}
|
||||
}
|
||||
getLogic()->getPlayer()->getPlayerActions()->sendGameCommand(
|
||||
getLogic()->getPlayer()->getPlayerActions()->prepareGameCommand(cmdList));
|
||||
}
|
||||
|
||||
void TableZone::resizeToContents()
|
||||
{
|
||||
int xMax = 0;
|
||||
|
||||
// Find rightmost card position, which includes the left margin amount.
|
||||
for (int i = 0; i < getLogic()->getCards().size(); ++i) {
|
||||
if (getLogic()->getCards()[i]->pos().x() > xMax) {
|
||||
xMax = (int)getLogic()->getCards()[i]->pos().x();
|
||||
}
|
||||
}
|
||||
|
||||
// Minimum width is the rightmost card position plus enough room for
|
||||
// another card with padding, then margin.
|
||||
currentMinimumWidth = xMax + (2 * CardDimensions::WIDTH) + PADDING_X + MARGIN_RIGHT;
|
||||
|
||||
if (currentMinimumWidth < MIN_WIDTH) {
|
||||
currentMinimumWidth = MIN_WIDTH;
|
||||
}
|
||||
|
||||
if (currentMinimumWidth != width) {
|
||||
prepareGeometryChange();
|
||||
width = currentMinimumWidth;
|
||||
emit sizeChanged();
|
||||
}
|
||||
}
|
||||
|
||||
CardItem *TableZone::getCardFromGrid(const QPoint &gridPoint) const
|
||||
{
|
||||
for (int i = 0; i < getLogic()->getCards().size(); i++) {
|
||||
if (getLogic()->getCards().at(i)->getGridPoint() == gridPoint) {
|
||||
return getLogic()->getCards().at(i);
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
CardItem *TableZone::getCardFromCoords(const QPointF &point) const
|
||||
{
|
||||
QPoint gridPoint = mapToGrid(point);
|
||||
return getCardFromGrid(gridPoint);
|
||||
}
|
||||
|
||||
void TableZone::computeCardStackWidths()
|
||||
{
|
||||
// Each card stack is three grid points worth of card locations.
|
||||
// First pass: compute the number of cards at each card stack.
|
||||
QMap<int, int> cardStackCount;
|
||||
for (int i = 0; i < getLogic()->getCards().size(); ++i) {
|
||||
const QPoint &gridPoint = getLogic()->getCards()[i]->getGridPos();
|
||||
if (gridPoint.x() == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int key = getCardStackMapKey(gridPoint.x() / 3, gridPoint.y());
|
||||
cardStackCount.insert(key, cardStackCount.value(key, 0) + 1);
|
||||
}
|
||||
|
||||
// Second pass: compute the width at each card stack.
|
||||
cardStackWidth.clear();
|
||||
for (int i = 0; i < getLogic()->getCards().size(); ++i) {
|
||||
const QPoint &gridPoint = getLogic()->getCards()[i]->getGridPos();
|
||||
if (gridPoint.x() == -1) {
|
||||
continue;
|
||||
}
|
||||
|
||||
const int key = getCardStackMapKey(gridPoint.x() / 3, gridPoint.y());
|
||||
const int stackCount = cardStackCount.value(key, 0);
|
||||
if (stackCount == 1) {
|
||||
cardStackWidth.insert(key, CardDimensions::WIDTH + getLogic()->getCards()[i]->getAttachedCards().size() *
|
||||
STACKED_CARD_OFFSET_X);
|
||||
} else {
|
||||
cardStackWidth.insert(key, CardDimensions::WIDTH + (stackCount - 1) * STACKED_CARD_OFFSET_X);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
QPointF TableZone::mapFromGrid(QPoint gridPoint) const
|
||||
{
|
||||
qreal x, y;
|
||||
|
||||
// Start with margin plus stacked card offset
|
||||
x = MARGIN_LEFT + (gridPoint.x() % 3) * STACKED_CARD_OFFSET_X;
|
||||
|
||||
// Add in width of card stack plus padding for each column
|
||||
for (int i = 0; i < gridPoint.x() / 3; ++i) {
|
||||
const int key = getCardStackMapKey(i, gridPoint.y());
|
||||
x += cardStackWidth.value(key, CardDimensions::WIDTH) + PADDING_X;
|
||||
}
|
||||
|
||||
if (isInverted()) {
|
||||
gridPoint.setY(TABLEROWS - 1 - gridPoint.y());
|
||||
}
|
||||
|
||||
// Start with margin plus stacked card offset
|
||||
y = MARGIN_TOP + (gridPoint.x() % 3) * STACKED_CARD_OFFSET_Y;
|
||||
|
||||
// Add in card size and padding for each row
|
||||
for (int i = 0; i < gridPoint.y(); ++i) {
|
||||
y += CardDimensions::HEIGHT + PADDING_Y;
|
||||
}
|
||||
|
||||
return QPointF(x, y);
|
||||
}
|
||||
|
||||
QPoint TableZone::mapToGrid(const QPointF &mapPoint) const
|
||||
{
|
||||
// Begin by calculating the y-coordinate of the grid space, which will be
|
||||
// used for the x-coordinate.
|
||||
|
||||
// Offset point by the margin amount to reference point within grid area.
|
||||
int y = mapPoint.y() - MARGIN_TOP;
|
||||
|
||||
// Below calculation effectively rounds to the nearest grid point.
|
||||
const int gridPointHeight = CardDimensions::HEIGHT + PADDING_Y;
|
||||
int gridPointY = (y + PADDING_Y / 2) / gridPointHeight;
|
||||
|
||||
gridPointY = clampValidTableRow(gridPointY);
|
||||
|
||||
if (isInverted()) {
|
||||
gridPointY = TABLEROWS - 1 - gridPointY;
|
||||
}
|
||||
|
||||
// Calculating the x-coordinate of the grid space requires adding up the
|
||||
// widths of each card stack along the row.
|
||||
|
||||
// Offset point by the margin amount to reference point within grid area.
|
||||
int x = mapPoint.x() - MARGIN_LEFT + PADDING_X / 2;
|
||||
|
||||
// Maximum value is a card width from the right margin, referenced to the
|
||||
// grid area.
|
||||
const int xMax = width - MARGIN_LEFT - MARGIN_RIGHT - CardDimensions::WIDTH;
|
||||
|
||||
int xStack = 0;
|
||||
int xNextStack = 0;
|
||||
int nextStackCol = 0;
|
||||
while ((xNextStack <= x) && (xNextStack <= xMax)) {
|
||||
xStack = xNextStack;
|
||||
const int key = getCardStackMapKey(nextStackCol, gridPointY);
|
||||
xNextStack += cardStackWidth.value(key, CardDimensions::WIDTH) + PADDING_X;
|
||||
nextStackCol++;
|
||||
}
|
||||
int stackCol = qMax(nextStackCol - 1, 0);
|
||||
|
||||
// Have the stack column, need to refine to the grid column. Take the
|
||||
// difference between the point and the stack point and divide by stacked
|
||||
// card offsets.
|
||||
int xDiff = x - xStack;
|
||||
int gridPointX = stackCol * 3 + qMin(xDiff / STACKED_CARD_OFFSET_X, 2);
|
||||
|
||||
return QPoint(gridPointX, gridPointY);
|
||||
}
|
||||
|
||||
QPointF TableZone::closestGridPoint(const QPointF &point)
|
||||
{
|
||||
QPoint gridPoint = mapToGrid(point);
|
||||
gridPoint.setX((gridPoint.x() / 3) * 3);
|
||||
if (getCardFromGrid(gridPoint)) {
|
||||
gridPoint.setX(gridPoint.x() + 1);
|
||||
}
|
||||
if (getCardFromGrid(gridPoint)) {
|
||||
gridPoint.setX(gridPoint.x() + 1);
|
||||
}
|
||||
return mapFromGrid(gridPoint);
|
||||
}
|
||||
|
||||
int TableZone::clampValidTableRow(const int row)
|
||||
{
|
||||
if (row < 0) {
|
||||
return 0;
|
||||
}
|
||||
if (row >= TABLEROWS) {
|
||||
return TABLEROWS - 1;
|
||||
}
|
||||
return row;
|
||||
}
|
||||
|
||||
int TableZone::tableRowToGridY(int tableRow)
|
||||
{
|
||||
if (tableRow > 2) {
|
||||
tableRow = 1;
|
||||
}
|
||||
return clampValidTableRow(2 - tableRow);
|
||||
}
|
||||
210
cockatrice/src/game_graphics/zones/table_zone.h
Normal file
210
cockatrice/src/game_graphics/zones/table_zone.h
Normal file
|
|
@ -0,0 +1,210 @@
|
|||
/**
|
||||
* @file table_zone.h
|
||||
* @ingroup GameGraphicsZones
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef TABLEZONE_H
|
||||
#define TABLEZONE_H
|
||||
|
||||
#include "../../game/board/abstract_card_item.h"
|
||||
#include "../../game/zones/table_zone_logic.h"
|
||||
#include "select_zone.h"
|
||||
|
||||
/*
|
||||
* TableZone is the grid based rect where CardItems may be placed.
|
||||
* It is the main play zone and can be customized with background images.
|
||||
*
|
||||
* TODO: Refactor methods to make more readable, extract some logic to
|
||||
* private methods (Im looking at you TableZone::reorganizeCards())
|
||||
*/
|
||||
class TableZone : public SelectZone
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
signals:
|
||||
void sizeChanged();
|
||||
|
||||
private:
|
||||
static const int TABLEROWS = 3;
|
||||
|
||||
/*
|
||||
Margins between table edges and cards, paddings between cards
|
||||
*/
|
||||
static const int MARGIN_LEFT = 20;
|
||||
static const int MARGIN_RIGHT = 15;
|
||||
static const int MARGIN_TOP = 10;
|
||||
static const int MARGIN_BOTTOM = 30;
|
||||
static const int PADDING_X = 35;
|
||||
static const int PADDING_Y = 30;
|
||||
|
||||
/*
|
||||
Minimum width of the table zone including margins.
|
||||
*/
|
||||
static const int MIN_WIDTH = MARGIN_LEFT + (5 * CardDimensions::WIDTH) + MARGIN_RIGHT;
|
||||
|
||||
/*
|
||||
Offset sizes when cards are stacked on each other in the grid
|
||||
*/
|
||||
static const int STACKED_CARD_OFFSET_X = CardDimensions::WIDTH / 3;
|
||||
static const int STACKED_CARD_OFFSET_Y = PADDING_Y / 3;
|
||||
|
||||
/*
|
||||
Width of the box line drawn in the margin around the active player's area
|
||||
*/
|
||||
static const int BOX_LINE_WIDTH = 10;
|
||||
|
||||
/*
|
||||
Default inactive mask and border gradient
|
||||
*/
|
||||
static const QColor BACKGROUND_COLOR;
|
||||
static const QColor FADE_MASK;
|
||||
static const QColor GRADIENT_COLOR;
|
||||
static const QColor GRADIENT_COLORLESS;
|
||||
|
||||
/*
|
||||
Size and shape variables
|
||||
*/
|
||||
int width;
|
||||
int height;
|
||||
int currentMinimumWidth;
|
||||
|
||||
/*
|
||||
Internal cache for widths of stacks of cards by row and column.
|
||||
*/
|
||||
QMap<int, int> cardStackWidth;
|
||||
|
||||
/*
|
||||
Holds any custom background image for the TableZone
|
||||
*/
|
||||
QPixmap backgroundPixelMap;
|
||||
|
||||
/*
|
||||
If this TableZone is currently active
|
||||
*/
|
||||
bool active = false;
|
||||
|
||||
[[nodiscard]] bool isInverted() const;
|
||||
|
||||
private slots:
|
||||
/**
|
||||
Loads in any found custom background and updates
|
||||
*/
|
||||
void updateBg();
|
||||
|
||||
public slots:
|
||||
/**
|
||||
Reorganizes CardItems in the TableZone
|
||||
*/
|
||||
void reorganizeCards() override;
|
||||
|
||||
public:
|
||||
/**
|
||||
Constructs TableZone.
|
||||
|
||||
@param _p the Player
|
||||
@param parent defaults to null
|
||||
*/
|
||||
explicit TableZone(TableZoneLogic *_logic, QGraphicsItem *parent = nullptr);
|
||||
|
||||
/**
|
||||
@return a QRectF of the TableZone bounding box.
|
||||
*/
|
||||
[[nodiscard]] QRectF boundingRect() const override;
|
||||
|
||||
/**
|
||||
Render the TableZone
|
||||
|
||||
@param painter
|
||||
@param option
|
||||
*/
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
|
||||
|
||||
/**
|
||||
Toggles the selected items as tapped.
|
||||
*/
|
||||
void toggleTapped();
|
||||
|
||||
/**
|
||||
See HandleDropEventByGrid
|
||||
*/
|
||||
void
|
||||
handleDropEvent(const QList<CardDragItem *> &dragItems, CardZoneLogic *startZone, const QPoint &dropPoint) override;
|
||||
|
||||
/**
|
||||
Handles the placement of cards
|
||||
*/
|
||||
void
|
||||
handleDropEventByGrid(const QList<CardDragItem *> &dragItems, CardZoneLogic *startZone, const QPoint &gridPoint);
|
||||
|
||||
/**
|
||||
@return CardItem from grid location
|
||||
*/
|
||||
[[nodiscard]] CardItem *getCardFromGrid(const QPoint &gridPoint) const;
|
||||
|
||||
/**
|
||||
@return CardItem from coordinate location
|
||||
*/
|
||||
[[nodiscard]] CardItem *getCardFromCoords(const QPointF &point) const;
|
||||
|
||||
QPointF closestGridPoint(const QPointF &point) override;
|
||||
|
||||
static int clampValidTableRow(const int row);
|
||||
|
||||
/**
|
||||
* Converts a card's logical table row (0=creatures, 1=noncreatures, 2=lands)
|
||||
* to the corresponding grid Y coordinate. Cards with tableRow > 2 (e.g.,
|
||||
* instants/sorceries) default to the noncreatures row.
|
||||
*/
|
||||
static int tableRowToGridY(int tableRow);
|
||||
|
||||
/**
|
||||
Resizes the TableZone in case CardItems are within or
|
||||
outside of the TableZone constraints.
|
||||
*/
|
||||
void resizeToContents();
|
||||
|
||||
[[nodiscard]] int getMinimumWidth() const
|
||||
{
|
||||
return currentMinimumWidth;
|
||||
}
|
||||
void setWidth(qreal _width)
|
||||
{
|
||||
prepareGeometryChange();
|
||||
width = _width;
|
||||
}
|
||||
[[nodiscard]] qreal getWidth() const
|
||||
{
|
||||
return width;
|
||||
}
|
||||
void setActive(bool _active)
|
||||
{
|
||||
active = _active;
|
||||
update();
|
||||
}
|
||||
|
||||
private:
|
||||
void paintZoneOutline(QPainter *painter);
|
||||
void paintLandDivider(QPainter *painter);
|
||||
|
||||
/*
|
||||
Calculates card stack widths so mapping functions work properly
|
||||
*/
|
||||
void computeCardStackWidths();
|
||||
|
||||
/*
|
||||
Mapping functions for points to/from gridpoints.
|
||||
*/
|
||||
[[nodiscard]] QPointF mapFromGrid(QPoint gridPoint) const;
|
||||
[[nodiscard]] QPoint mapToGrid(const QPointF &mapPoint) const;
|
||||
|
||||
/*
|
||||
Helper function to create a single key from a card stack location.
|
||||
*/
|
||||
[[nodiscard]] int getCardStackMapKey(int x, int y) const
|
||||
{
|
||||
return x + (y * 1000);
|
||||
}
|
||||
};
|
||||
|
||||
#endif
|
||||
317
cockatrice/src/game_graphics/zones/view_zone.cpp
Normal file
317
cockatrice/src/game_graphics/zones/view_zone.cpp
Normal file
|
|
@ -0,0 +1,317 @@
|
|||
#include "view_zone.h"
|
||||
|
||||
#include "../../game/board/card_drag_item.h"
|
||||
#include "../../game/board/card_item.h"
|
||||
#include "../../game/player/player.h"
|
||||
#include "../../game/player/player_actions.h"
|
||||
#include "../../game/zones/view_zone_logic.h"
|
||||
|
||||
#include <QBrush>
|
||||
#include <QDebug>
|
||||
#include <QGraphicsSceneWheelEvent>
|
||||
#include <QPainter>
|
||||
#include <QtMath>
|
||||
#include <libcockatrice/protocol/pb/command_dump_zone.pb.h>
|
||||
#include <libcockatrice/protocol/pb/command_move_card.pb.h>
|
||||
#include <libcockatrice/protocol/pb/response_dump_zone.pb.h>
|
||||
#include <libcockatrice/protocol/pb/serverinfo_card.pb.h>
|
||||
#include <libcockatrice/protocol/pending_command.h>
|
||||
|
||||
/**
|
||||
* @param parent the parent QGraphicsWidget containing the reveal zone
|
||||
*/
|
||||
ZoneViewZone::ZoneViewZone(ZoneViewZoneLogic *_logic, QGraphicsItem *parent)
|
||||
: SelectZone(_logic, parent), bRect(QRectF()), minRows(0), groupBy(CardList::NoSort), sortBy(CardList::NoSort)
|
||||
{
|
||||
if (!(qobject_cast<ZoneViewZoneLogic *>(getLogic())->getRevealZone() &&
|
||||
!qobject_cast<ZoneViewZoneLogic *>(getLogic())->getWriteableRevealZone())) {
|
||||
qobject_cast<ZoneViewZoneLogic *>(getLogic())->getOriginalZone()->getViews().append(this);
|
||||
}
|
||||
connect(_logic, &ZoneViewZoneLogic::closeView, this, &ZoneViewZone::close);
|
||||
}
|
||||
|
||||
void ZoneViewZone::addToViews()
|
||||
{
|
||||
qobject_cast<ZoneViewZoneLogic *>(getLogic())->getOriginalZone()->getViews().append(this);
|
||||
}
|
||||
|
||||
void ZoneViewZone::removeFromViews()
|
||||
{
|
||||
qobject_cast<ZoneViewZoneLogic *>(getLogic())->getOriginalZone()->getViews().removeOne(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Deletes this ZoneView and removes it from the origZone's views.
|
||||
* You should normally call this method instead of deleteLater()
|
||||
*/
|
||||
void ZoneViewZone::close()
|
||||
{
|
||||
emit closed();
|
||||
if (!(qobject_cast<ZoneViewZoneLogic *>(getLogic())->getRevealZone() &&
|
||||
!qobject_cast<ZoneViewZoneLogic *>(getLogic())->getWriteableRevealZone())) {
|
||||
qobject_cast<ZoneViewZoneLogic *>(getLogic())->getOriginalZone()->getViews().removeOne(this);
|
||||
}
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
QRectF ZoneViewZone::boundingRect() const
|
||||
{
|
||||
return bRect;
|
||||
}
|
||||
|
||||
void ZoneViewZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
||||
{
|
||||
QBrush windowBrush(QColor(240, 240, 240));
|
||||
windowBrush.setColor(windowBrush.color().darker(150));
|
||||
painter->fillRect(boundingRect(), windowBrush);
|
||||
}
|
||||
|
||||
void ZoneViewZone::initializeCards(const QList<const ServerInfo_Card *> &cardList)
|
||||
{
|
||||
int numberCards = qobject_cast<ZoneViewZoneLogic *>(getLogic())->getNumberCards();
|
||||
if (!cardList.isEmpty()) {
|
||||
for (int i = 0; i < cardList.size(); ++i) {
|
||||
auto card = cardList[i];
|
||||
CardRef cardRef = {QString::fromStdString(card->name()), QString::fromStdString(card->provider_id())};
|
||||
auto copy = new CardItem(getLogic()->getPlayer(), this, cardRef, card->id());
|
||||
copy->setFaceDown(card->face_down());
|
||||
|
||||
getLogic()->addCard(copy, false, i);
|
||||
}
|
||||
reorganizeCards();
|
||||
} else if (!qobject_cast<ZoneViewZoneLogic *>(getLogic())->getOriginalZone()->contentsKnown()) {
|
||||
Command_DumpZone cmd;
|
||||
cmd.set_player_id(getLogic()->getPlayer()->getPlayerInfo()->getId());
|
||||
cmd.set_zone_name(getLogic()->getName().toStdString());
|
||||
cmd.set_number_cards(numberCards);
|
||||
cmd.set_is_reversed(qobject_cast<ZoneViewZoneLogic *>(getLogic())->getIsReversed());
|
||||
|
||||
PendingCommand *pend = getLogic()->getPlayer()->getPlayerActions()->prepareGameCommand(cmd);
|
||||
connect(pend, &PendingCommand::finished, this, &ZoneViewZone::zoneDumpReceived);
|
||||
getLogic()->getPlayer()->getPlayerActions()->sendGameCommand(pend);
|
||||
} else {
|
||||
const CardList &c = qobject_cast<ZoneViewZoneLogic *>(getLogic())->getOriginalZone()->getCards();
|
||||
int number = numberCards == -1 ? c.size() : (numberCards < c.size() ? numberCards : c.size());
|
||||
for (int i = 0; i < number; i++) {
|
||||
CardItem *card = c.at(i);
|
||||
auto copy = new CardItem(getLogic()->getPlayer(), this, card->getCardRef(), card->getId());
|
||||
copy->setFaceDown(card->getFaceDown());
|
||||
|
||||
getLogic()->addCard(copy, false, i);
|
||||
}
|
||||
reorganizeCards();
|
||||
}
|
||||
}
|
||||
|
||||
void ZoneViewZone::zoneDumpReceived(const Response &r)
|
||||
{
|
||||
const Response_DumpZone &resp = r.GetExtension(Response_DumpZone::ext);
|
||||
const int respCardListSize = resp.zone_info().card_list_size();
|
||||
for (int i = 0; i < respCardListSize; ++i) {
|
||||
const ServerInfo_Card &cardInfo = resp.zone_info().card_list(i);
|
||||
auto cardName = QString::fromStdString(cardInfo.name());
|
||||
auto cardProviderId = QString::fromStdString(cardInfo.provider_id());
|
||||
auto card = new CardItem(getLogic()->getPlayer(), this, {cardName, cardProviderId}, cardInfo.id(), getLogic());
|
||||
card->setFaceDown(cardInfo.face_down());
|
||||
getLogic()->rawInsertCard(card, i);
|
||||
}
|
||||
|
||||
qobject_cast<ZoneViewZoneLogic *>(getLogic())->updateCardIds(ZoneViewZoneLogic::INITIALIZE);
|
||||
reorganizeCards();
|
||||
// clang-format off
|
||||
emit getLogic()->cardCountChanged(); // emit keyword causes spurious spacing around ->
|
||||
// clang-format on
|
||||
}
|
||||
|
||||
// Because of boundingRect(), this function must not be called before the zone was added to a scene.
|
||||
void ZoneViewZone::reorganizeCards()
|
||||
{
|
||||
// filter cards
|
||||
CardList cardsToDisplay = CardList(getLogic()->getCards().getContentsKnown());
|
||||
for (auto card : getLogic()->getCards()) {
|
||||
if (filterString.check(card->getCard().getCardPtr())) {
|
||||
card->show();
|
||||
cardsToDisplay.append(card);
|
||||
} else {
|
||||
card->hide();
|
||||
}
|
||||
}
|
||||
|
||||
// sort cards
|
||||
QList<CardList::SortOption> sortOptions;
|
||||
if (groupBy != CardList::NoSort) {
|
||||
sortOptions << groupBy;
|
||||
}
|
||||
|
||||
if (sortBy != CardList::NoSort) {
|
||||
sortOptions << sortBy;
|
||||
|
||||
// implicitly sort by name at the end so that cards with the same name appear together
|
||||
if (sortBy != CardList::SortByName) {
|
||||
sortOptions << CardList::SortByName;
|
||||
}
|
||||
|
||||
// group printings together
|
||||
sortOptions << CardList::SortByPrinting;
|
||||
}
|
||||
|
||||
cardsToDisplay.sortBy(sortOptions);
|
||||
|
||||
// position cards
|
||||
GridSize gridSize;
|
||||
if (pileView) {
|
||||
gridSize = positionCardsForDisplay(cardsToDisplay, groupBy);
|
||||
} else {
|
||||
gridSize = positionCardsForDisplay(cardsToDisplay);
|
||||
}
|
||||
|
||||
// determine bounding rect
|
||||
qreal aleft = 0;
|
||||
qreal atop = 0;
|
||||
qreal awidth = gridSize.cols * CardDimensions::WIDTH_F + CardDimensions::WIDTH_HALF_F + HORIZONTAL_PADDING;
|
||||
qreal aheight = (gridSize.rows * CardDimensions::HEIGHT_F) / 3 + CardDimensions::HEIGHT_F * 1.3;
|
||||
optimumRect = QRectF(aleft, atop, awidth, aheight);
|
||||
|
||||
updateGeometry();
|
||||
emit optimumRectChanged();
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Sets the position of each card to the proper position for the view
|
||||
*
|
||||
* @param cards The cards to reposition. Will modify the cards in the list.
|
||||
* @param pileOption Property used to group cards for the piles. Expects `cards` to be sorted by that property. Pass in
|
||||
* NoSort to not make piles.
|
||||
*
|
||||
* @returns The number of rows and columns to display
|
||||
*/
|
||||
ZoneViewZone::GridSize ZoneViewZone::positionCardsForDisplay(CardList &cards, CardList::SortOption pileOption)
|
||||
{
|
||||
int cardCount = cards.size();
|
||||
|
||||
if (pileOption != CardList::NoSort) {
|
||||
int row = 0;
|
||||
int col = 0;
|
||||
int longestRow = 0;
|
||||
|
||||
QString lastColumnProp;
|
||||
|
||||
const auto extractor = CardList::getExtractorFor(pileOption);
|
||||
|
||||
for (int i = 0; i < cardCount; i++) {
|
||||
CardItem *c = cards.at(i);
|
||||
QString columnProp = extractor(c);
|
||||
|
||||
if (i) { // if not the first card
|
||||
if (columnProp == lastColumnProp) {
|
||||
row++; // add below current card
|
||||
} else { // if no match then move card to next column
|
||||
col++;
|
||||
row = 0;
|
||||
}
|
||||
}
|
||||
|
||||
lastColumnProp = columnProp;
|
||||
qreal x = col * CardDimensions::WIDTH_F;
|
||||
qreal y = row * CardDimensions::HEIGHT_F / 3;
|
||||
c->setPos(HORIZONTAL_PADDING + x, VERTICAL_PADDING + y);
|
||||
c->setRealZValue(i);
|
||||
longestRow = qMax(row, longestRow);
|
||||
}
|
||||
|
||||
// +1 because the row/col variables used in the calculations are 0-indexed but
|
||||
// GridSize expects the actual row/col count
|
||||
return GridSize{longestRow + 1, qMax(col + 1, 3)};
|
||||
|
||||
} else {
|
||||
int cols = qBound(1, qFloor(qSqrt((double)cardCount / 2)), 7);
|
||||
int rows = qMax(qCeil((double)cardCount / cols), 1);
|
||||
if (minRows == 0) {
|
||||
minRows = rows;
|
||||
} else if (rows < minRows) {
|
||||
rows = minRows;
|
||||
cols = qCeil((double)cardCount / minRows);
|
||||
}
|
||||
|
||||
if (cols < 2) {
|
||||
cols = 2;
|
||||
}
|
||||
|
||||
qCDebug(ViewZoneLog) << "reorganizeCards: rows=" << rows << "cols=" << cols;
|
||||
|
||||
for (int i = 0; i < cardCount; i++) {
|
||||
CardItem *c = cards.at(i);
|
||||
qreal x = (i / rows) * CardDimensions::WIDTH_F;
|
||||
qreal y = (i % rows) * CardDimensions::HEIGHT_F / 3;
|
||||
c->setPos(HORIZONTAL_PADDING + x, VERTICAL_PADDING + y);
|
||||
c->setRealZValue(i);
|
||||
}
|
||||
|
||||
return GridSize{rows, qMax(cols, 1)};
|
||||
}
|
||||
}
|
||||
|
||||
void ZoneViewZone::setFilterString(const QString &_filterString)
|
||||
{
|
||||
filterString = FilterString(_filterString);
|
||||
reorganizeCards();
|
||||
}
|
||||
|
||||
void ZoneViewZone::setGroupBy(CardList::SortOption _groupBy)
|
||||
{
|
||||
groupBy = _groupBy;
|
||||
reorganizeCards();
|
||||
}
|
||||
|
||||
void ZoneViewZone::setSortBy(CardList::SortOption _sortBy)
|
||||
{
|
||||
sortBy = _sortBy;
|
||||
reorganizeCards();
|
||||
}
|
||||
|
||||
void ZoneViewZone::setPileView(int _pileView)
|
||||
{
|
||||
pileView = _pileView;
|
||||
reorganizeCards();
|
||||
}
|
||||
|
||||
void ZoneViewZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
|
||||
CardZoneLogic *startZone,
|
||||
const QPoint & /*dropPoint*/)
|
||||
{
|
||||
Command_MoveCard cmd;
|
||||
cmd.set_start_player_id(startZone->getPlayer()->getPlayerInfo()->getId());
|
||||
cmd.set_start_zone(startZone->getName().toStdString());
|
||||
cmd.set_target_player_id(getLogic()->getPlayer()->getPlayerInfo()->getId());
|
||||
cmd.set_target_zone(getLogic()->getName().toStdString());
|
||||
cmd.set_x(0);
|
||||
cmd.set_y(0);
|
||||
cmd.set_is_reversed(qobject_cast<ZoneViewZoneLogic *>(getLogic())->getIsReversed());
|
||||
|
||||
for (int i = 0; i < dragItems.size(); ++i) {
|
||||
auto cardToMove = cmd.mutable_cards_to_move()->add_card();
|
||||
cardToMove->set_card_id(dragItems[i]->getId());
|
||||
if (dragItems[i]->isForceFaceDown()) {
|
||||
cardToMove->set_face_down(true);
|
||||
}
|
||||
}
|
||||
|
||||
getLogic()->getPlayer()->getPlayerActions()->sendGameCommand(cmd);
|
||||
}
|
||||
|
||||
void ZoneViewZone::setGeometry(const QRectF &rect)
|
||||
{
|
||||
prepareGeometryChange();
|
||||
setPos(rect.x(), rect.y());
|
||||
bRect = QRectF(0, 0, rect.width(), rect.height());
|
||||
}
|
||||
|
||||
QSizeF ZoneViewZone::sizeHint(Qt::SizeHint /*which*/, const QSizeF & /*constraint*/) const
|
||||
{
|
||||
return optimumRect.size();
|
||||
}
|
||||
|
||||
void ZoneViewZone::wheelEvent(QGraphicsSceneWheelEvent *event)
|
||||
{
|
||||
emit wheelEventReceived(event);
|
||||
}
|
||||
90
cockatrice/src/game_graphics/zones/view_zone.h
Normal file
90
cockatrice/src/game_graphics/zones/view_zone.h
Normal file
|
|
@ -0,0 +1,90 @@
|
|||
/**
|
||||
* @file view_zone.h
|
||||
* @ingroup GameGraphicsZones
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
|
||||
#ifndef ZONEVIEWERZONE_H
|
||||
#define ZONEVIEWERZONE_H
|
||||
|
||||
#include "../../game/zones/view_zone_logic.h"
|
||||
#include "select_zone.h"
|
||||
|
||||
#include <QGraphicsLayoutItem>
|
||||
#include <QLoggingCategory>
|
||||
#include <libcockatrice/filters/filter_string.h>
|
||||
#include <libcockatrice/protocol/pb/commands.pb.h>
|
||||
|
||||
inline Q_LOGGING_CATEGORY(ViewZoneLog, "view_zone");
|
||||
|
||||
class ZoneViewWidget;
|
||||
class Response;
|
||||
class ServerInfo_Card;
|
||||
class QGraphicsSceneWheelEvent;
|
||||
|
||||
/**
|
||||
* A CardZone that is a view into another CardZone.
|
||||
* If this zone is writable, then modifications to this zone will be reflected in the underlying zone.
|
||||
*
|
||||
* Most interactions with StackZones are handled through a zone view.
|
||||
* For example, viewing the deck/graveyard/exile is handled through a view.
|
||||
*
|
||||
* Handles both limited reveals (eg. look at top X cards) and full zone reveals (eg. searching the deck).
|
||||
*/
|
||||
class ZoneViewZone : public SelectZone, public QGraphicsLayoutItem
|
||||
{
|
||||
Q_OBJECT
|
||||
Q_INTERFACES(QGraphicsLayoutItem)
|
||||
private:
|
||||
static constexpr int HORIZONTAL_PADDING = 12;
|
||||
static constexpr int VERTICAL_PADDING = 5;
|
||||
|
||||
QRectF bRect, optimumRect;
|
||||
int minRows;
|
||||
FilterString filterString = FilterString("");
|
||||
CardList::SortOption groupBy, sortBy;
|
||||
bool pileView;
|
||||
|
||||
struct GridSize
|
||||
{
|
||||
int rows;
|
||||
int cols;
|
||||
};
|
||||
|
||||
GridSize positionCardsForDisplay(CardList &cards, CardList::SortOption pileOption = CardList::NoSort);
|
||||
|
||||
void
|
||||
handleDropEvent(const QList<CardDragItem *> &dragItems, CardZoneLogic *startZone, const QPoint &dropPoint) override;
|
||||
|
||||
public:
|
||||
ZoneViewZone(ZoneViewZoneLogic *_logic, QGraphicsItem *parent);
|
||||
[[nodiscard]] QRectF boundingRect() const override;
|
||||
void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override;
|
||||
void reorganizeCards() override;
|
||||
void initializeCards(const QList<const ServerInfo_Card *> &cardList = QList<const ServerInfo_Card *>());
|
||||
void setGeometry(const QRectF &rect) override;
|
||||
[[nodiscard]] QRectF getOptimumRect() const
|
||||
{
|
||||
return optimumRect;
|
||||
}
|
||||
public slots:
|
||||
void addToViews();
|
||||
void removeFromViews();
|
||||
void close();
|
||||
void setFilterString(const QString &_filterString);
|
||||
void setGroupBy(CardList::SortOption _groupBy);
|
||||
void setSortBy(CardList::SortOption _sortBy);
|
||||
void setPileView(int _pileView);
|
||||
private slots:
|
||||
void zoneDumpReceived(const Response &r);
|
||||
signals:
|
||||
void closed();
|
||||
void optimumRectChanged();
|
||||
void wheelEventReceived(QGraphicsSceneWheelEvent *event);
|
||||
|
||||
protected:
|
||||
[[nodiscard]] QSizeF sizeHint(Qt::SizeHint which, const QSizeF &constraint = QSizeF()) const override;
|
||||
void wheelEvent(QGraphicsSceneWheelEvent *event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
584
cockatrice/src/game_graphics/zones/view_zone_widget.cpp
Normal file
584
cockatrice/src/game_graphics/zones/view_zone_widget.cpp
Normal file
|
|
@ -0,0 +1,584 @@
|
|||
#include "view_zone_widget.h"
|
||||
|
||||
#include "../../client/settings/cache_settings.h"
|
||||
#include "../../filters/syntax_help.h"
|
||||
#include "../../game/board/card_item.h"
|
||||
#include "../../game/game_scene.h"
|
||||
#include "../../game/player/player.h"
|
||||
#include "../../game/player/player_actions.h"
|
||||
#include "../../game/z_values.h"
|
||||
#include "../../interface/pixel_map_generator.h"
|
||||
#include "view_zone.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QGraphicsLinearLayout>
|
||||
#include <QGraphicsProxyWidget>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QGraphicsView>
|
||||
#include <QLabel>
|
||||
#include <QPainter>
|
||||
#include <QScrollBar>
|
||||
#include <QStyle>
|
||||
#include <QStyleOption>
|
||||
#include <libcockatrice/protocol/pb/command_shuffle.pb.h>
|
||||
|
||||
namespace
|
||||
{
|
||||
constexpr qreal kTitleBarHeight = 24.0;
|
||||
constexpr qreal kMinVisibleWidth = 100.0;
|
||||
} // namespace
|
||||
|
||||
/**
|
||||
* @param _player the player the cards were revealed to.
|
||||
* @param _origZone the zone the cards were revealed from.
|
||||
* @param numberCards num of cards to reveal from the zone. Ex: scry the top 3 cards.
|
||||
* Pass in a negative number to reveal the entire zone.
|
||||
* -1 specifically will give the option to shuffle the zone upon closing the window.
|
||||
* @param _revealZone if false, the cards will be face down.
|
||||
* @param _writeableRevealZone whether the player can interact with the revealed cards.
|
||||
*/
|
||||
ZoneViewWidget::ZoneViewWidget(Player *_player,
|
||||
CardZoneLogic *_origZone,
|
||||
int numberCards,
|
||||
bool _revealZone,
|
||||
bool _writeableRevealZone,
|
||||
const QList<const ServerInfo_Card *> &cardList,
|
||||
bool _isReversed)
|
||||
: QGraphicsWidget(0, Qt::Window), canBeShuffled(_origZone->getIsShufflable()), player(_player)
|
||||
{
|
||||
setAcceptHoverEvents(true);
|
||||
setAttribute(Qt::WA_DeleteOnClose);
|
||||
setZValue(ZValues::ZONE_VIEW_WIDGET);
|
||||
setFlag(ItemIgnoresTransformations);
|
||||
|
||||
QGraphicsLinearLayout *vbox = new QGraphicsLinearLayout(Qt::Vertical);
|
||||
vbox->setSpacing(2);
|
||||
|
||||
// If the number is < 0, then it means that we can give the option to make the area sorted
|
||||
if (numberCards < 0) {
|
||||
// search edit
|
||||
searchEdit.setFocusPolicy(Qt::ClickFocus);
|
||||
searchEdit.setPlaceholderText(tr("Search by card name (or search expressions)"));
|
||||
searchEdit.setClearButtonEnabled(true);
|
||||
searchEdit.addAction(loadColorAdjustedPixmap("theme:icons/search"), QLineEdit::LeadingPosition);
|
||||
auto help = searchEdit.addAction(QPixmap("theme:icons/info"), QLineEdit::TrailingPosition);
|
||||
|
||||
connect(help, &QAction::triggered, this, [this] { createSearchSyntaxHelpWindow(&searchEdit); });
|
||||
|
||||
if (SettingsCache::instance().getFocusCardViewSearchBar()) {
|
||||
this->setActive(true);
|
||||
searchEdit.setFocus();
|
||||
}
|
||||
|
||||
QGraphicsProxyWidget *searchEditProxy = new QGraphicsProxyWidget;
|
||||
searchEditProxy->setWidget(&searchEdit);
|
||||
searchEditProxy->setZValue(ZValues::DRAG_ITEM);
|
||||
vbox->addItem(searchEditProxy);
|
||||
|
||||
// top row
|
||||
QGraphicsLinearLayout *hTopRow = new QGraphicsLinearLayout(Qt::Horizontal);
|
||||
|
||||
// groupBy options
|
||||
QGraphicsProxyWidget *groupBySelectorProxy = new QGraphicsProxyWidget;
|
||||
groupBySelectorProxy->setWidget(&groupBySelector);
|
||||
groupBySelectorProxy->setZValue(ZValues::TOP_UI);
|
||||
hTopRow->addItem(groupBySelectorProxy);
|
||||
|
||||
// sortBy options
|
||||
QGraphicsProxyWidget *sortBySelectorProxy = new QGraphicsProxyWidget;
|
||||
sortBySelectorProxy->setWidget(&sortBySelector);
|
||||
sortBySelectorProxy->setZValue(ZValues::DRAG_ITEM);
|
||||
hTopRow->addItem(sortBySelectorProxy);
|
||||
|
||||
vbox->addItem(hTopRow);
|
||||
|
||||
// line
|
||||
QGraphicsProxyWidget *lineProxy = new QGraphicsProxyWidget;
|
||||
QFrame *line = new QFrame;
|
||||
line->setFrameShape(QFrame::HLine);
|
||||
line->setFrameShadow(QFrame::Sunken);
|
||||
lineProxy->setWidget(line);
|
||||
vbox->addItem(lineProxy);
|
||||
|
||||
// bottom row
|
||||
QGraphicsLinearLayout *hBottomRow = new QGraphicsLinearLayout(Qt::Horizontal);
|
||||
|
||||
// pile view options
|
||||
QGraphicsProxyWidget *pileViewProxy = new QGraphicsProxyWidget;
|
||||
pileViewProxy->setWidget(&pileViewCheckBox);
|
||||
hBottomRow->addItem(pileViewProxy);
|
||||
|
||||
// shuffle options
|
||||
if (_origZone->getIsShufflable() && numberCards == -1) {
|
||||
shuffleCheckBox.setChecked(true);
|
||||
QGraphicsProxyWidget *shuffleProxy = new QGraphicsProxyWidget;
|
||||
shuffleProxy->setWidget(&shuffleCheckBox);
|
||||
hBottomRow->addItem(shuffleProxy);
|
||||
}
|
||||
|
||||
vbox->addItem(hBottomRow);
|
||||
}
|
||||
|
||||
extraHeight = vbox->sizeHint(Qt::PreferredSize).height();
|
||||
|
||||
QGraphicsLinearLayout *zoneHBox = new QGraphicsLinearLayout(Qt::Horizontal);
|
||||
|
||||
zoneContainer = new QGraphicsWidget(this);
|
||||
zoneContainer->setSizePolicy(QSizePolicy::Expanding, QSizePolicy::Expanding);
|
||||
zoneContainer->setFlag(QGraphicsItem::ItemClipsChildrenToShape);
|
||||
zoneHBox->addItem(zoneContainer);
|
||||
|
||||
scrollBar = new QScrollBar(Qt::Vertical);
|
||||
scrollBar->setMinimum(0);
|
||||
scrollBar->setSingleStep(20);
|
||||
scrollBar->setPageStep(200);
|
||||
connect(scrollBar, &QScrollBar::valueChanged, this, &ZoneViewWidget::handleScrollBarChange);
|
||||
scrollBarProxy = new ScrollableGraphicsProxyWidget;
|
||||
scrollBarProxy->setWidget(scrollBar);
|
||||
zoneHBox->addItem(scrollBarProxy);
|
||||
|
||||
vbox->addItem(zoneHBox);
|
||||
|
||||
zone = new ZoneViewZone(new ZoneViewZoneLogic(player, _origZone, numberCards, _revealZone, _writeableRevealZone,
|
||||
_isReversed, zoneContainer),
|
||||
zoneContainer);
|
||||
connect(zone, &ZoneViewZone::wheelEventReceived, scrollBarProxy, &ScrollableGraphicsProxyWidget::recieveWheelEvent);
|
||||
|
||||
retranslateUi();
|
||||
|
||||
// only wire up sort options after creating ZoneViewZone, since it segfaults otherwise.
|
||||
if (numberCards < 0) {
|
||||
connect(&groupBySelector, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
|
||||
&ZoneViewWidget::processGroupBy);
|
||||
connect(&sortBySelector, static_cast<void (QComboBox::*)(int)>(&QComboBox::currentIndexChanged), this,
|
||||
&ZoneViewWidget::processSortBy);
|
||||
connect(&pileViewCheckBox, &QCheckBox::QT_STATE_CHANGED, this, &ZoneViewWidget::processSetPileView);
|
||||
groupBySelector.setCurrentIndex(SettingsCache::instance().getZoneViewGroupByIndex());
|
||||
sortBySelector.setCurrentIndex(SettingsCache::instance().getZoneViewSortByIndex());
|
||||
pileViewCheckBox.setChecked(SettingsCache::instance().getZoneViewPileView());
|
||||
|
||||
if (CardList::NoSort == static_cast<CardList::SortOption>(groupBySelector.currentData().toInt())) {
|
||||
pileViewCheckBox.setEnabled(false);
|
||||
}
|
||||
|
||||
connect(&searchEdit, &QLineEdit::textChanged, zone, &ZoneViewZone::setFilterString);
|
||||
}
|
||||
|
||||
setLayout(vbox);
|
||||
|
||||
connect(zone, &ZoneViewZone::optimumRectChanged, this, [this] { resizeToZoneContents(); });
|
||||
connect(zone, &ZoneViewZone::closed, this, &ZoneViewWidget::zoneDeleted);
|
||||
zone->initializeCards(cardList);
|
||||
|
||||
// QLabel sizes aren't taken into account until the widget is rendered.
|
||||
// Force refresh after 1ms to fix glitchy rendering with long QLabels.
|
||||
auto *lastResizeBeforeVisibleTimer = new QTimer(this);
|
||||
connect(lastResizeBeforeVisibleTimer, &QTimer::timeout, this, [=, this] {
|
||||
resizeToZoneContents();
|
||||
disconnect(lastResizeBeforeVisibleTimer);
|
||||
lastResizeBeforeVisibleTimer->deleteLater();
|
||||
});
|
||||
lastResizeBeforeVisibleTimer->setSingleShot(true);
|
||||
lastResizeBeforeVisibleTimer->start(1);
|
||||
}
|
||||
|
||||
void ZoneViewWidget::processGroupBy(int index)
|
||||
{
|
||||
auto option = static_cast<CardList::SortOption>(groupBySelector.itemData(index).toInt());
|
||||
SettingsCache::instance().setZoneViewGroupByIndex(index);
|
||||
zone->setGroupBy(option);
|
||||
|
||||
// disable pile view checkbox if we're not grouping by anything
|
||||
pileViewCheckBox.setEnabled(option != CardList::NoSort);
|
||||
|
||||
// reset sortBy if it has the same value as groupBy
|
||||
if (option != CardList::NoSort &&
|
||||
option == static_cast<CardList::SortOption>(sortBySelector.currentData().toInt())) {
|
||||
sortBySelector.setCurrentIndex(1); // set to SortByName
|
||||
}
|
||||
}
|
||||
|
||||
void ZoneViewWidget::processSortBy(int index)
|
||||
{
|
||||
auto option = static_cast<CardList::SortOption>(sortBySelector.itemData(index).toInt());
|
||||
|
||||
// set to SortByName instead if it has the same value as groupBy
|
||||
if (option != CardList::NoSort &&
|
||||
option == static_cast<CardList::SortOption>(groupBySelector.currentData().toInt())) {
|
||||
sortBySelector.setCurrentIndex(1); // set to SortByName
|
||||
return;
|
||||
}
|
||||
|
||||
SettingsCache::instance().setZoneViewSortByIndex(index);
|
||||
zone->setSortBy(option);
|
||||
}
|
||||
|
||||
void ZoneViewWidget::processSetPileView(QT_STATE_CHANGED_T value)
|
||||
{
|
||||
SettingsCache::instance().setZoneViewPileView(value);
|
||||
zone->setPileView(value);
|
||||
}
|
||||
|
||||
void ZoneViewWidget::retranslateUi()
|
||||
{
|
||||
setWindowTitle(zone->getLogic()->getTranslatedName(false, CaseNominative));
|
||||
|
||||
{ // We can't change the strings after they're put into the QComboBox, so this is our workaround
|
||||
int oldIndex = groupBySelector.currentIndex();
|
||||
groupBySelector.clear();
|
||||
groupBySelector.addItem(tr("Ungrouped"), CardList::NoSort);
|
||||
groupBySelector.addItem(tr("Group by Type"), CardList::SortByMainType);
|
||||
groupBySelector.addItem(tr("Group by Mana Value"), CardList::SortByManaValue);
|
||||
groupBySelector.addItem(tr("Group by Color"), CardList::SortByColorGrouping);
|
||||
groupBySelector.setCurrentIndex(oldIndex);
|
||||
}
|
||||
|
||||
{
|
||||
int oldIndex = sortBySelector.currentIndex();
|
||||
sortBySelector.clear();
|
||||
sortBySelector.addItem(tr("Unsorted"), CardList::NoSort);
|
||||
sortBySelector.addItem(tr("Sort by Name"), CardList::SortByName);
|
||||
sortBySelector.addItem(tr("Sort by Type"), CardList::SortByType);
|
||||
sortBySelector.addItem(tr("Sort by Mana Cost"), CardList::SortByManaCost);
|
||||
sortBySelector.addItem(tr("Sort by Colors"), CardList::SortByColors);
|
||||
sortBySelector.addItem(tr("Sort by P/T"), CardList::SortByPt);
|
||||
sortBySelector.addItem(tr("Sort by Set"), CardList::SortBySet);
|
||||
sortBySelector.setCurrentIndex(oldIndex);
|
||||
}
|
||||
|
||||
shuffleCheckBox.setText(tr("shuffle when closing"));
|
||||
pileViewCheckBox.setText(tr("pile view"));
|
||||
}
|
||||
|
||||
void ZoneViewWidget::stopWindowDrag()
|
||||
{
|
||||
if (!draggingWindow) {
|
||||
return;
|
||||
}
|
||||
|
||||
draggingWindow = false;
|
||||
ungrabMouse();
|
||||
}
|
||||
|
||||
void ZoneViewWidget::startWindowDrag(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
draggingWindow = true;
|
||||
dragStartItemPos = pos();
|
||||
dragStartScreenPos = event->screenPos();
|
||||
dragView = findDragView(event->widget());
|
||||
|
||||
// need to grab mouse to receive events and not miss initial movement
|
||||
grabMouse();
|
||||
}
|
||||
|
||||
QRectF ZoneViewWidget::closeButtonRect(QWidget *styleWidget) const
|
||||
{
|
||||
const QRectF frameRectF = windowFrameRect();
|
||||
|
||||
// query the style for the close button position (handles macOS top-left placement)
|
||||
// Title bar rect MUST be local (0,0-based) for QStyle
|
||||
const QRect titleBarRect(0, 0, static_cast<int>(frameRectF.width()), static_cast<int>(kTitleBarHeight));
|
||||
|
||||
if (styleWidget) {
|
||||
QStyleOptionTitleBar opt;
|
||||
opt.initFrom(styleWidget);
|
||||
opt.rect = titleBarRect;
|
||||
opt.text = windowTitle();
|
||||
opt.icon = styleWidget->windowIcon();
|
||||
opt.titleBarFlags = Qt::Window | Qt::WindowTitleHint | Qt::WindowSystemMenuHint | Qt::WindowCloseButtonHint;
|
||||
|
||||
opt.subControls = QStyle::SC_TitleBarCloseButton;
|
||||
opt.activeSubControls = QStyle::SC_TitleBarCloseButton;
|
||||
opt.titleBarState = styleWidget->isActiveWindow() ? Qt::WindowActive : Qt::WindowNoState;
|
||||
|
||||
if (styleWidget->isActiveWindow()) {
|
||||
opt.state |= QStyle::State_Active;
|
||||
}
|
||||
|
||||
QRect r = styleWidget->style()->subControlRect(QStyle::CC_TitleBar, &opt, QStyle::SC_TitleBarCloseButton,
|
||||
styleWidget);
|
||||
|
||||
if (r.isValid() && !r.isEmpty()) {
|
||||
// Translate from local-titlebar coords → frame coords
|
||||
r.translate(frameRectF.topLeft().toPoint());
|
||||
return QRectF(r);
|
||||
}
|
||||
}
|
||||
|
||||
// Fallback: frame-relative top-right
|
||||
return QRectF(frameRectF.right() - kTitleBarHeight, frameRectF.top(), kTitleBarHeight, kTitleBarHeight);
|
||||
}
|
||||
|
||||
QGraphicsView *ZoneViewWidget::findDragView(QWidget *eventWidget) const
|
||||
{
|
||||
QWidget *current = eventWidget;
|
||||
while (current) {
|
||||
if (auto *view = qobject_cast<QGraphicsView *>(current)) {
|
||||
return view;
|
||||
}
|
||||
current = current->parentWidget();
|
||||
}
|
||||
|
||||
if (scene() && !scene()->views().isEmpty()) {
|
||||
return scene()->views().constFirst();
|
||||
}
|
||||
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
QPointF ZoneViewWidget::calcDraggedWindowPos(const QPoint &screenPos,
|
||||
const QPointF &scenePos,
|
||||
const QPointF &buttonDownScenePos) const
|
||||
{
|
||||
if (dragView && dragView->viewport()) {
|
||||
const QPoint vpStart = dragView->viewport()->mapFromGlobal(dragStartScreenPos);
|
||||
const QPoint vpNow = dragView->viewport()->mapFromGlobal(screenPos);
|
||||
const QPointF sceneStart = dragView->mapToScene(vpStart);
|
||||
const QPointF sceneNow = dragView->mapToScene(vpNow);
|
||||
return dragStartItemPos + (sceneNow - sceneStart);
|
||||
}
|
||||
|
||||
return dragStartItemPos + (scenePos - buttonDownScenePos);
|
||||
}
|
||||
|
||||
bool ZoneViewWidget::windowFrameEvent(QEvent *event)
|
||||
{
|
||||
if (event->type() == QEvent::UngrabMouse) {
|
||||
stopWindowDrag();
|
||||
return QGraphicsWidget::windowFrameEvent(event);
|
||||
}
|
||||
|
||||
auto *me = dynamic_cast<QGraphicsSceneMouseEvent *>(event);
|
||||
if (!me) {
|
||||
return QGraphicsWidget::windowFrameEvent(event);
|
||||
}
|
||||
|
||||
switch (event->type()) {
|
||||
case QEvent::GraphicsSceneMousePress:
|
||||
if (me->button() == Qt::LeftButton && windowFrameSectionAt(me->pos()) == Qt::TitleBarArea) {
|
||||
// avoid drag on close button
|
||||
if (closeButtonRect(me->widget()).contains(me->pos())) {
|
||||
me->accept();
|
||||
close();
|
||||
return true;
|
||||
}
|
||||
|
||||
startWindowDrag(me);
|
||||
me->accept();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case QEvent::GraphicsSceneMouseMove:
|
||||
if (draggingWindow) {
|
||||
if (!(me->buttons() & Qt::LeftButton)) {
|
||||
stopWindowDrag();
|
||||
} else {
|
||||
setPos(
|
||||
calcDraggedWindowPos(me->screenPos(), me->scenePos(), me->buttonDownScenePos(Qt::LeftButton)));
|
||||
}
|
||||
me->accept();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
case QEvent::GraphicsSceneMouseRelease:
|
||||
if (draggingWindow && me->button() == Qt::LeftButton) {
|
||||
stopWindowDrag();
|
||||
me->accept();
|
||||
return true;
|
||||
}
|
||||
break;
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
return QGraphicsWidget::windowFrameEvent(event);
|
||||
}
|
||||
|
||||
void ZoneViewWidget::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
// move if the scene routes moves while dragging
|
||||
if (draggingWindow && (event->buttons() & Qt::LeftButton)) {
|
||||
setPos(calcDraggedWindowPos(event->screenPos(), event->scenePos(), event->buttonDownScenePos(Qt::LeftButton)));
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
|
||||
QGraphicsWidget::mouseMoveEvent(event);
|
||||
}
|
||||
|
||||
void ZoneViewWidget::mouseReleaseEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (draggingWindow && event->button() == Qt::LeftButton) {
|
||||
stopWindowDrag();
|
||||
event->accept();
|
||||
return;
|
||||
}
|
||||
|
||||
QGraphicsWidget::mouseReleaseEvent(event);
|
||||
}
|
||||
|
||||
QVariant ZoneViewWidget::itemChange(GraphicsItemChange change, const QVariant &value)
|
||||
{
|
||||
if (change == QGraphicsItem::ItemPositionChange && scene()) {
|
||||
// Keep grab area in main view
|
||||
const QRectF sceneRect = scene()->sceneRect();
|
||||
const QPointF requestedPos = value.toPointF();
|
||||
QPointF desiredPos = requestedPos;
|
||||
|
||||
const qreal minX = sceneRect.left();
|
||||
const qreal maxX = qMax(minX, sceneRect.right() - kMinVisibleWidth);
|
||||
const qreal minY = sceneRect.top() + kTitleBarHeight;
|
||||
const qreal maxY = qMax(minY, sceneRect.bottom() - kTitleBarHeight);
|
||||
|
||||
desiredPos.setX(qBound(minX, desiredPos.x(), maxX));
|
||||
desiredPos.setY(qBound(minY, desiredPos.y(), maxY));
|
||||
return desiredPos;
|
||||
}
|
||||
|
||||
return QGraphicsWidget::itemChange(change, value);
|
||||
}
|
||||
|
||||
void ZoneViewWidget::resizeEvent(QGraphicsSceneResizeEvent *event)
|
||||
{
|
||||
// We need to manually resize the scroll bar whenever the window gets resized
|
||||
resizeScrollbar(event->newSize().height() - extraHeight - 10);
|
||||
}
|
||||
|
||||
void ZoneViewWidget::resizeScrollbar(const qreal newZoneHeight)
|
||||
{
|
||||
qreal totalZoneHeight = zone->getOptimumRect().height();
|
||||
qreal newMax = qMax(totalZoneHeight - newZoneHeight, 0.0);
|
||||
scrollBar->setMaximum(newMax);
|
||||
}
|
||||
|
||||
/**
|
||||
* Maps a height that is given as number of rows of cards to the actual height, given in pixels.
|
||||
*
|
||||
* @param rows Rows of cards
|
||||
* @return The height in pixels
|
||||
*/
|
||||
static qreal rowsToHeight(int rows)
|
||||
{
|
||||
const qreal cardsHeight = (rows + 1) * (CardDimensions::HEIGHT_F / 3);
|
||||
return cardsHeight + 5; // +5 padding to make the cutoff look nicer
|
||||
}
|
||||
|
||||
/**
|
||||
* Calculates the max initial height from the settings.
|
||||
* The max initial height setting is given as number of rows, so we need to map it to a height.
|
||||
**/
|
||||
static qreal calcMaxInitialHeight()
|
||||
{
|
||||
return rowsToHeight(SettingsCache::instance().getCardViewInitialRowsMax());
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief Handles edge cases in determining the next default zone height. We want the height to snap when the number of
|
||||
* rows changes, but not if the player has already expanded the window.
|
||||
*/
|
||||
static qreal determineNewZoneHeight(qreal oldZoneHeight)
|
||||
{
|
||||
// don't snap if window is taller than max initial height
|
||||
if (oldZoneHeight > calcMaxInitialHeight()) {
|
||||
return oldZoneHeight;
|
||||
}
|
||||
|
||||
return calcMaxInitialHeight();
|
||||
}
|
||||
|
||||
void ZoneViewWidget::resizeToZoneContents(bool forceInitialHeight)
|
||||
{
|
||||
QRectF zoneRect = zone->getOptimumRect();
|
||||
qreal totalZoneHeight = zoneRect.height();
|
||||
|
||||
qreal width = qMax(QGraphicsWidget::layout()->effectiveSizeHint(Qt::MinimumSize, QSizeF()).width(),
|
||||
zoneRect.width() + scrollBar->width() + 10);
|
||||
|
||||
QSizeF maxSize(width, zoneRect.height() + extraHeight + 10);
|
||||
|
||||
qreal currentZoneHeight = rect().height() - extraHeight - 10;
|
||||
qreal newZoneHeight = forceInitialHeight ? calcMaxInitialHeight() : determineNewZoneHeight(currentZoneHeight);
|
||||
|
||||
QSizeF initialSize(width, newZoneHeight + extraHeight + 10);
|
||||
|
||||
setMaximumSize(maxSize);
|
||||
resize(initialSize);
|
||||
resizeScrollbar(newZoneHeight);
|
||||
|
||||
zone->setGeometry(QRectF(0, -scrollBar->value(), zoneContainer->size().width(), totalZoneHeight));
|
||||
|
||||
if (layout()) {
|
||||
layout()->invalidate();
|
||||
}
|
||||
}
|
||||
|
||||
void ZoneViewWidget::handleScrollBarChange(int value)
|
||||
{
|
||||
zone->setY(-value);
|
||||
}
|
||||
|
||||
void ZoneViewWidget::closeEvent(QCloseEvent *event)
|
||||
{
|
||||
stopWindowDrag();
|
||||
disconnect(zone, &ZoneViewZone::closed, this, 0);
|
||||
// manually call zone->close in order to remove it from the origZones views
|
||||
zone->close();
|
||||
if (shuffleCheckBox.isChecked()) {
|
||||
player->getPlayerActions()->sendGameCommand(Command_Shuffle());
|
||||
}
|
||||
zoneDeleted();
|
||||
event->accept();
|
||||
}
|
||||
|
||||
void ZoneViewWidget::zoneDeleted()
|
||||
{
|
||||
emit closePressed(this);
|
||||
deleteLater();
|
||||
}
|
||||
|
||||
void ZoneViewWidget::initStyleOption(QStyleOption *option) const
|
||||
{
|
||||
QStyleOptionTitleBar *titleBar = qstyleoption_cast<QStyleOptionTitleBar *>(option);
|
||||
if (titleBar) {
|
||||
titleBar->icon = QPixmap("theme:cockatrice");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Expands/shrinks the window, depending on the current height as well as the configured initial and expanded max
|
||||
* heights.
|
||||
*/
|
||||
void ZoneViewWidget::expandWindow()
|
||||
{
|
||||
qreal maxInitialHeight = calcMaxInitialHeight();
|
||||
qreal maxExpandedHeight = rowsToHeight(SettingsCache::instance().getCardViewExpandedRowsMax());
|
||||
qreal height = rect().height() - extraHeight - 10;
|
||||
qreal maxHeight = maximumHeight() - extraHeight - 10;
|
||||
|
||||
// reset window to initial max height if...
|
||||
bool doResetSize =
|
||||
// current height is less than that
|
||||
(height < maxInitialHeight) ||
|
||||
// current height is at expanded max height
|
||||
(height == maxExpandedHeight) ||
|
||||
// current height is at actual max height, and actual max height is less than expanded max height
|
||||
(height == maxHeight && height > maxInitialHeight && height < maxExpandedHeight);
|
||||
|
||||
if (doResetSize) {
|
||||
resizeToZoneContents(true);
|
||||
} else {
|
||||
// expand/shrink window to expanded max height if current height is anywhere at or between initial max height
|
||||
// and actual max height
|
||||
resize(maximumSize().boundedTo({maximumWidth(), maxExpandedHeight + extraHeight + 10}));
|
||||
}
|
||||
}
|
||||
|
||||
void ZoneViewWidget::mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event)
|
||||
{
|
||||
if (event->pos().y() <= 0) {
|
||||
expandWindow();
|
||||
}
|
||||
}
|
||||
138
cockatrice/src/game_graphics/zones/view_zone_widget.h
Normal file
138
cockatrice/src/game_graphics/zones/view_zone_widget.h
Normal file
|
|
@ -0,0 +1,138 @@
|
|||
/**
|
||||
* @file view_zone_widget.h
|
||||
* @ingroup GameGraphicsZones
|
||||
* @brief TODO: Document this.
|
||||
*/
|
||||
#ifndef ZONEVIEWWIDGET_H
|
||||
#define ZONEVIEWWIDGET_H
|
||||
|
||||
#include "../../game/zones/card_zone_logic.h"
|
||||
|
||||
#include <QCheckBox>
|
||||
#include <QComboBox>
|
||||
#include <QGraphicsProxyWidget>
|
||||
#include <QGraphicsWidget>
|
||||
#include <QLineEdit>
|
||||
#include <QPointer>
|
||||
#include <libcockatrice/utility/macros.h>
|
||||
|
||||
class QLabel;
|
||||
class QPushButton;
|
||||
class CardZone;
|
||||
class ZoneViewZone;
|
||||
class Player;
|
||||
class CardDatabase;
|
||||
class QScrollBar;
|
||||
class GameScene;
|
||||
class ServerInfo_Card;
|
||||
class QGraphicsSceneMouseEvent;
|
||||
class QGraphicsSceneWheelEvent;
|
||||
class QStyleOption;
|
||||
class QGraphicsView;
|
||||
class QWidget;
|
||||
|
||||
class ScrollableGraphicsProxyWidget : public QGraphicsProxyWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
public slots:
|
||||
void recieveWheelEvent(QGraphicsSceneWheelEvent *event)
|
||||
{
|
||||
wheelEvent(event);
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* A QGraphicsWidget that holds a ZoneViewZone.
|
||||
*
|
||||
* Some zone views allow sorting.
|
||||
* This widget will display the sort options when relevant, and forward the values of the options to the ZoneViewZone.
|
||||
*/
|
||||
class ZoneViewWidget : public QGraphicsWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
ZoneViewZone *zone;
|
||||
QGraphicsWidget *zoneContainer;
|
||||
|
||||
QScrollBar *scrollBar;
|
||||
ScrollableGraphicsProxyWidget *scrollBarProxy;
|
||||
|
||||
QLineEdit searchEdit;
|
||||
QComboBox groupBySelector;
|
||||
QComboBox sortBySelector;
|
||||
QCheckBox shuffleCheckBox;
|
||||
QCheckBox pileViewCheckBox;
|
||||
|
||||
bool canBeShuffled;
|
||||
int extraHeight;
|
||||
Player *player;
|
||||
|
||||
bool draggingWindow = false;
|
||||
QPoint dragStartScreenPos;
|
||||
QPointF dragStartItemPos;
|
||||
QPointer<QGraphicsView> dragView;
|
||||
|
||||
void stopWindowDrag();
|
||||
void startWindowDrag(QGraphicsSceneMouseEvent *event);
|
||||
QRectF closeButtonRect(QWidget *styleWidget) const;
|
||||
/**
|
||||
* @brief Resolves the QGraphicsView to use for drag coordinate mapping
|
||||
*
|
||||
* @param eventWidget QWidget that originated the mouse event
|
||||
* @return The resolved QGraphicsView
|
||||
*/
|
||||
QGraphicsView *findDragView(QWidget *eventWidget) const;
|
||||
/**
|
||||
* @brief Calculates the desired widget position while dragging
|
||||
*
|
||||
* @param screenPos Global screen coordinates of the current mouse position
|
||||
* @param scenePos Scene coordinates of the current mouse position
|
||||
* @param buttonDownScenePos Scene coordinates of the initial mouse press position
|
||||
*
|
||||
* @return The new widget position in scene coordinates
|
||||
*/
|
||||
QPointF
|
||||
calcDraggedWindowPos(const QPoint &screenPos, const QPointF &scenePos, const QPointF &buttonDownScenePos) const;
|
||||
|
||||
void resizeScrollbar(qreal newZoneHeight);
|
||||
signals:
|
||||
void closePressed(ZoneViewWidget *zv);
|
||||
private slots:
|
||||
void processGroupBy(int value);
|
||||
void processSortBy(int value);
|
||||
void processSetPileView(QT_STATE_CHANGED_T value);
|
||||
void resizeToZoneContents(bool forceInitialHeight = false);
|
||||
void handleScrollBarChange(int value);
|
||||
void zoneDeleted();
|
||||
void resizeEvent(QGraphicsSceneResizeEvent * /* event */) override;
|
||||
void expandWindow();
|
||||
|
||||
public:
|
||||
ZoneViewWidget(Player *_player,
|
||||
CardZoneLogic *_origZone,
|
||||
int numberCards = 0,
|
||||
bool _revealZone = false,
|
||||
bool _writeableRevealZone = false,
|
||||
const QList<const ServerInfo_Card *> &cardList = QList<const ServerInfo_Card *>(),
|
||||
bool _isReversed = false);
|
||||
ZoneViewZone *getZone() const
|
||||
{
|
||||
return zone;
|
||||
}
|
||||
Player *getPlayer() const
|
||||
{
|
||||
return player;
|
||||
}
|
||||
void retranslateUi();
|
||||
|
||||
protected:
|
||||
void closeEvent(QCloseEvent *event) override;
|
||||
void initStyleOption(QStyleOption *option) const override;
|
||||
bool windowFrameEvent(QEvent *event) override;
|
||||
QVariant itemChange(GraphicsItemChange change, const QVariant &value) override;
|
||||
void mouseMoveEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
void mouseDoubleClickEvent(QGraphicsSceneMouseEvent *event) override;
|
||||
};
|
||||
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue