mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-10 20:13:24 -08:00
[Game] Repaint cached zones when the hover zoom moves
`TableZone` cuts the hovered card out of its own paint so zone washes, indicators, dimming and the land divider never draw over the zoom. It is a `DeviceCoordinateCache` item, so the card changing geometry never dirtied it and the stale hole stayed transparent, uncovering the black viewport as soon as the zoom crossed a zone edge. - Emit `hoverFootprintChanged(old, new)` from `setCardScale()` and once from `setHovered(false)`. - Repaint every overlapping `TableZone` on that signal. - Clip with the card's painted outline instead of its bounding rectangle, and treat a zoom covering the whole zone as "paint nothing".
This commit is contained in:
parent
abbab61e4f
commit
692edb30dd
6 changed files with 97 additions and 29 deletions
|
|
@ -290,7 +290,11 @@ void AbstractCardItem::setHovered(bool _hovered)
|
|||
// 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());
|
||||
const QRectF footprint = sceneBoundingRect();
|
||||
scene()->update(footprint);
|
||||
// The hole disappears the moment the card is no longer hovered, even though the
|
||||
// card has not changed size yet: tell the zones so they repaint it shut.
|
||||
emit hoverFootprintChanged(footprint, footprint);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -354,8 +358,13 @@ void AbstractCardItem::setCardScale(qreal _scale)
|
|||
const QRectF oldFootprint = sceneBoundingRect();
|
||||
prepareGeometryChange();
|
||||
setScale(_scale);
|
||||
const QRectF newFootprint = sceneBoundingRect();
|
||||
if (scene()) {
|
||||
scene()->update(oldFootprint.united(sceneBoundingRect()));
|
||||
scene()->update(oldFootprint.united(newFootprint));
|
||||
// Marking the scene region dirty is not enough for cached items: a zone that cuts a
|
||||
// hover hole out of itself keeps the hole from its last repaint, which then shows as
|
||||
// an uncovered black rectangle under the zoom.
|
||||
emit hoverFootprintChanged(oldFootprint, newFootprint);
|
||||
} else {
|
||||
update();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,6 +66,15 @@ signals:
|
|||
void faceDownChanged(bool faceDown);
|
||||
/** @brief Emitted when the hover shrink-out animation runs to completion while unhovered. */
|
||||
void hoverShrinkFinished(AbstractCardItem *card);
|
||||
/**
|
||||
* @brief Emitted when the hover zoom changes this card's scene footprint.
|
||||
*
|
||||
* Zones (and anything else that dodges the zoomed card) draw from the live footprint at
|
||||
* paint time but are cached: they need this to invalidate, or a stale hole is left behind.
|
||||
* Also emitted once when the hover ends, with both rects equal, because the hole
|
||||
* disappears without the card changing size.
|
||||
*/
|
||||
void hoverFootprintChanged(const QRectF &oldFootprint, const QRectF &newFootprint);
|
||||
|
||||
public:
|
||||
enum
|
||||
|
|
|
|||
|
|
@ -15,6 +15,7 @@
|
|||
#include "z_values.h"
|
||||
#include "zones/card_zone.h"
|
||||
#include "zones/select_zone.h"
|
||||
#include "zones/table_zone.h"
|
||||
#include "zones/view_zone.h"
|
||||
#include "zones/view_zone_widget.h"
|
||||
|
||||
|
|
@ -860,23 +861,20 @@ void GameScene::updateHoveredCard(CardItem *newCard)
|
|||
}
|
||||
return;
|
||||
}
|
||||
if (cardHoverWidget && cardHoverWidget->isVisible() && cardHoverWidget->getCardItem() == newCard) {
|
||||
if (cardHoverWidget && cardHoverWidget->isVisible() && !cardHoverWidget->isClosing() &&
|
||||
cardHoverWidget->getCardItem() == newCard) {
|
||||
// Panel open but zoom dropped (e.g. survived a Leave) — zoom back and refresh.
|
||||
beginCardHover(newCard);
|
||||
hoveredCard = newCard;
|
||||
cardHoverWidget->refreshDisplay();
|
||||
return;
|
||||
}
|
||||
if (cardHoverWidget && cardHoverWidget->isVisible()) {
|
||||
// Panel open elsewhere — move the zoom at once so it never strands, but let
|
||||
// the panel follow after the configured delay instead of strobing open on
|
||||
// every card swept across.
|
||||
beginCardHover(newCard);
|
||||
hoveredCard = newCard;
|
||||
if (cardHoverWidget && cardHoverWidget->isVisible() && !cardHoverWidget->isClosing()) {
|
||||
// A different card is hovered while the panel is up: close the old panel
|
||||
// first, then fall through to first contact so the new card follows the
|
||||
// normal dwell instead of the old panel trailing the cursor.
|
||||
hideCardHoverPopup();
|
||||
confirmArmedCard.clear();
|
||||
hoverPanelTarget = newCard;
|
||||
hoverPanelTimer->start(display.getHoverPanelDelayMs());
|
||||
return;
|
||||
}
|
||||
if (zoomTrigger != CardsDisplaySettings::HoverPanelTriggerHover &&
|
||||
panelTrigger != CardsDisplaySettings::HoverPanelTriggerHover) {
|
||||
|
|
@ -944,6 +942,8 @@ void GameScene::beginCardHover(CardItem *card)
|
|||
liftOwner = card;
|
||||
connect(card, &AbstractCardItem::hoverShrinkFinished, this, &GameScene::onHoverShrinkFinished,
|
||||
Qt::UniqueConnection);
|
||||
connect(card, &AbstractCardItem::hoverFootprintChanged, this, &GameScene::onHoverFootprintChanged,
|
||||
Qt::UniqueConnection);
|
||||
}
|
||||
|
||||
void GameScene::endCardHover(CardItem *card)
|
||||
|
|
@ -971,6 +971,25 @@ void GameScene::onHoverShrinkFinished(AbstractCardItem *card)
|
|||
restoreHoverLift();
|
||||
}
|
||||
|
||||
void GameScene::onHoverFootprintChanged(const QRectF &oldFootprint, const QRectF &newFootprint)
|
||||
{
|
||||
const QRectF dirty = oldFootprint.united(newFootprint);
|
||||
if (dirty.isEmpty()) {
|
||||
return;
|
||||
}
|
||||
// TableZone cuts the hovered card out of its own paint, but it is cached: the card
|
||||
// changing size does not dirty the zone, so the previous hole would stay transparent in
|
||||
// the cache — an uncovered black rectangle under the zoom whenever the card grows across
|
||||
// a zone boundary. Nudge only the zones the footprint touches; the scene repaint itself
|
||||
// is already scheduled by the card.
|
||||
const auto overlapping = items(dirty, Qt::IntersectsItemBoundingRect);
|
||||
for (QGraphicsItem *item : overlapping) {
|
||||
if (auto *zone = qobject_cast<TableZone *>(item->toGraphicsObject())) {
|
||||
zone->update();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void GameScene::restoreHoverLift()
|
||||
{
|
||||
for (const auto &[lifted, zValue] : hoverLiftedAncestors) {
|
||||
|
|
@ -1170,7 +1189,8 @@ void GameScene::fireTriggers(CardItem *card, bool zoomFired, bool panelFired)
|
|||
return;
|
||||
}
|
||||
|
||||
if (cardHoverWidget && cardHoverWidget->isVisible() && cardHoverWidget->getCardItem() == card) {
|
||||
if (cardHoverWidget && cardHoverWidget->isVisible() && !cardHoverWidget->isClosing() &&
|
||||
cardHoverWidget->getCardItem() == card) {
|
||||
cardHoverWidget->refreshDisplay();
|
||||
return;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
#include <QPair>
|
||||
#include <QPointF>
|
||||
#include <QPointer>
|
||||
#include <QRectF>
|
||||
#include <QSize>
|
||||
#include <QSizeF>
|
||||
#include <QString>
|
||||
|
|
@ -126,6 +127,14 @@ private:
|
|||
void restoreHoverLift();
|
||||
/** @brief Settles a finished shrink-out: clip container and ancestor lift restore. */
|
||||
void onHoverShrinkFinished(AbstractCardItem *card);
|
||||
/**
|
||||
* @brief Repaints the zones that dodge the zoomed card when its footprint moves.
|
||||
*
|
||||
* Those zones cut the hovered card out of their paint, but they are cached: another
|
||||
* item's geometry change does not dirty them, so without this the previous hole stays
|
||||
* transparent in the cache and a black rectangle shows under the zoom.
|
||||
*/
|
||||
void onHoverFootprintChanged(const QRectF &oldFootprint, const QRectF &newFootprint);
|
||||
|
||||
// Card hover popup methods
|
||||
void showCardHoverPopup(CardItem *card);
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@
|
|||
#include <QList>
|
||||
#include <QListIterator>
|
||||
#include <QPainter>
|
||||
#include <QPainterPath>
|
||||
#include <QRectF>
|
||||
#include <QSharedPointer>
|
||||
#include <QString>
|
||||
|
|
@ -34,6 +35,7 @@
|
|||
#include <libcockatrice/game/zones/table_zone_logic.h>
|
||||
#include <libcockatrice/settings/interface_settings.h>
|
||||
#include <libcockatrice/utility/zone_names.h>
|
||||
#include <optional>
|
||||
#include <string>
|
||||
|
||||
namespace google
|
||||
|
|
@ -121,10 +123,14 @@ void TableZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*opti
|
|||
// The hovered (zoomed) card may spill over this zone: cut it out of every layer
|
||||
// below so washes, indicators, dimming and the land divider never paint over it,
|
||||
// whatever the stacking order is.
|
||||
const QRect hoverHole = hoveredCardHole();
|
||||
const bool hasHole = !hoverHole.isNull();
|
||||
if (hasHole) {
|
||||
painter->setClipRegion(QRegion(boundingRect().toRect()) - QRegion(hoverHole));
|
||||
const std::optional<QPainterPath> hoverHole = hoveredCardHole();
|
||||
const bool clipped = hoverHole.has_value();
|
||||
if (clipped && hoverHole->isEmpty()) {
|
||||
// The zoom covers this zone entirely: there is nothing left of it to paint.
|
||||
return;
|
||||
}
|
||||
if (clipped) {
|
||||
painter->setClipPath(*hoverHole);
|
||||
}
|
||||
|
||||
if (playmatActive) {
|
||||
|
|
@ -152,31 +158,38 @@ void TableZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*opti
|
|||
|
||||
paintLandDivider(painter);
|
||||
|
||||
if (hasHole) {
|
||||
if (clipped) {
|
||||
painter->setClipping(false);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @brief The hovered card's footprint in zone coordinates, or null when it does not
|
||||
* overlap this zone. Lets every paint layer dodge the zoomed card instead of painting
|
||||
* over it.
|
||||
* @brief This zone's paint area minus the hovered card's painted outline, in zone coordinates.
|
||||
*
|
||||
* @c std::nullopt when no card is hovered or it does not overlap this zone; an *empty* path when
|
||||
* the zoom covers this zone entirely, which means nothing of it is left to paint. Lets every
|
||||
* paint layer dodge the zoomed card instead of painting over it.
|
||||
*/
|
||||
QRect TableZone::hoveredCardHole() const
|
||||
std::optional<QPainterPath> TableZone::hoveredCardHole() const
|
||||
{
|
||||
auto *scene = qobject_cast<GameScene *>(this->scene());
|
||||
if (!scene) {
|
||||
return {};
|
||||
return std::nullopt;
|
||||
}
|
||||
CardItem *hovered = scene->getHoveredCard();
|
||||
if (!hovered) {
|
||||
return {};
|
||||
return std::nullopt;
|
||||
}
|
||||
QRect hole = mapRectFromScene(hovered->sceneBoundingRect()).toRect();
|
||||
if (!hole.intersects(boundingRect().toRect())) {
|
||||
return {};
|
||||
// Cut out the card's painted outline, not its bounding rectangle: the rectangle's corners
|
||||
// are never covered by the artwork (rounded corners, antialiased edges), so they would stay
|
||||
// black around the zoom.
|
||||
const QPainterPath cardOutline = sceneTransform().inverted().map(hovered->sceneTransform().map(hovered->shape()));
|
||||
if (!cardOutline.boundingRect().intersects(boundingRect())) {
|
||||
return std::nullopt;
|
||||
}
|
||||
return hole & boundingRect().toRect();
|
||||
QPainterPath zoneArea;
|
||||
zoneArea.addRect(boundingRect());
|
||||
return zoneArea.subtracted(cardOutline);
|
||||
}
|
||||
|
||||
void TableZone::onPlaymatChanged(bool active)
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@
|
|||
#include <QPointF>
|
||||
#include <QtGlobal>
|
||||
#include <libcockatrice/game/zones/table_zone_logic.h>
|
||||
#include <optional>
|
||||
#include <qtmetamacros.h>
|
||||
|
||||
class CardDragItem;
|
||||
|
|
@ -26,6 +27,7 @@ class CardZoneLogic;
|
|||
class QColor;
|
||||
class QGraphicsItem;
|
||||
class QPainter;
|
||||
class QPainterPath;
|
||||
template <typename T> class QList;
|
||||
|
||||
/**
|
||||
|
|
@ -221,7 +223,13 @@ private:
|
|||
|
||||
void paintZoneOutline(QPainter *painter);
|
||||
void paintLandDivider(QPainter *painter);
|
||||
[[nodiscard]] QRect hoveredCardHole() const;
|
||||
/**
|
||||
* @brief This zone's paint area minus the hovered card's painted outline, in zone coordinates.
|
||||
*
|
||||
* @c std::nullopt when no card is hovered or it does not overlap this zone; an *empty* path when
|
||||
* the hovered card covers this zone entirely, in which case nothing of the zone is visible.
|
||||
*/
|
||||
[[nodiscard]] std::optional<QPainterPath> hoveredCardHole() const;
|
||||
|
||||
/*
|
||||
Calculates card stack widths so mapping functions work properly
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue