mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-11 20:43:25 -08:00
[Game] Wire hover zoom/panel triggers through the scene
This commit is contained in:
parent
b2f0528fc7
commit
abbab61e4f
6 changed files with 609 additions and 13 deletions
|
|
@ -1,6 +1,7 @@
|
||||||
#include "game_scene.h"
|
#include "game_scene.h"
|
||||||
|
|
||||||
#include "../client/settings/cache_settings.h"
|
#include "../client/settings/cache_settings.h"
|
||||||
|
#include "../interface/widgets/cards/card_hover_widget.h"
|
||||||
#include "../interface/theme_manager.h"
|
#include "../interface/theme_manager.h"
|
||||||
#include "animated_item.h"
|
#include "animated_item.h"
|
||||||
#include "board/abstract_card_item.h"
|
#include "board/abstract_card_item.h"
|
||||||
|
|
@ -11,16 +12,20 @@
|
||||||
#include "player/menu/player_menu.h"
|
#include "player/menu/player_menu.h"
|
||||||
#include "player/player_graphics_item.h"
|
#include "player/player_graphics_item.h"
|
||||||
#include "player/player_target.h"
|
#include "player/player_target.h"
|
||||||
|
#include "z_values.h"
|
||||||
#include "zones/card_zone.h"
|
#include "zones/card_zone.h"
|
||||||
#include "zones/select_zone.h"
|
#include "zones/select_zone.h"
|
||||||
#include "zones/view_zone.h"
|
#include "zones/view_zone.h"
|
||||||
#include "zones/view_zone_widget.h"
|
#include "zones/view_zone_widget.h"
|
||||||
|
|
||||||
#include <QBasicTimer>
|
#include <QBasicTimer>
|
||||||
|
#include <QCursor>
|
||||||
#include <QDebug>
|
#include <QDebug>
|
||||||
#include <QEvent>
|
#include <QEvent>
|
||||||
#include <QGraphicsItem>
|
#include <QGraphicsItem>
|
||||||
|
#include <QGraphicsObject>
|
||||||
#include <QGraphicsSceneMouseEvent>
|
#include <QGraphicsSceneMouseEvent>
|
||||||
|
#include <QGraphicsSceneWheelEvent>
|
||||||
#include <QGraphicsView>
|
#include <QGraphicsView>
|
||||||
#include <QListIterator>
|
#include <QListIterator>
|
||||||
#include <QMenu>
|
#include <QMenu>
|
||||||
|
|
@ -29,9 +34,11 @@
|
||||||
#include <QObject>
|
#include <QObject>
|
||||||
#include <QPoint>
|
#include <QPoint>
|
||||||
#include <QRectF>
|
#include <QRectF>
|
||||||
|
#include <QScrollBar>
|
||||||
#include <QSet>
|
#include <QSet>
|
||||||
#include <QSharedPointer>
|
#include <QSharedPointer>
|
||||||
#include <QtMath>
|
#include <QTimer>
|
||||||
|
#include <QtGlobal>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <functional>
|
#include <functional>
|
||||||
#include <libcockatrice/game/abstract_game.h>
|
#include <libcockatrice/game/abstract_game.h>
|
||||||
|
|
@ -43,6 +50,7 @@
|
||||||
#include <libcockatrice/game/player/player_logic.h>
|
#include <libcockatrice/game/player/player_logic.h>
|
||||||
#include <libcockatrice/game/zones/card_zone_logic.h>
|
#include <libcockatrice/game/zones/card_zone_logic.h>
|
||||||
#include <libcockatrice/game/zones/view_zone_logic.h>
|
#include <libcockatrice/game/zones/view_zone_logic.h>
|
||||||
|
#include <libcockatrice/settings/cards_display_settings.h>
|
||||||
#include <libcockatrice/settings/interface_settings.h>
|
#include <libcockatrice/settings/interface_settings.h>
|
||||||
#include <libcockatrice/utility/zone_names.h>
|
#include <libcockatrice/utility/zone_names.h>
|
||||||
#include <numeric>
|
#include <numeric>
|
||||||
|
|
@ -75,6 +83,22 @@ GameScene::GameScene(PhasesToolbar *_phasesToolbar, QObject *parent)
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Card hover popup (cardHoverWidget is created lazily on first show)
|
||||||
|
hoverActivateTimer = new QTimer(this);
|
||||||
|
hoverActivateTimer->setSingleShot(true);
|
||||||
|
hoverActivateTimer->setInterval(500);
|
||||||
|
connect(hoverActivateTimer, &QTimer::timeout, this, &GameScene::onHoverActivateTimeout);
|
||||||
|
|
||||||
|
hoverPanelTimer = new QTimer(this);
|
||||||
|
hoverPanelTimer->setSingleShot(true);
|
||||||
|
hoverPanelTimer->setInterval(200);
|
||||||
|
connect(hoverPanelTimer, &QTimer::timeout, this, &GameScene::onHoverPanelTimeout);
|
||||||
|
|
||||||
|
hoverGraceTimer = new QTimer(this);
|
||||||
|
hoverGraceTimer->setSingleShot(true);
|
||||||
|
hoverGraceTimer->setInterval(200);
|
||||||
|
connect(hoverGraceTimer, &QTimer::timeout, this, &GameScene::onHoverGraceTimeout);
|
||||||
|
|
||||||
rearrange();
|
rearrange();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -93,6 +117,9 @@ GameScene::~GameScene()
|
||||||
|
|
||||||
delete animationTimer;
|
delete animationTimer;
|
||||||
animationTimer = nullptr;
|
animationTimer = nullptr;
|
||||||
|
delete hoverActivateTimer;
|
||||||
|
delete hoverPanelTimer;
|
||||||
|
delete hoverGraceTimer;
|
||||||
|
|
||||||
// Delete all ArrowItems before QGraphicsScene's base destructor runs.
|
// Delete all ArrowItems before QGraphicsScene's base destructor runs.
|
||||||
// QGraphicsScene::~QGraphicsScene() destroys items in arbitrary order.
|
// QGraphicsScene::~QGraphicsScene() destroys items in arbitrary order.
|
||||||
|
|
@ -239,6 +266,21 @@ void GameScene::onCardRightClicked(AbstractCardItem *abstractCard, QPoint screen
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The zoom and panel triggers are independently bindable: a right-click fires
|
||||||
|
// whichever phases listen to it. Once right-click drives either phase the context
|
||||||
|
// menu stays suppressed — popping it would steal the flow (and the panel already
|
||||||
|
// carries every action when it is the panel trigger).
|
||||||
|
auto &display = SettingsCache::instance().cardsDisplay();
|
||||||
|
const bool zoomFired = display.getHoverZoomTrigger() == CardsDisplaySettings::HoverPanelTriggerRightClick;
|
||||||
|
const bool panelFired = display.getHoverPanelTrigger() == CardsDisplaySettings::HoverPanelTriggerRightClick;
|
||||||
|
if (zoomFired || panelFired) {
|
||||||
|
fireTriggers(card, zoomFired, panelFired);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hide the hover popup when the context menu opens
|
||||||
|
hideCardHoverPopup();
|
||||||
|
|
||||||
setActiveCard(card);
|
setActiveCard(card);
|
||||||
|
|
||||||
if (auto *menu = view->getPlayerMenu()->updateCardMenu(card->getState())) {
|
if (auto *menu = view->getPlayerMenu()->updateCardMenu(card->getState())) {
|
||||||
|
|
@ -690,7 +732,6 @@ void GameScene::requestArrowDeletion(int playerId, int arrowId)
|
||||||
emit arrowDeletionRequested(playerId, arrowId);
|
emit arrowDeletionRequested(playerId, arrowId);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameScene::onCardZoneChanged(CardState *card, bool sameZone)
|
void GameScene::onCardZoneChanged(CardState *card, bool sameZone)
|
||||||
{
|
{
|
||||||
auto *item = qobject_cast<CardItem *>(card->parent());
|
auto *item = qobject_cast<CardItem *>(card->parent());
|
||||||
|
|
@ -710,6 +751,11 @@ void GameScene::onCardZoneChanged(CardState *card, bool sameZone)
|
||||||
for (auto *arrow : toDelete) {
|
for (auto *arrow : toDelete) {
|
||||||
deleteArrow(arrow->getCreatorId(), arrow->getId());
|
deleteArrow(arrow->getCreatorId(), arrow->getId());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// The hover panel stays open on a moved card: follow it instead of stranding.
|
||||||
|
if (!sameZone && cardHoverWidget && cardHoverWidget->getCardItem() == item) {
|
||||||
|
positionCardHoverPopup();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameScene::onCardViewRefreshRequested(CardState *card, bool zoneChanged, PlayerLogic *newOwner)
|
void GameScene::onCardViewRefreshRequested(CardState *card, bool zoneChanged, PlayerLogic *newOwner)
|
||||||
|
|
@ -746,27 +792,129 @@ void GameScene::clearArrowsForPlayerLocally(int playerId)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---------- Hover Handling ----------
|
// ---------- Hover Handling ----------
|
||||||
|
|
||||||
|
CardItem *GameScene::getHoveredCard() const
|
||||||
|
{
|
||||||
|
return hoveredCard;
|
||||||
|
}
|
||||||
|
|
||||||
void GameScene::updateHover(const QPointF &scenePos)
|
void GameScene::updateHover(const QPointF &scenePos)
|
||||||
{
|
{
|
||||||
auto itemList = items(scenePos, Qt::IntersectsItemBoundingRect, Qt::DescendingOrder, getViewTransform());
|
updateHoveredCard(pickCardAt(scenePos));
|
||||||
|
}
|
||||||
|
|
||||||
|
CardItem *GameScene::pickCardAt(const QPointF &scenePos)
|
||||||
|
{
|
||||||
|
auto itemList = items(scenePos, Qt::IntersectsItemBoundingRect, Qt::DescendingOrder, getViewTransform());
|
||||||
CardZone *zone = findTopmostZone(itemList);
|
CardZone *zone = findTopmostZone(itemList);
|
||||||
CardItem *topCard = zone ? findTopmostCardInZone(itemList, zone) : nullptr;
|
CardItem *topCard = zone ? findTopmostCardInZone(itemList, zone) : nullptr;
|
||||||
updateHoveredCard(topCard);
|
if (!topCard && hoveredCard) {
|
||||||
|
const QRectF guard = hoveredCard->sceneBoundingRect().adjusted(-8, -8, 8, 8);
|
||||||
|
if (guard.contains(scenePos)) {
|
||||||
|
// The zoomed card overflows onto neighboring zones, where naive topmost-zone
|
||||||
|
// picking attributes the cursor to the zone underneath. While the cursor is
|
||||||
|
// still over the zoomed footprint (with hysteresis for edge jiggle), keep it.
|
||||||
|
// A genuinely different topmost card still takes over.
|
||||||
|
return hoveredCard;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return topCard;
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameScene::updateHoveredCard(CardItem *newCard)
|
void GameScene::updateHoveredCard(CardItem *newCard)
|
||||||
{
|
{
|
||||||
|
// Safety: if the popup's card was destroyed, hide the popup immediately
|
||||||
|
if (cardHoverWidget && cardHoverWidget->isVisible() && !cardHoverWidget->hasCard()) {
|
||||||
|
hideCardHoverPopup();
|
||||||
|
}
|
||||||
|
// The hovered card may have been destroyed without ending the hover: its QPointer
|
||||||
|
// nulled itself, but the ancestor lift is still outstanding. Restore it now.
|
||||||
|
if (!hoveredCard && !hoverLiftedAncestors.isEmpty()) {
|
||||||
|
restoreHoverLift();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Hovering alone never starts anything anymore: the trigger (dwell, click, wheel,
|
||||||
|
// shortcut) resolves first, then the zoom follows. This keeps the feature calm.
|
||||||
|
bool wasHoveringCard = (hoveredCard != nullptr);
|
||||||
if (hoveredCard && (newCard != hoveredCard)) {
|
if (hoveredCard && (newCard != hoveredCard)) {
|
||||||
endCardHover(hoveredCard);
|
endCardHover(hoveredCard);
|
||||||
|
hoveredCard = nullptr;
|
||||||
}
|
}
|
||||||
if (newCard && (newCard != hoveredCard)) {
|
|
||||||
beginCardHover(newCard);
|
auto &display = SettingsCache::instance().cardsDisplay();
|
||||||
|
const int zoomTrigger = display.getHoverZoomTrigger();
|
||||||
|
const int panelTrigger = display.getHoverPanelTrigger();
|
||||||
|
const bool doubleConfirm = display.getHoverPanelDoubleConfirm() && zoomTrigger == panelTrigger;
|
||||||
|
|
||||||
|
if (newCard) {
|
||||||
|
// Cancel any pending grace timer (mouse is back on a card)
|
||||||
|
hoverGracePending = false;
|
||||||
|
hoverGraceTimer->stop();
|
||||||
|
|
||||||
|
if (newCard == hoveredCard) {
|
||||||
|
// Steady state: zoom already up. Refresh an open panel, otherwise nothing —
|
||||||
|
// in particular this must not re-fire an armed double-confirm.
|
||||||
|
if (cardHoverWidget && cardHoverWidget->isVisible() && cardHoverWidget->getCardItem() == newCard) {
|
||||||
|
cardHoverWidget->refreshDisplay();
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (cardHoverWidget && cardHoverWidget->isVisible() && 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;
|
||||||
|
confirmArmedCard.clear();
|
||||||
|
hoverPanelTarget = newCard;
|
||||||
|
hoverPanelTimer->start(display.getHoverPanelDelayMs());
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (zoomTrigger != CardsDisplaySettings::HoverPanelTriggerHover &&
|
||||||
|
panelTrigger != CardsDisplaySettings::HoverPanelTriggerHover) {
|
||||||
|
// Mouse contact alone drives neither phase in this config.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (doubleConfirm && confirmArmedCard == newCard) {
|
||||||
|
// Repeat hover on an armed card: zoom and show at once, no second dwell.
|
||||||
|
beginCardHover(newCard);
|
||||||
|
hoveredCard = newCard;
|
||||||
|
confirmArmedCard.clear();
|
||||||
|
showCardHoverPopup(newCard);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (newCard != hoverPopupTarget) {
|
||||||
|
// First contact (or a new card): dwell, then resolve on timeout.
|
||||||
|
hoverPopupTarget = newCard;
|
||||||
|
hoverActivateTimer->start(display.getHoverZoomDelayMs());
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// No card hovered: drop any pending dwell/panel timers (kills stale shows) and start
|
||||||
|
// the grace timer only when the mouse just left a card with the panel open.
|
||||||
|
hoverPopupTarget = nullptr;
|
||||||
|
hoverPanelTarget = nullptr;
|
||||||
|
hoverActivateTimer->stop();
|
||||||
|
hoverPanelTimer->stop();
|
||||||
|
// Non-hover zoom arms only make sense while approaching the card: leaving
|
||||||
|
// re-arms the zoom for the next explicit trigger instead of showing the panel.
|
||||||
|
// (Hover-trigger arms survive the leave so re-entering completes the repeat.)
|
||||||
|
if (zoomTrigger != CardsDisplaySettings::HoverPanelTriggerHover) {
|
||||||
|
confirmArmedCard.clear();
|
||||||
|
}
|
||||||
|
if (wasHoveringCard && cardHoverWidget && cardHoverWidget->isVisible() &&
|
||||||
|
!cardHoverWidget->isMouseOverWidget() && !hoverGracePending) {
|
||||||
|
hoverGracePending = true;
|
||||||
|
hoverGraceTimer->start();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
hoveredCard = newCard;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameScene::beginCardHover(CardItem *card)
|
void GameScene::beginCardHover(CardItem *card)
|
||||||
|
|
@ -775,16 +923,317 @@ void GameScene::beginCardHover(CardItem *card)
|
||||||
if (auto *zone = SelectZone::findOwningSelectZone(card)) {
|
if (auto *zone = SelectZone::findOwningSelectZone(card)) {
|
||||||
zone->escapeClipForHover(card);
|
zone->escapeClipForHover(card);
|
||||||
}
|
}
|
||||||
|
// Lift the whole ancestor chain above their siblings: the zoomed card overflows its
|
||||||
|
// own zone, and Qt orders whole subtrees level by level, so no single z-value wins
|
||||||
|
// every comparison on its own. Harmless elsewhere (areas and zones tile without
|
||||||
|
// overlapping; arrows, zone views and drag items keep their higher layers).
|
||||||
|
// Restore first: if a previous hover never ended (its card was destroyed mid-hover),
|
||||||
|
// its chain is still outstanding and must land before a new one is recorded.
|
||||||
|
restoreHoverLift();
|
||||||
|
QGraphicsItem *ancestor = card->parentItem();
|
||||||
|
while (ancestor) {
|
||||||
|
// The chain is QGraphicsObject-derived in practice; a plain QGraphicsItem
|
||||||
|
// ancestor cannot be guarded, so it keeps its z-value instead of risking an
|
||||||
|
// unrestorable lift.
|
||||||
|
if (QGraphicsObject *ancestorObject = ancestor->toGraphicsObject()) {
|
||||||
|
hoverLiftedAncestors.append({QPointer<QGraphicsObject>(ancestorObject), ancestor->zValue()});
|
||||||
|
ancestor->setZValue(ZValues::HOVERED_AREA);
|
||||||
|
}
|
||||||
|
ancestor = ancestor->parentItem();
|
||||||
|
}
|
||||||
|
liftOwner = card;
|
||||||
|
connect(card, &AbstractCardItem::hoverShrinkFinished, this, &GameScene::onHoverShrinkFinished,
|
||||||
|
Qt::UniqueConnection);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameScene::endCardHover(CardItem *card)
|
void GameScene::endCardHover(CardItem *card)
|
||||||
{
|
{
|
||||||
if (auto *zone = SelectZone::findOwningSelectZone(card)) {
|
// Only the zoom-out starts here: the z-value, the clip escape and the ancestor
|
||||||
zone->restoreClipAfterHover(card);
|
// lift stay put until the shrink finishes (onHoverShrinkFinished), so siblings and
|
||||||
}
|
// the clip container cannot cut through the still-enlarged card.
|
||||||
card->setHovered(false);
|
card->setHovered(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void GameScene::onHoverShrinkFinished(AbstractCardItem *card)
|
||||||
|
{
|
||||||
|
auto *cardItem = qobject_cast<CardItem *>(card);
|
||||||
|
if (!cardItem) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
// Per-card and idempotent: always settle, even if a newer hover owns the lift.
|
||||||
|
if (auto *zone = SelectZone::findOwningSelectZone(cardItem)) {
|
||||||
|
zone->restoreClipAfterHover(cardItem);
|
||||||
|
}
|
||||||
|
if (liftOwner != cardItem) {
|
||||||
|
// A newer hover already owns the lift (or nothing does): leave it alone.
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
restoreHoverLift();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameScene::restoreHoverLift()
|
||||||
|
{
|
||||||
|
for (const auto &[lifted, zValue] : hoverLiftedAncestors) {
|
||||||
|
if (lifted) {
|
||||||
|
lifted->setZValue(zValue);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hoverLiftedAncestors.clear();
|
||||||
|
liftOwner.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---------- Card Hover Popup ----------
|
||||||
|
|
||||||
|
void GameScene::showCardHoverPopup(CardItem *card)
|
||||||
|
{
|
||||||
|
if (!card || !card->getOwner()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
PlayerGraphicsItem *playerView = playerViews.value(card->getOwner()->getPlayerInfo()->getId());
|
||||||
|
if (!playerView) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
hoverActivateTimer->stop();
|
||||||
|
hoverPanelTimer->stop();
|
||||||
|
hoverGracePending = false;
|
||||||
|
hoverGraceTimer->stop();
|
||||||
|
hoverPopupTarget = card;
|
||||||
|
hoverPanelTarget = nullptr;
|
||||||
|
confirmArmedCard.clear();
|
||||||
|
|
||||||
|
if (!cardHoverWidget) {
|
||||||
|
auto *viewport = views().isEmpty() ? nullptr : views().first()->viewport();
|
||||||
|
cardHoverWidget = new CardHoverWidget(viewport);
|
||||||
|
connect(cardHoverWidget, &CardHoverWidget::hoverEntered, this, &GameScene::onHoverWidgetEntered);
|
||||||
|
connect(cardHoverWidget, &CardHoverWidget::hoverLeft, this, &GameScene::onHoverWidgetLeft);
|
||||||
|
|
||||||
|
// Keep the popup anchored to its card while the view scrolls, zooms, or resizes
|
||||||
|
if (!views().isEmpty()) {
|
||||||
|
auto *view = views().first();
|
||||||
|
connect(view->horizontalScrollBar(), &QScrollBar::valueChanged, this, &GameScene::positionCardHoverPopup);
|
||||||
|
connect(view->verticalScrollBar(), &QScrollBar::valueChanged, this, &GameScene::positionCardHoverPopup);
|
||||||
|
if (viewport) {
|
||||||
|
viewport->installEventFilter(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Crossfade: the opaque panel picture sits exactly over the zoomed card, so fade
|
||||||
|
// the board card out underneath it (masks any residual size/corner mismatch) and
|
||||||
|
// fade any previously shown card back in.
|
||||||
|
CardItem *oldCard = cardHoverWidget->hasCard() ? cardHoverWidget->getCardItem() : nullptr;
|
||||||
|
|
||||||
|
cardHoverWidget->setCard(card, playerView);
|
||||||
|
positionCardHoverPopup();
|
||||||
|
cardHoverWidget->showAnimated();
|
||||||
|
|
||||||
|
if (oldCard && oldCard != card) {
|
||||||
|
oldCard->fadeCardIn();
|
||||||
|
}
|
||||||
|
card->fadeCardOut();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameScene::hideCardHoverPopup()
|
||||||
|
{
|
||||||
|
// Hiding is terminal: drop all pending hover timing with it, or a dwell/panel
|
||||||
|
// timer that resolves afterwards pops the panel back up (e.g. over the context
|
||||||
|
// menu that hideCardHoverPopup was just called for).
|
||||||
|
hoverActivateTimer->stop();
|
||||||
|
hoverPanelTimer->stop();
|
||||||
|
hoverGracePending = false;
|
||||||
|
hoverGraceTimer->stop();
|
||||||
|
if (cardHoverWidget) {
|
||||||
|
// The panel is gone: whatever card it covered must come back.
|
||||||
|
if (CardItem *coveredCard = cardHoverWidget->getCardItem()) {
|
||||||
|
coveredCard->fadeCardIn();
|
||||||
|
}
|
||||||
|
if (cardHoverWidget->isVisible()) {
|
||||||
|
cardHoverWidget->hideAnimated();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
hoverPopupTarget = nullptr;
|
||||||
|
hoverPanelTarget = nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameScene::positionCardHoverPopup()
|
||||||
|
{
|
||||||
|
if (!cardHoverWidget || !cardHoverWidget->hasCard()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
auto *viewport = views().isEmpty() ? nullptr : views().first()->viewport();
|
||||||
|
if (!viewport) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
QRectF sceneRect = cardHoverWidget->getCardItem()->sceneBoundingRect();
|
||||||
|
QTransform vpTransform = getViewportTransform();
|
||||||
|
cardHoverWidget->positionNextTo(sceneRect, vpTransform, viewport);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameScene::onHoverActivateTimeout()
|
||||||
|
{
|
||||||
|
// Dwell resolved: the hover drives whichever phases listen to it. The cursor must
|
||||||
|
// still be over the pending card (rect test, not a fresh pick, so pick flakiness
|
||||||
|
// cannot veto it); otherwise this was a stale timer.
|
||||||
|
CardItem *target = hoverPopupTarget;
|
||||||
|
hoverPopupTarget = nullptr;
|
||||||
|
if (!target || views().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto *view = views().first();
|
||||||
|
const QPointF cursorScene = view->mapToScene(view->viewport()->mapFromGlobal(QCursor::pos()));
|
||||||
|
if (!target->sceneBoundingRect().contains(cursorScene)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto &display = SettingsCache::instance().cardsDisplay();
|
||||||
|
const bool zoomPhase = display.getHoverZoomTrigger() == CardsDisplaySettings::HoverPanelTriggerHover;
|
||||||
|
const bool panelPhase = display.getHoverPanelTrigger() == CardsDisplaySettings::HoverPanelTriggerHover;
|
||||||
|
const bool effectiveDouble =
|
||||||
|
display.getHoverPanelDoubleConfirm() && display.getHoverZoomTrigger() == display.getHoverPanelTrigger();
|
||||||
|
if (hoveredCard && hoveredCard != target) {
|
||||||
|
endCardHover(hoveredCard);
|
||||||
|
}
|
||||||
|
if (zoomPhase) {
|
||||||
|
if (hoveredCard != target) {
|
||||||
|
beginCardHover(target);
|
||||||
|
hoveredCard = target;
|
||||||
|
}
|
||||||
|
if (effectiveDouble) {
|
||||||
|
confirmArmedCard = target;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (panelPhase && !effectiveDouble) {
|
||||||
|
hoverPanelTarget = target;
|
||||||
|
hoverPanelTimer->start(display.getHoverPanelDelayMs());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameScene::onHoverPanelTimeout()
|
||||||
|
{
|
||||||
|
// Panel delay resolved: show, but only if the target card is still the one under
|
||||||
|
// the cursor (same cheap rect check — no dependence on pick stability).
|
||||||
|
CardItem *target = hoverPanelTarget;
|
||||||
|
hoverPanelTarget = nullptr;
|
||||||
|
if (!target || views().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto *view = views().first();
|
||||||
|
const QPointF cursorScene = view->mapToScene(view->viewport()->mapFromGlobal(QCursor::pos()));
|
||||||
|
if (!target->sceneBoundingRect().contains(cursorScene)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
ensureZoomed(target);
|
||||||
|
showCardHoverPopup(target);
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameScene::onHoverGraceTimeout()
|
||||||
|
{
|
||||||
|
hoverGracePending = false;
|
||||||
|
if (cardHoverWidget && cardHoverWidget->isVisible() && cardHoverWidget->isMouseOverWidget()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
hideCardHoverPopup();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameScene::summonHoverPanel()
|
||||||
|
{
|
||||||
|
// Summon for whatever is under the cursor right now; hovering is not required.
|
||||||
|
if (views().isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
auto *view = views().first();
|
||||||
|
const QPointF cursorScene = view->mapToScene(view->viewport()->mapFromGlobal(QCursor::pos()));
|
||||||
|
if (CardItem *card = pickCardAt(cursorScene)) {
|
||||||
|
auto &display = SettingsCache::instance().cardsDisplay();
|
||||||
|
fireTriggers(card, display.getHoverZoomTrigger() == CardsDisplaySettings::HoverPanelTriggerShortcut,
|
||||||
|
display.getHoverPanelTrigger() == CardsDisplaySettings::HoverPanelTriggerShortcut);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameScene::ensureZoomed(CardItem *card)
|
||||||
|
{
|
||||||
|
if (hoveredCard != card) {
|
||||||
|
if (hoveredCard) {
|
||||||
|
endCardHover(hoveredCard);
|
||||||
|
}
|
||||||
|
beginCardHover(card);
|
||||||
|
hoveredCard = card;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameScene::fireTriggers(CardItem *card, bool zoomFired, bool panelFired)
|
||||||
|
{
|
||||||
|
if (!card || !card->getOwner()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (cardHoverWidget && cardHoverWidget->isVisible() && cardHoverWidget->getCardItem() == card) {
|
||||||
|
cardHoverWidget->refreshDisplay();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Explicit triggers preempt any pending hover timing.
|
||||||
|
hoverPopupTarget = nullptr;
|
||||||
|
hoverPanelTarget = nullptr;
|
||||||
|
hoverActivateTimer->stop();
|
||||||
|
hoverPanelTimer->stop();
|
||||||
|
|
||||||
|
auto &display = SettingsCache::instance().cardsDisplay();
|
||||||
|
const bool effectiveDouble =
|
||||||
|
display.getHoverPanelDoubleConfirm() && display.getHoverZoomTrigger() == display.getHoverPanelTrigger();
|
||||||
|
|
||||||
|
if (zoomFired && panelFired) {
|
||||||
|
// Both phases on one action: one-step zooms and shows at once (the crossfade
|
||||||
|
// covers it); double-confirm arms on first fire and shows on repeat.
|
||||||
|
if (effectiveDouble) {
|
||||||
|
if (confirmArmedCard == card) {
|
||||||
|
confirmArmedCard.clear();
|
||||||
|
ensureZoomed(card);
|
||||||
|
showCardHoverPopup(card);
|
||||||
|
} else {
|
||||||
|
confirmArmedCard = card;
|
||||||
|
ensureZoomed(card);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
ensureZoomed(card);
|
||||||
|
showCardHoverPopup(card);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (zoomFired) {
|
||||||
|
ensureZoomed(card);
|
||||||
|
}
|
||||||
|
if (panelFired) {
|
||||||
|
ensureZoomed(card);
|
||||||
|
showCardHoverPopup(card);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameScene::onHoverWidgetEntered()
|
||||||
|
{
|
||||||
|
hoverGracePending = false;
|
||||||
|
hoverGraceTimer->stop();
|
||||||
|
}
|
||||||
|
|
||||||
|
void GameScene::onHoverWidgetLeft()
|
||||||
|
{
|
||||||
|
if (!hoveredCard && cardHoverWidget && cardHoverWidget->isVisible() && !hoverGracePending) {
|
||||||
|
hoverGracePending = true;
|
||||||
|
hoverGraceTimer->start();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool GameScene::eventFilter(QObject *watched, QEvent *event)
|
||||||
|
{
|
||||||
|
if (event->type() == QEvent::Resize) {
|
||||||
|
positionCardHoverPopup();
|
||||||
|
}
|
||||||
|
return QGraphicsScene::eventFilter(watched, event);
|
||||||
|
}
|
||||||
|
|
||||||
CardZone *GameScene::findTopmostZone(const QList<QGraphicsItem *> &items)
|
CardZone *GameScene::findTopmostZone(const QList<QGraphicsItem *> &items)
|
||||||
{
|
{
|
||||||
for (QGraphicsItem *item : items) {
|
for (QGraphicsItem *item : items) {
|
||||||
|
|
@ -940,7 +1389,50 @@ bool GameScene::event(QEvent *event)
|
||||||
{
|
{
|
||||||
if (event->type() == QEvent::GraphicsSceneMouseMove) {
|
if (event->type() == QEvent::GraphicsSceneMouseMove) {
|
||||||
updateHover(static_cast<QGraphicsSceneMouseEvent *>(event)->scenePos());
|
updateHover(static_cast<QGraphicsSceneMouseEvent *>(event)->scenePos());
|
||||||
|
} else if (event->type() == QEvent::GraphicsSceneWheel) {
|
||||||
|
// Wheel trigger: wheeling over a card fires whichever phases listen to it
|
||||||
|
// instead of scrolling. Fully consumed here: forwarding to the base dispatcher
|
||||||
|
// as well would double-deliver it to items under the cursor (e.g. zone views,
|
||||||
|
// which scroll on wheel). Scrolling up instead reverts an active zoom/panel —
|
||||||
|
// backing out of the gesture undoes it; with nothing to revert the event falls
|
||||||
|
// through to normal scrolling.
|
||||||
|
auto &display = SettingsCache::instance().cardsDisplay();
|
||||||
|
const bool zoomFired = display.getHoverZoomTrigger() == CardsDisplaySettings::HoverPanelTriggerWheel;
|
||||||
|
const bool panelFired = display.getHoverPanelTrigger() == CardsDisplaySettings::HoverPanelTriggerWheel;
|
||||||
|
if (zoomFired || panelFired) {
|
||||||
|
auto *wheelEvent = static_cast<QGraphicsSceneWheelEvent *>(event);
|
||||||
|
// QGraphicsSceneWheelEvent carries delta()/pixelDelta(), not angleDelta().
|
||||||
|
const int wheelUp = !wheelEvent->pixelDelta().isNull()
|
||||||
|
? wheelEvent->pixelDelta().y()
|
||||||
|
: (wheelEvent->orientation() == Qt::Vertical ? wheelEvent->delta() : 0);
|
||||||
|
if (wheelUp > 0) {
|
||||||
|
if ((cardHoverWidget && cardHoverWidget->isVisible()) || hoveredCard) {
|
||||||
|
hoverPopupTarget = nullptr;
|
||||||
|
hoverPanelTarget = nullptr;
|
||||||
|
confirmArmedCard.clear();
|
||||||
|
hideCardHoverPopup();
|
||||||
|
if (hoveredCard) {
|
||||||
|
endCardHover(hoveredCard);
|
||||||
|
hoveredCard = nullptr;
|
||||||
|
}
|
||||||
|
event->accept();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
} else if (CardItem *card = pickCardAt(wheelEvent->scenePos())) {
|
||||||
|
fireTriggers(card, zoomFired, panelFired);
|
||||||
|
event->accept();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
} else if (event->type() == QEvent::Leave) {
|
} else if (event->type() == QEvent::Leave) {
|
||||||
|
// The hover panel opens directly over the zoomed card under the cursor, which the
|
||||||
|
// scene observes as a Leave. The cursor never actually left: if it is over the open
|
||||||
|
// panel, keep the hover (dropping it here shrinks the card from under the panel and
|
||||||
|
// starves the popup logic, so the menu never shows). A genuine leave of the window
|
||||||
|
// misses the panel and still ends the hover as before.
|
||||||
|
if (cardHoverWidget && cardHoverWidget->isVisible() && cardHoverWidget->isMouseOverWidget()) {
|
||||||
|
return QGraphicsScene::event(event);
|
||||||
|
}
|
||||||
updateHoveredCard(nullptr);
|
updateHoveredCard(nullptr);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -11,6 +11,7 @@
|
||||||
#include <QLoggingCategory>
|
#include <QLoggingCategory>
|
||||||
#include <QMap>
|
#include <QMap>
|
||||||
#include <QMetaObject>
|
#include <QMetaObject>
|
||||||
|
#include <QPair>
|
||||||
#include <QPointF>
|
#include <QPointF>
|
||||||
#include <QPointer>
|
#include <QPointer>
|
||||||
#include <QSize>
|
#include <QSize>
|
||||||
|
|
@ -30,6 +31,7 @@
|
||||||
class CardZoneLogic;
|
class CardZoneLogic;
|
||||||
class IAnimatedItem;
|
class IAnimatedItem;
|
||||||
class QGraphicsItem;
|
class QGraphicsItem;
|
||||||
|
class QGraphicsObject;
|
||||||
class QPoint;
|
class QPoint;
|
||||||
struct ArrowData;
|
struct ArrowData;
|
||||||
template <class T> class QSharedPointer;
|
template <class T> class QSharedPointer;
|
||||||
|
|
@ -48,6 +50,8 @@ class ServerInfo_Card;
|
||||||
class PhasesToolbar;
|
class PhasesToolbar;
|
||||||
class QBasicTimer;
|
class QBasicTimer;
|
||||||
class QObject;
|
class QObject;
|
||||||
|
class QTimer;
|
||||||
|
class CardHoverWidget;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @class GameScene
|
* @class GameScene
|
||||||
|
|
@ -75,14 +79,36 @@ private:
|
||||||
QSize viewSize; ///< Current view size
|
QSize viewSize; ///< Current view size
|
||||||
QPointer<CardItem> hoveredCard; ///< Currently hovered card
|
QPointer<CardItem> hoveredCard; ///< Currently hovered card
|
||||||
QPointer<CardItem> activeCard; ///< Card the currently open context menu targets
|
QPointer<CardItem> activeCard; ///< Card the currently open context menu targets
|
||||||
QBasicTimer *animationTimer; ///< Timer for scene animations
|
/// Card armed for double-confirm panel summon (first trigger zooms, repeat shows).
|
||||||
QHash<QObject *, IAnimatedItem *> animatedItems; ///< Items currently animating
|
QPointer<CardItem> confirmArmedCard;
|
||||||
|
/// Ancestor chain of the hovered card with pre-hover Z-values. Lifted while hovering
|
||||||
|
/// because Qt orders subtrees level by level: the card z alone cannot beat a sibling
|
||||||
|
/// zone, and the top ancestor alone cannot beat same-area sibling zones either — every
|
||||||
|
/// level from the zone up must win its own comparison. Guarded pointers: if the card
|
||||||
|
/// is destroyed mid-hover (or an ancestor goes away first), the guards null out and
|
||||||
|
/// the next restore skips them instead of touching freed memory. An orphaned lift is
|
||||||
|
/// invisible — areas and zones tile without overlapping and all UI layers still win —
|
||||||
|
/// and any outstanding lift is restored by the next hover transition.
|
||||||
|
QList<QPair<QPointer<QGraphicsObject>, qreal>> hoverLiftedAncestors;
|
||||||
|
/// Card whose ancestor chain is currently lifted (cleared together with the chain).
|
||||||
|
QPointer<CardItem> liftOwner;
|
||||||
|
QBasicTimer *animationTimer; ///< Timer for scene animations
|
||||||
|
QHash<QObject *, IAnimatedItem *> animatedItems; ///< Items currently animating
|
||||||
QHash<QObject *, QMetaObject::Connection>
|
QHash<QObject *, QMetaObject::Connection>
|
||||||
animationItemConnections; ///< destroyed->removeAnimatedItem handles per animated item
|
animationItemConnections; ///< destroyed->removeAnimatedItem handles per animated item
|
||||||
int playerRotation; ///< Rotation offset for player layout
|
int playerRotation; ///< Rotation offset for player layout
|
||||||
bool rearranging = false; ///< Guard against re-entrant rearrange
|
bool rearranging = false; ///< Guard against re-entrant rearrange
|
||||||
bool needsReArrange = false; ///< Pending rearrange requested during a pass
|
bool needsReArrange = false; ///< Pending rearrange requested during a pass
|
||||||
|
|
||||||
|
// Card hover popup
|
||||||
|
QPointer<CardHoverWidget> cardHoverWidget; ///< The hover popup widget
|
||||||
|
QTimer *hoverActivateTimer; ///< Dwell delay before the hover zoom kicks in
|
||||||
|
QTimer *hoverPanelTimer; ///< Delay after the zoom before the tab panel shows
|
||||||
|
QTimer *hoverGraceTimer; ///< Grace period before hiding popup (200ms)
|
||||||
|
QPointer<CardItem> hoverPopupTarget; ///< Card the popup is intended for
|
||||||
|
QPointer<CardItem> hoverPanelTarget; ///< Card the panel timer will show
|
||||||
|
bool hoverGracePending = false; ///< Grace timer armed (mouse left a card or the popup)
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Updates which card is currently hovered based on scene coordinates.
|
* @brief Updates which card is currently hovered based on scene coordinates.
|
||||||
* @param scenePos Scene position of the cursor.
|
* @param scenePos Scene position of the cursor.
|
||||||
|
|
@ -96,6 +122,15 @@ private:
|
||||||
void beginCardHover(CardItem *card);
|
void beginCardHover(CardItem *card);
|
||||||
/** @brief Deactivates hover state and restores the card to its clip container. */
|
/** @brief Deactivates hover state and restores the card to its clip container. */
|
||||||
void endCardHover(CardItem *card);
|
void endCardHover(CardItem *card);
|
||||||
|
/** @brief Restores every lifted ancestor to its pre-hover Z-value and drops the chain. */
|
||||||
|
void restoreHoverLift();
|
||||||
|
/** @brief Settles a finished shrink-out: clip container and ancestor lift restore. */
|
||||||
|
void onHoverShrinkFinished(AbstractCardItem *card);
|
||||||
|
|
||||||
|
// Card hover popup methods
|
||||||
|
void showCardHoverPopup(CardItem *card);
|
||||||
|
void hideCardHoverPopup();
|
||||||
|
void positionCardHoverPopup();
|
||||||
|
|
||||||
public:
|
public:
|
||||||
/**
|
/**
|
||||||
|
|
@ -227,6 +262,21 @@ public:
|
||||||
/** @brief Updates hovered card highlighting. */
|
/** @brief Updates hovered card highlighting. */
|
||||||
void updateHoveredCard(CardItem *newCard);
|
void updateHoveredCard(CardItem *newCard);
|
||||||
|
|
||||||
|
/** @brief Topmost card at a scene position, without side effects. */
|
||||||
|
CardItem *pickCardAt(const QPointF &scenePos);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @brief Fires the zoom and/or panel trigger for a card (one explicit user action).
|
||||||
|
* When both fire on one action, double-confirm arms on first fire and shows on repeat.
|
||||||
|
*/
|
||||||
|
void fireTriggers(CardItem *card, bool zoomFired, bool panelFired);
|
||||||
|
|
||||||
|
/** @brief Starts the hover zoom for a card without touching panel state. */
|
||||||
|
void ensureZoomed(CardItem *card);
|
||||||
|
|
||||||
|
/** @brief The currently hovered card, if any (used by zones to dodge it while painting). */
|
||||||
|
CardItem *getHoveredCard() const;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @brief Registers an item for animation updates with the shared scene timer.
|
* @brief Registers an item for animation updates with the shared scene timer.
|
||||||
*
|
*
|
||||||
|
|
@ -293,10 +343,22 @@ public slots:
|
||||||
*/
|
*/
|
||||||
void onCardViewRefreshRequested(CardState *card, bool zoneChanged, PlayerLogic *newOwner);
|
void onCardViewRefreshRequested(CardState *card, bool zoneChanged, PlayerLogic *newOwner);
|
||||||
|
|
||||||
|
// Card hover popup slots
|
||||||
|
void onHoverActivateTimeout();
|
||||||
|
void onHoverPanelTimeout();
|
||||||
|
void onHoverGraceTimeout();
|
||||||
|
void onHoverWidgetEntered();
|
||||||
|
void onHoverWidgetLeft();
|
||||||
|
/** @brief Summons the hover panel for the currently hovered card, if any. */
|
||||||
|
void summonHoverPanel();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
/** @brief Handles hover updates. */
|
/** @brief Handles hover updates. */
|
||||||
bool event(QEvent *event) override;
|
bool event(QEvent *event) override;
|
||||||
|
|
||||||
|
/** @brief Repositions the hover popup when the viewport resizes. */
|
||||||
|
bool eventFilter(QObject *watched, QEvent *event) override;
|
||||||
|
|
||||||
/** @brief Handles animation timer updates. */
|
/** @brief Handles animation timer updates. */
|
||||||
void timerEvent(QTimerEvent *event) override;
|
void timerEvent(QTimerEvent *event) override;
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -87,6 +87,10 @@ GameView::GameView(GameScene *scene, QWidget *parent) : QGraphicsView(scene, par
|
||||||
|
|
||||||
connect(aCloseMostRecentZoneView, &QAction::triggered, scene, &GameScene::closeMostRecentZoneView);
|
connect(aCloseMostRecentZoneView, &QAction::triggered, scene, &GameScene::closeMostRecentZoneView);
|
||||||
addAction(aCloseMostRecentZoneView);
|
addAction(aCloseMostRecentZoneView);
|
||||||
|
|
||||||
|
aShowHoverPanel = new QAction(this);
|
||||||
|
connect(aShowHoverPanel, &QAction::triggered, scene, &GameScene::summonHoverPanel);
|
||||||
|
addAction(aShowHoverPanel);
|
||||||
connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this,
|
connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this,
|
||||||
&GameView::refreshShortcuts);
|
&GameView::refreshShortcuts);
|
||||||
refreshShortcuts();
|
refreshShortcuts();
|
||||||
|
|
@ -246,6 +250,7 @@ void GameView::refreshShortcuts()
|
||||||
{
|
{
|
||||||
aCloseMostRecentZoneView->setShortcuts(
|
aCloseMostRecentZoneView->setShortcuts(
|
||||||
SettingsCache::instance().shortcuts().getShortcut("Player/aCloseMostRecentZoneView"));
|
SettingsCache::instance().shortcuts().getShortcut("Player/aCloseMostRecentZoneView"));
|
||||||
|
aShowHoverPanel->setShortcuts(SettingsCache::instance().shortcuts().getShortcut("Player/aShowHoverPanel"));
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameView::clearTallyLabels()
|
void GameView::clearTallyLabels()
|
||||||
|
|
|
||||||
|
|
@ -29,6 +29,7 @@ class GameView : public QGraphicsView
|
||||||
Q_OBJECT
|
Q_OBJECT
|
||||||
private:
|
private:
|
||||||
QAction *aCloseMostRecentZoneView;
|
QAction *aCloseMostRecentZoneView;
|
||||||
|
QAction *aShowHoverPanel;
|
||||||
QRubberBand *rubberBand;
|
QRubberBand *rubberBand;
|
||||||
QLabel *dragCountLabel;
|
QLabel *dragCountLabel;
|
||||||
QLabel *totalCountLabel;
|
QLabel *totalCountLabel;
|
||||||
|
|
|
||||||
|
|
@ -118,6 +118,15 @@ bool TableZone::isInverted() const
|
||||||
|
|
||||||
void TableZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
void TableZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/)
|
||||||
{
|
{
|
||||||
|
// 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));
|
||||||
|
}
|
||||||
|
|
||||||
if (playmatActive) {
|
if (playmatActive) {
|
||||||
// Subtle overlay to distinguish table zone from stack zone
|
// Subtle overlay to distinguish table zone from stack zone
|
||||||
painter->fillRect(boundingRect(), QColor(0, 0, 0, 60));
|
painter->fillRect(boundingRect(), QColor(0, 0, 0, 60));
|
||||||
|
|
@ -142,6 +151,32 @@ void TableZone::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*opti
|
||||||
}
|
}
|
||||||
|
|
||||||
paintLandDivider(painter);
|
paintLandDivider(painter);
|
||||||
|
|
||||||
|
if (hasHole) {
|
||||||
|
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.
|
||||||
|
*/
|
||||||
|
QRect TableZone::hoveredCardHole() const
|
||||||
|
{
|
||||||
|
auto *scene = qobject_cast<GameScene *>(this->scene());
|
||||||
|
if (!scene) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
CardItem *hovered = scene->getHoveredCard();
|
||||||
|
if (!hovered) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
QRect hole = mapRectFromScene(hovered->sceneBoundingRect()).toRect();
|
||||||
|
if (!hole.intersects(boundingRect().toRect())) {
|
||||||
|
return {};
|
||||||
|
}
|
||||||
|
return hole & boundingRect().toRect();
|
||||||
}
|
}
|
||||||
|
|
||||||
void TableZone::onPlaymatChanged(bool active)
|
void TableZone::onPlaymatChanged(bool active)
|
||||||
|
|
|
||||||
|
|
@ -221,6 +221,7 @@ private:
|
||||||
|
|
||||||
void paintZoneOutline(QPainter *painter);
|
void paintZoneOutline(QPainter *painter);
|
||||||
void paintLandDivider(QPainter *painter);
|
void paintLandDivider(QPainter *painter);
|
||||||
|
[[nodiscard]] QRect hoveredCardHole() const;
|
||||||
|
|
||||||
/*
|
/*
|
||||||
Calculates card stack widths so mapping functions work properly
|
Calculates card stack widths so mapping functions work properly
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue