mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-04-27 07:48:01 -07:00
Merge 4c93ebf14d into b1fe4c85d3
This commit is contained in:
commit
973d8d2a4a
12 changed files with 402 additions and 111 deletions
|
|
@ -85,7 +85,11 @@ const CardInfo &AbstractCardItem::getCardInfo() const
|
|||
void AbstractCardItem::setRealZValue(qreal _zValue)
|
||||
{
|
||||
realZValue = _zValue;
|
||||
setZValue(_zValue);
|
||||
// During hover, zValue is overridden to HOVERED_CARD. Layout operations
|
||||
// like reorganizeCards() call setRealZValue() on all cards including the
|
||||
// hovered one — skip setZValue() here to avoid clobbering the override.
|
||||
if (!isHovered)
|
||||
setZValue(_zValue);
|
||||
}
|
||||
|
||||
QSizeF AbstractCardItem::getTranslatedSize(QPainter *painter) const
|
||||
|
|
@ -213,8 +217,16 @@ void AbstractCardItem::setHovered(bool _hovered)
|
|||
if (isHovered == _hovered)
|
||||
return;
|
||||
|
||||
if (_hovered)
|
||||
if (_hovered) {
|
||||
processHoverEvent();
|
||||
} else {
|
||||
// Mark the hovered card's current scene footprint dirty so overlapped
|
||||
// sibling zones (e.g. StackZone) repaint after the card moves away.
|
||||
if (scene()) {
|
||||
scene()->update(sceneBoundingRect());
|
||||
}
|
||||
}
|
||||
|
||||
isHovered = _hovered;
|
||||
setZValue(_hovered ? ZValues::HOVERED_CARD : realZValue);
|
||||
setScale(_hovered && SettingsCache::instance().getScaleCards() ? 1.1 : 1);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* @file abstract_card_item.h
|
||||
* @ingroup GameGraphicsCards
|
||||
* @brief TODO: Document this.
|
||||
* @brief Base class for graphical card items, providing shared rendering, identity, and interaction logic.
|
||||
*/
|
||||
|
||||
#ifndef ABSTRACTCARDITEM_H
|
||||
|
|
@ -96,6 +96,10 @@ public:
|
|||
}
|
||||
void setRealZValue(qreal _zValue);
|
||||
void setHovered(bool _hovered);
|
||||
bool getIsHovered() const
|
||||
{
|
||||
return isHovered;
|
||||
}
|
||||
QString getColor() const
|
||||
{
|
||||
return color;
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@
|
|||
#include "phases_toolbar.h"
|
||||
#include "player/player.h"
|
||||
#include "player/player_graphics_item.h"
|
||||
#include "zones/select_zone.h"
|
||||
#include "zones/view_zone.h"
|
||||
#include "zones/view_zone_widget.h"
|
||||
|
||||
|
|
@ -344,12 +345,26 @@ void GameScene::updateHover(const QPointF &scenePos)
|
|||
void GameScene::updateHoveredCard(CardItem *newCard)
|
||||
{
|
||||
if (hoveredCard && (newCard != hoveredCard))
|
||||
hoveredCard->setHovered(false);
|
||||
endCardHover(hoveredCard);
|
||||
if (newCard && (newCard != hoveredCard))
|
||||
newCard->setHovered(true);
|
||||
beginCardHover(newCard);
|
||||
hoveredCard = newCard;
|
||||
}
|
||||
|
||||
void GameScene::beginCardHover(CardItem *card)
|
||||
{
|
||||
card->setHovered(true);
|
||||
if (auto *zone = SelectZone::findOwningSelectZone(card))
|
||||
zone->escapeClipForHover(card);
|
||||
}
|
||||
|
||||
void GameScene::endCardHover(CardItem *card)
|
||||
{
|
||||
if (auto *zone = SelectZone::findOwningSelectZone(card))
|
||||
zone->restoreClipAfterHover(card);
|
||||
card->setHovered(false);
|
||||
}
|
||||
|
||||
CardZone *GameScene::findTopmostZone(const QList<QGraphicsItem *> &items)
|
||||
{
|
||||
for (QGraphicsItem *item : items)
|
||||
|
|
@ -484,6 +499,8 @@ bool GameScene::event(QEvent *event)
|
|||
{
|
||||
if (event->type() == QEvent::GraphicsSceneMouseMove)
|
||||
updateHover(static_cast<QGraphicsSceneMouseEvent *>(event)->scenePos());
|
||||
else if (event->type() == QEvent::Leave)
|
||||
updateHoveredCard(nullptr);
|
||||
|
||||
return QGraphicsScene::event(event);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -56,6 +56,12 @@ private:
|
|||
*/
|
||||
void updateHover(const QPointF &scenePos);
|
||||
|
||||
/// Activates hover state and escapes the card from its clip container so hover scaling is visible beyond zone
|
||||
/// bounds.
|
||||
void beginCardHover(CardItem *card);
|
||||
/// Deactivates hover state and restores the card to its clip container.
|
||||
void endCardHover(CardItem *card);
|
||||
|
||||
public:
|
||||
/**
|
||||
* @brief Constructs the GameScene.
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ CardZone::CardZone(CardZoneLogic *_logic, QGraphicsItem *parent)
|
|||
void CardZone::onCardAdded(CardItem *addedCard)
|
||||
{
|
||||
addedCard->setParentItem(this);
|
||||
addedCard->setVisible(true);
|
||||
addedCard->update();
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* @file card_zone.h
|
||||
* @ingroup GameGraphicsZones
|
||||
* @brief TODO: Document this.
|
||||
* @brief Base graphics item for zones that contain cards.
|
||||
*/
|
||||
|
||||
#ifndef CARDZONE_H
|
||||
|
|
@ -40,7 +40,10 @@ protected:
|
|||
}
|
||||
public slots:
|
||||
bool showContextMenu(const QPoint &screenPos);
|
||||
void onCardAdded(CardItem *addedCard);
|
||||
/// @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
|
||||
|
|
|
|||
|
|
@ -27,6 +27,10 @@ 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()) {
|
||||
|
|
@ -34,9 +38,7 @@ void HandZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
|
|||
if (point.x() < static_cast<CardItem *>(getLogic()->getCards().at(x))->scenePos().x())
|
||||
break;
|
||||
} else {
|
||||
for (x = 0; x < getLogic()->getCards().size(); x++)
|
||||
if (point.y() < static_cast<CardItem *>(getLogic()->getCards().at(x))->scenePos().y())
|
||||
break;
|
||||
x = calcDropIndexFromY(dropPoint.y());
|
||||
}
|
||||
|
||||
Command_MoveCard cmd;
|
||||
|
|
@ -58,7 +60,7 @@ QRectF HandZone::boundingRect() const
|
|||
if (SettingsCache::instance().getHorizontalHand())
|
||||
return QRectF(0, 0, width, CardDimensions::HEIGHT_F + 10);
|
||||
else
|
||||
return QRectF(0, 0, 100, zoneHeight);
|
||||
return QRectF(0, 0, CardDimensions::WIDTH_F * 1.5, zoneHeight);
|
||||
}
|
||||
|
||||
void HandZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
||||
|
|
@ -78,35 +80,31 @@ void HandZone::reorganizeCards()
|
|||
qreal totalWidth =
|
||||
leftJustified ? boundingRect().width() - (1 * xPadding) - 5 : boundingRect().width() - 2 * xPadding;
|
||||
|
||||
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);
|
||||
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);
|
||||
}
|
||||
c->setRealZValue(i);
|
||||
}
|
||||
} else {
|
||||
qreal totalWidth = boundingRect().width();
|
||||
qreal cardWidth = getLogic()->getCards().at(0)->boundingRect().width();
|
||||
qreal xspace = 5;
|
||||
qreal x1 = xspace;
|
||||
qreal x2 = totalWidth - xspace - cardWidth;
|
||||
|
||||
for (int i = 0; i < cardCount; i++) {
|
||||
CardItem *card = getLogic()->getCards().at(i);
|
||||
qreal x = (i % 2) ? x2 : x1;
|
||||
qreal y = divideCardSpaceInZone(i, cardCount, boundingRect().height(),
|
||||
getLogic()->getCards().at(0)->boundingRect().height());
|
||||
card->setPos(x, y);
|
||||
card->setRealZValue(i);
|
||||
}
|
||||
// No clip container: hand cards should always be visible to the player.
|
||||
const auto params = buildStackParams();
|
||||
layoutCardsVertically(params);
|
||||
}
|
||||
}
|
||||
update();
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* @file hand_zone.h
|
||||
* @ingroup GameGraphicsZones
|
||||
* @brief TODO: Document this.
|
||||
* @brief Graphical zone for the player's hand, supporting horizontal and vertical layouts.
|
||||
*/
|
||||
|
||||
#ifndef HANDZONE_H
|
||||
|
|
@ -14,7 +14,8 @@ class HandZone : public SelectZone
|
|||
{
|
||||
Q_OBJECT
|
||||
private:
|
||||
qreal width, zoneHeight;
|
||||
qreal width = 0.0;
|
||||
qreal zoneHeight;
|
||||
private slots:
|
||||
void updateBg();
|
||||
public slots:
|
||||
|
|
|
|||
|
|
@ -4,38 +4,207 @@
|
|||
#include "../board/card_item.h"
|
||||
#include "../game_scene.h"
|
||||
|
||||
#include <QDebug>
|
||||
#include <QGraphicsRectItem>
|
||||
#include <QGraphicsSceneMouseEvent>
|
||||
#include <QtMath>
|
||||
|
||||
qreal divideCardSpaceInZone(qreal index, int cardCount, qreal totalHeight, qreal cardHeight, bool reverse)
|
||||
namespace
|
||||
{
|
||||
qreal cardMinOverlap = cardHeight * SettingsCache::instance().getStackCardOverlapPercent() / 100;
|
||||
qreal desiredHeight = cardHeight * cardCount - cardMinOverlap * (cardCount - 1);
|
||||
qreal y;
|
||||
if (desiredHeight > totalHeight) {
|
||||
if (reverse) {
|
||||
y = index / ((totalHeight - cardHeight) / (cardCount - 1));
|
||||
qreal stackingOffset(qreal cardHeight)
|
||||
{
|
||||
const qreal overlapPercent = SettingsCache::instance().getStackCardOverlapPercent();
|
||||
return cardHeight * (100.0 - overlapPercent) / 100.0;
|
||||
}
|
||||
} // namespace
|
||||
|
||||
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 {
|
||||
y = index * (totalHeight - cardHeight) / (cardCount - 1);
|
||||
}
|
||||
} else {
|
||||
qreal start = (totalHeight - desiredHeight) / 2;
|
||||
if (reverse) {
|
||||
if (index <= start) {
|
||||
return 0;
|
||||
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;
|
||||
}
|
||||
y = (index - start) / (cardHeight - cardMinOverlap);
|
||||
} else {
|
||||
y = index * (cardHeight - cardMinOverlap) + start;
|
||||
fitOffset = (params.totalHeight - reservedForBottomCard) / (params.cardCount - 1);
|
||||
effectiveOffset = qMax(params.minOffset, qMin(params.desiredOffset, fitOffset));
|
||||
}
|
||||
}
|
||||
return y;
|
||||
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)) {
|
||||
|
|
@ -56,7 +225,7 @@ void SelectZone::mouseMoveEvent(QGraphicsSceneMouseEvent *event)
|
|||
continue;
|
||||
}
|
||||
|
||||
bool inRect = selectionRect.intersects(card->mapRectToParent(card->boundingRect()));
|
||||
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);
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* @file select_zone.h
|
||||
* @ingroup GameGraphicsZones
|
||||
* @brief TODO: Document this.
|
||||
* @brief Base class for zones where cards are laid out and individually interactable.
|
||||
*/
|
||||
|
||||
#ifndef SELECTZONE_H
|
||||
|
|
@ -10,6 +10,9 @@
|
|||
#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.
|
||||
|
|
@ -17,19 +20,113 @@
|
|||
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;
|
||||
qreal totalHeight;
|
||||
qreal cardHeight;
|
||||
qreal desiredOffset;
|
||||
qreal minOffset = 0.0;
|
||||
/// 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;
|
||||
|
||||
public:
|
||||
SelectZone(CardZoneLogic *logic, QGraphicsItem *parent = nullptr);
|
||||
};
|
||||
|
||||
qreal divideCardSpaceInZone(qreal index, int cardCount, qreal totalHeight, qreal cardHeight, bool reverse = false);
|
||||
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -1,10 +1,9 @@
|
|||
#include "stack_zone.h"
|
||||
|
||||
#include "../../client/settings/cache_settings.h"
|
||||
#include "../../interface/theme_manager.h"
|
||||
#include "../board/arrow_item.h"
|
||||
#include "../board/card_drag_item.h"
|
||||
#include "../board/card_item.h"
|
||||
#include "../card_dimensions.h"
|
||||
#include "../player/player.h"
|
||||
#include "../player/player_actions.h"
|
||||
#include "logic/stack_zone_logic.h"
|
||||
|
|
@ -27,7 +26,7 @@ void StackZone::updateBg()
|
|||
|
||||
QRectF StackZone::boundingRect() const
|
||||
{
|
||||
return {0, 0, 100, zoneHeight};
|
||||
return {0, 0, CardDimensions::WIDTH_F * 1.5, zoneHeight};
|
||||
}
|
||||
|
||||
void StackZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
||||
|
|
@ -40,7 +39,15 @@ void StackZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
|
|||
CardZoneLogic *startZone,
|
||||
const QPoint &dropPoint)
|
||||
{
|
||||
if (startZone == nullptr || startZone->getPlayer() == nullptr) {
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -49,36 +56,12 @@ void StackZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
|
|||
cmd.set_start_zone(startZone->getName().toStdString());
|
||||
cmd.set_target_player_id(getLogic()->getPlayer()->getPlayerInfo()->getId());
|
||||
cmd.set_target_zone(getLogic()->getName().toStdString());
|
||||
|
||||
int index = 0;
|
||||
|
||||
if (!getLogic()->getCards().isEmpty()) {
|
||||
const auto cardCount = static_cast<int>(getLogic()->getCards().size());
|
||||
const auto &card = getLogic()->getCards().at(0);
|
||||
|
||||
index = qRound(divideCardSpaceInZone(dropPoint.y(), cardCount, boundingRect().height(),
|
||||
card->boundingRect().height(), true));
|
||||
|
||||
// divideCardSpaceInZone is not guaranteed to return a valid index
|
||||
// currently, so clamp it to avoid crashes.
|
||||
index = qBound(0, index, cardCount - 1);
|
||||
|
||||
if (startZone == getLogic()) {
|
||||
const auto &dragItem = dragItems.at(0);
|
||||
const auto &card = getLogic()->getCards().at(index);
|
||||
|
||||
if (card->getId() == dragItem->getId()) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
cmd.set_x(index);
|
||||
cmd.set_y(0);
|
||||
|
||||
for (CardDragItem *item : dragItems) {
|
||||
for (const CardDragItem *item : dragItems) {
|
||||
if (item) {
|
||||
auto cardToMove = cmd.mutable_cards_to_move()->add_card();
|
||||
auto *cardToMove = cmd.mutable_cards_to_move()->add_card();
|
||||
cardToMove->set_card_id(item->getId());
|
||||
if (item->isForceFaceDown()) {
|
||||
cardToMove->set_face_down(true);
|
||||
|
|
@ -89,24 +72,22 @@ void StackZone::handleDropEvent(const QList<CardDragItem *> &dragItems,
|
|||
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 cardCount = static_cast<int>(getLogic()->getCards().size());
|
||||
qreal totalWidth = boundingRect().width();
|
||||
qreal cardWidth = getLogic()->getCards().at(0)->boundingRect().width();
|
||||
qreal xspace = 5;
|
||||
qreal x1 = xspace;
|
||||
qreal x2 = totalWidth - xspace - cardWidth;
|
||||
|
||||
for (int i = 0; i < cardCount; i++) {
|
||||
CardItem *card = getLogic()->getCards().at(i);
|
||||
qreal x = (i % 2) ? x2 : x1;
|
||||
qreal y = divideCardSpaceInZone(i, cardCount, boundingRect().height(),
|
||||
getLogic()->getCards().at(0)->boundingRect().height());
|
||||
card->setPos(x, y);
|
||||
card->setRealZValue(i);
|
||||
}
|
||||
const auto params = buildStackParams(MIN_CARD_VISIBLE);
|
||||
layoutCardsVertically(params);
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* @file stack_zone.h
|
||||
* @ingroup GameGraphicsZones
|
||||
* @brief TODO: Document this.
|
||||
* @brief Graphical zone for the stack, displaying cards in a vertical pile.
|
||||
*/
|
||||
|
||||
#ifndef STACKZONE_H
|
||||
|
|
@ -20,6 +20,8 @@ private slots:
|
|||
|
||||
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;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue