mirror of
https://github.com/Cockatrice/Cockatrice.git
synced 2026-11-10 20:13:24 -08:00
[Interface] Address hover panel review findings
This commit is contained in:
parent
6b636baa60
commit
9fa6c06601
10 changed files with 137 additions and 29 deletions
|
|
@ -13,6 +13,7 @@
|
|||
#include "libcockatrice/card/printing/exact_card.h"
|
||||
#include "libcockatrice/utility/card_ref.h"
|
||||
|
||||
#include <QAbstractAnimation>
|
||||
#include <QApplication>
|
||||
#include <QBrush>
|
||||
#include <QChar>
|
||||
|
|
@ -47,13 +48,16 @@ class QSizeF;
|
|||
// Width the hovered card smooth-zooms to: exactly the hover panel picture size, so
|
||||
// all four corners coincide when the panel swaps in over it.
|
||||
static constexpr qreal HOVER_ZOOM_WIDTH = 270.0;
|
||||
// The pre-panel hover enlargement. The smooth zoom is opt-in, so this stays the hover
|
||||
// behavior whenever no zoom trigger is configured (see setHoverHighlighted).
|
||||
static constexpr qreal LEGACY_HOVER_SCALE = 1.1;
|
||||
static constexpr int HOVER_ZOOM_IN_MS = 220;
|
||||
static constexpr int HOVER_ZOOM_OUT_MS = 140;
|
||||
static constexpr int FADE_DURATION_MS = 140;
|
||||
|
||||
AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, const CardRef &cardRef, PlayerLogic *_owner, int _id)
|
||||
: ArrowTarget(_owner, parent), state(new CardState(this, nullptr, cardRef, _id)), tapAngle(0),
|
||||
bgColor(Qt::transparent), isHovered(false), realZValue(0)
|
||||
bgColor(Qt::transparent), isHovered(false), isHoverHighlighted(false), realZValue(0)
|
||||
{
|
||||
setCursor(Qt::OpenHandCursor);
|
||||
setFlag(ItemIsSelectable);
|
||||
|
|
@ -79,7 +83,9 @@ AbstractCardItem::AbstractCardItem(QGraphicsItem *parent, const CardRef &cardRef
|
|||
// so siblings and the clip container cannot cut through the shrinking card.
|
||||
connect(hoverZoom, &QPropertyAnimation::finished, this, [this] {
|
||||
if (!isHovered) {
|
||||
setZValue(realZValue);
|
||||
// The cursor may still rest on the card (e.g. wheel-up reverts the zoom),
|
||||
// in which case the highlight keeps it raised.
|
||||
setZValue(isHoverHighlighted ? ZValues::HOVERED_CARD : realZValue);
|
||||
// Final repaint at the settled footprint: with no scale delta the animation
|
||||
// emits no per-frame updates, and zones would keep a stale clip hole behind.
|
||||
if (scene()) {
|
||||
|
|
@ -164,7 +170,7 @@ void AbstractCardItem::setRealZValue(qreal _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) {
|
||||
if (!isHovered && !isHoverHighlighted) {
|
||||
setZValue(_zValue);
|
||||
}
|
||||
}
|
||||
|
|
@ -262,9 +268,9 @@ void AbstractCardItem::paint(QPainter *painter, const QStyleOptionGraphicsItem *
|
|||
|
||||
painter->setRenderHint(QPainter::Antialiasing, false);
|
||||
|
||||
if (isSelected() || isHovered) {
|
||||
if (isSelected() || isHovered || isHoverHighlighted) {
|
||||
QPen pen;
|
||||
if (isHovered) {
|
||||
if (isHovered || isHoverHighlighted) {
|
||||
pen.setColor(Qt::yellow);
|
||||
}
|
||||
if (isSelected()) {
|
||||
|
|
@ -334,6 +340,47 @@ void AbstractCardItem::setHovered(bool _hovered)
|
|||
update();
|
||||
}
|
||||
|
||||
void AbstractCardItem::setHoverHighlighted(bool highlighted)
|
||||
{
|
||||
if (isHoverHighlighted == highlighted) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (highlighted) {
|
||||
// The card-info widget listens for this: hovering still updates it right away.
|
||||
processHoverEvent();
|
||||
}
|
||||
|
||||
isHoverHighlighted = highlighted;
|
||||
if (highlighted) {
|
||||
setZValue(ZValues::HOVERED_CARD);
|
||||
}
|
||||
|
||||
// Fallback to the historical hover scale: the smooth hover zoom is opt-in and its
|
||||
// trigger defaults to Off, so when hovering drives neither the zoom nor the panel
|
||||
// (zoom trigger Off, panel not on hover) contact alone must still enlarge the card
|
||||
// to the old 1.1x while scaleCards is on. Otherwise every existing user silently
|
||||
// loses the hover enlargement on upgrade.
|
||||
auto &display = SettingsCache::instance().cardsDisplay();
|
||||
const bool legacyHoverScale = display.getScaleCards() &&
|
||||
display.getHoverZoomTrigger() == CardsDisplaySettings::HoverPanelTriggerOff &&
|
||||
display.getHoverPanelTrigger() != CardsDisplaySettings::HoverPanelTriggerHover;
|
||||
if (legacyHoverScale) {
|
||||
// Centered like the smooth zoom, so growing and shrinking never drift.
|
||||
setTransformOriginPoint(CardDimensions::WIDTH_HALF_F, CardDimensions::HEIGHT_HALF_F);
|
||||
hoverZoom->stop();
|
||||
hoverZoom->setStartValue(scale());
|
||||
hoverZoom->setEndValue(highlighted ? LEGACY_HOVER_SCALE : 1.0);
|
||||
hoverZoom->setDuration(highlighted ? HOVER_ZOOM_IN_MS : HOVER_ZOOM_OUT_MS);
|
||||
hoverZoom->start();
|
||||
} else if (!highlighted && !isHovered && hoverZoom->state() != QAbstractAnimation::Running) {
|
||||
// A zoomed card keeps its raised z until the shrink-out finishes (its finished
|
||||
// handler restores it); a highlight-only card has nothing to unwind.
|
||||
setZValue(realZValue);
|
||||
}
|
||||
update();
|
||||
}
|
||||
|
||||
void AbstractCardItem::setColor(const QString &_color)
|
||||
{
|
||||
state->setColor(_color);
|
||||
|
|
|
|||
|
|
@ -37,6 +37,7 @@ protected:
|
|||
|
||||
private:
|
||||
bool isHovered;
|
||||
bool isHoverHighlighted;
|
||||
qreal realZValue;
|
||||
bool pendingSelectionCollapse;
|
||||
int selectionCollapseGeneration = 0; ///< Guards the deferred collapse timer against stale firings
|
||||
|
|
@ -134,6 +135,16 @@ public:
|
|||
}
|
||||
void setRealZValue(qreal _zValue);
|
||||
void setHovered(bool _hovered);
|
||||
/**
|
||||
* @brief Shows the hover outline and stacking lift, plus the historical 1.1x hover
|
||||
* scale whenever no zoom trigger is configured.
|
||||
*
|
||||
* The board zoom is trigger-gated; the hover feedback (card info, outline, raised
|
||||
* stacking) still follows the cursor immediately on contact. With the zoom trigger
|
||||
* off and the panel not on hover, the old 1.1x enlargement (gated by scaleCards)
|
||||
* stays in effect so upgrading does not silently drop it.
|
||||
*/
|
||||
void setHoverHighlighted(bool highlighted);
|
||||
qreal getCardScale() const;
|
||||
void setCardScale(qreal scale);
|
||||
qreal getCardOpacity() const;
|
||||
|
|
|
|||
|
|
@ -268,15 +268,17 @@ void GameScene::onCardRightClicked(AbstractCardItem *abstractCard, QPoint screen
|
|||
}
|
||||
|
||||
// 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).
|
||||
// whichever phases listen to it. When the panel is the trigger it carries every
|
||||
// action itself, so the context menu stays suppressed; when right-click only zooms,
|
||||
// the menu still opens, since nothing else would summon it.
|
||||
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;
|
||||
if (panelFired) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Hide the hover popup when the context menu opens
|
||||
|
|
@ -843,6 +845,17 @@ void GameScene::updateHoveredCard(CardItem *newCard)
|
|||
hoveredCard = nullptr;
|
||||
}
|
||||
|
||||
// The hover outline and the card info follow the cursor on contact: only the board
|
||||
// zoom and the panel wait for their trigger. A highlight-only card has no zoom to
|
||||
// unwind, so it is dropped directly instead of through endCardHover().
|
||||
if (hoverHighlightCard && hoverHighlightCard != newCard) {
|
||||
hoverHighlightCard->setHoverHighlighted(false);
|
||||
}
|
||||
hoverHighlightCard = newCard;
|
||||
if (newCard) {
|
||||
newCard->setHoverHighlighted(true);
|
||||
}
|
||||
|
||||
auto &display = SettingsCache::instance().cardsDisplay();
|
||||
const int zoomTrigger = display.getHoverZoomTrigger();
|
||||
const int panelTrigger = display.getHoverPanelTrigger();
|
||||
|
|
|
|||
|
|
@ -78,8 +78,11 @@ private:
|
|||
ArrowRegistry arrowRegistry; ///< ID registry for arrow graphics items
|
||||
QList<ZoneViewWidget *> zoneViews; ///< Active zone view widgets
|
||||
QSize viewSize; ///< Current view size
|
||||
QPointer<CardItem> hoveredCard; ///< Currently hovered card
|
||||
QPointer<CardItem> activeCard; ///< Card the currently open context menu targets
|
||||
QPointer<CardItem> hoveredCard; ///< Currently hovered card (zoomed)
|
||||
/// Card the cursor is resting on. It carries the hover outline and the card-info
|
||||
/// update immediately, independently of the zoom/panel triggers.
|
||||
QPointer<CardItem> hoverHighlightCard;
|
||||
QPointer<CardItem> activeCard; ///< Card the currently open context menu targets
|
||||
/// Card armed for double-confirm panel summon (first trigger zooms, repeat shows).
|
||||
QPointer<CardItem> confirmArmedCard;
|
||||
/// Ancestor chain of the hovered card with pre-hover Z-values. Lifted while hovering
|
||||
|
|
|
|||
|
|
@ -284,9 +284,8 @@ void CardHoverWidget::createNameBarOverlay()
|
|||
if (!nameEdit->isVisible()) {
|
||||
return;
|
||||
}
|
||||
if (currentCard && currentCard->getOwner()) {
|
||||
auto *actions = currentCard->getOwner()->getPlayerActions();
|
||||
if (actions) {
|
||||
if (canModifyCurrentCard()) {
|
||||
if (auto *actions = currentCard->getOwner()->getPlayerActions()) {
|
||||
QList<CardState *> cardList{currentCard->getState()};
|
||||
actions->actSetAnnotation(cardList, nameEdit->text());
|
||||
}
|
||||
|
|
@ -771,6 +770,23 @@ void CardHoverWidget::onCardDestroyed()
|
|||
|
||||
// ---------- Grouped action builders (mirror CardMenu per zone) ----------
|
||||
|
||||
bool CardHoverWidget::canModifyCurrentCard() const
|
||||
{
|
||||
if (!currentCard || !currentCard->getOwner() || !currentPlayer) {
|
||||
return false;
|
||||
}
|
||||
if (!currentPlayer->getLogic()->getPlayerInfo()->getLocalOrJudge()) {
|
||||
return false;
|
||||
}
|
||||
// A revealed zone we cannot write to is read-only even for its owner.
|
||||
if (auto *view = qobject_cast<ZoneViewZoneLogic *>(currentCard->getZone())) {
|
||||
if (view->getRevealZone() && !view->getWriteableRevealZone()) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
void CardHoverWidget::rebuildActionsForZone()
|
||||
{
|
||||
clearLayout(mainPageLayout);
|
||||
|
|
@ -785,12 +801,7 @@ void CardHoverWidget::rebuildActionsForZone()
|
|||
auto *card = currentCard.data();
|
||||
QList<CardState *> cardList{card->getState()};
|
||||
|
||||
bool canModify = currentPlayer->getLogic()->getPlayerInfo()->getLocalOrJudge();
|
||||
if (auto *view = qobject_cast<ZoneViewZoneLogic *>(card->getZone())) {
|
||||
if (view->getRevealZone() && !view->getWriteableRevealZone()) {
|
||||
canModify = false;
|
||||
}
|
||||
}
|
||||
const bool canModify = canModifyCurrentCard();
|
||||
|
||||
QString zoneName = card->getZone() ? card->getZone()->getName() : QString();
|
||||
|
||||
|
|
@ -1175,6 +1186,11 @@ void CardHoverWidget::setPTExpanded(bool expanded)
|
|||
if (expanded && (!currentCard || currentCard->getPT().isEmpty())) {
|
||||
expanded = false;
|
||||
}
|
||||
// The buttons act on our own copy of the card by id, so on another player's card they
|
||||
// would edit our zone. Offer them only where the action tabs allow edits.
|
||||
if (expanded && !canModifyCurrentCard()) {
|
||||
expanded = false;
|
||||
}
|
||||
// Mouse movement drives this on every event; only apply a real state change.
|
||||
if (ptExpanded == expanded) {
|
||||
return;
|
||||
|
|
@ -1558,8 +1574,9 @@ bool CardHoverWidget::isClosing() const
|
|||
|
||||
bool CardHoverWidget::eventFilter(QObject *obj, QEvent *event)
|
||||
{
|
||||
// Handle double-click on name label to start editing annotation
|
||||
if (obj == nameLabel && event->type() == QEvent::MouseButtonDblClick) {
|
||||
// Handle double-click on name label to start editing annotation. Annotations are set
|
||||
// by card id on our own zone, so another player's card must not open the editor.
|
||||
if (obj == nameLabel && event->type() == QEvent::MouseButtonDblClick && canModifyCurrentCard()) {
|
||||
nameLabel->setVisible(false);
|
||||
annotationLabel->setVisible(false);
|
||||
nameEdit->setVisible(true);
|
||||
|
|
|
|||
|
|
@ -174,6 +174,8 @@ private:
|
|||
void scheduleRefresh();
|
||||
|
||||
void rebuildActionsForZone();
|
||||
/** @brief Whether the local player may edit the current card (own card, judge, writable zone). */
|
||||
bool canModifyCurrentCard() const;
|
||||
void buildMainTab(const QList<CardState *> &cardList, const QString &zoneName, bool canModify);
|
||||
void buildMoveTab(const QList<CardState *> &cardList, const QString &zoneName, bool canModify);
|
||||
void buildCounterTab(const QList<CardState *> &cardList, const QString &zoneName, bool canModify);
|
||||
|
|
|
|||
|
|
@ -33,7 +33,7 @@ enum visualDeckStoragePromptForConversionIndex
|
|||
static int clampHoverTrigger(int trigger)
|
||||
{
|
||||
return qBound(static_cast<int>(CardsDisplaySettings::HoverPanelTriggerHover), trigger,
|
||||
static_cast<int>(CardsDisplaySettings::HoverPanelTriggerShortcut));
|
||||
static_cast<int>(CardsDisplaySettings::HoverPanelTriggerOff));
|
||||
}
|
||||
|
||||
UserInterfaceSettingsPage::UserInterfaceSettingsPage()
|
||||
|
|
@ -167,6 +167,7 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage()
|
|||
hoverZoomTriggerSelector.addItem(QString());
|
||||
hoverZoomTriggerSelector.addItem(QString());
|
||||
hoverZoomTriggerSelector.addItem(QString());
|
||||
hoverZoomTriggerSelector.addItem(QString());
|
||||
hoverZoomTriggerSelector.setCurrentIndex(
|
||||
clampHoverTrigger(SettingsCache::instance().cardsDisplay().getHoverZoomTrigger()));
|
||||
connect(&hoverZoomTriggerSelector, &QComboBox::currentIndexChanged, &SettingsCache::instance().cardsDisplay(),
|
||||
|
|
@ -176,6 +177,7 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage()
|
|||
hoverPanelTriggerSelector.addItem(QString());
|
||||
hoverPanelTriggerSelector.addItem(QString());
|
||||
hoverPanelTriggerSelector.addItem(QString());
|
||||
hoverPanelTriggerSelector.addItem(QString());
|
||||
hoverPanelTriggerSelector.setCurrentIndex(
|
||||
clampHoverTrigger(SettingsCache::instance().cardsDisplay().getHoverPanelTrigger()));
|
||||
connect(&hoverPanelTriggerSelector, &QComboBox::currentIndexChanged, &SettingsCache::instance().cardsDisplay(),
|
||||
|
|
@ -426,9 +428,13 @@ void UserInterfaceSettingsPage::updateHoverPanelUiState()
|
|||
// both phases share one trigger (repeating it).
|
||||
const auto hoverTrigger = CardsDisplaySettings::HoverPanelTriggerHover;
|
||||
const auto wheelTrigger = CardsDisplaySettings::HoverPanelTriggerWheel;
|
||||
const auto offTrigger = CardsDisplaySettings::HoverPanelTriggerOff;
|
||||
const bool zoomOnHover = hoverZoomTriggerSelector.currentIndex() == hoverTrigger;
|
||||
const bool panelOnHover = hoverPanelTriggerSelector.currentIndex() == hoverTrigger;
|
||||
const bool sharedTrigger = hoverZoomTriggerSelector.currentIndex() == hoverPanelTriggerSelector.currentIndex();
|
||||
const bool anyOff =
|
||||
hoverZoomTriggerSelector.currentIndex() == offTrigger || hoverPanelTriggerSelector.currentIndex() == offTrigger;
|
||||
const bool sharedTrigger =
|
||||
!anyOff && hoverZoomTriggerSelector.currentIndex() == hoverPanelTriggerSelector.currentIndex();
|
||||
hoverZoomDelayLabel.setVisible(zoomOnHover);
|
||||
hoverZoomDelayBox.setVisible(zoomOnHover);
|
||||
hoverPanelDelayLabel.setVisible(panelOnHover);
|
||||
|
|
@ -480,6 +486,7 @@ void UserInterfaceSettingsPage::retranslateUi()
|
|||
selector->setItemText(CardsDisplaySettings::HoverPanelTriggerRightClick, tr("Right-click"));
|
||||
selector->setItemText(CardsDisplaySettings::HoverPanelTriggerWheel, tr("Mouse wheel"));
|
||||
selector->setItemText(CardsDisplaySettings::HoverPanelTriggerShortcut, tr("Keyboard shortcut"));
|
||||
selector->setItemText(CardsDisplaySettings::HoverPanelTriggerOff, tr("Off"));
|
||||
}
|
||||
hoverPanelDoubleConfirmCheckBox.setText(tr("Zoom first; repeat the action to show the panel"));
|
||||
hoverZoomDelayLabel.setText(tr("Time before the zoom kicks in:"));
|
||||
|
|
|
|||
|
|
@ -70,12 +70,16 @@ bool CardsDisplaySettings::getScaleCards() const
|
|||
|
||||
int CardsDisplaySettings::getHoverPanelTrigger() const
|
||||
{
|
||||
return getValue("hoverPanelTrigger", QString(), QString(), 0).toInt();
|
||||
// Off by default: the panel is new, so it is opt-in.
|
||||
return getValue("hoverPanelTrigger", QString(), QString(), static_cast<int>(HoverPanelTriggerOff)).toInt();
|
||||
}
|
||||
|
||||
int CardsDisplaySettings::getHoverZoomTrigger() const
|
||||
{
|
||||
return getValue("hoverZoomTrigger", QString(), QString(), 0).toInt();
|
||||
// Off by default: the smooth hover zoom is opt-in. With no trigger configured,
|
||||
// hovering keeps the historical 1.1x scale (AbstractCardItem::setHoverHighlighted)
|
||||
// instead of the smooth zoom to the hover panel size.
|
||||
return getValue("hoverZoomTrigger", QString(), QString(), static_cast<int>(HoverPanelTriggerOff)).toInt();
|
||||
}
|
||||
|
||||
bool CardsDisplaySettings::getHoverPanelDoubleConfirm() const
|
||||
|
|
|
|||
|
|
@ -22,7 +22,9 @@ public:
|
|||
HoverPanelTriggerHover = 0,
|
||||
HoverPanelTriggerRightClick = 1,
|
||||
HoverPanelTriggerWheel = 2,
|
||||
HoverPanelTriggerShortcut = 3
|
||||
HoverPanelTriggerShortcut = 3,
|
||||
/// Never fires. The hover zoom and panel are opt-in, so this is their default.
|
||||
HoverPanelTriggerOff = 4
|
||||
};
|
||||
|
||||
[[nodiscard]] bool getDisplayCardNames() const override;
|
||||
|
|
|
|||
|
|
@ -686,7 +686,8 @@ TEST_F(SettingsDefaultsTest, CardsDisplay_ArrowDrawAnimation_Default)
|
|||
TEST_F(SettingsDefaultsTest, CardsDisplay_HoverPanelTrigger_Default)
|
||||
{
|
||||
CardsDisplaySettings s(settingsPath, nullptr);
|
||||
ASSERT_EQ(s.getHoverPanelTrigger(), 0);
|
||||
// Off by default: the panel is opt-in on upgrade.
|
||||
ASSERT_EQ(s.getHoverPanelTrigger(), CardsDisplaySettings::HoverPanelTriggerOff);
|
||||
}
|
||||
|
||||
TEST_F(SettingsDefaultsTest, CardsDisplay_HoverPanelTrigger_SetAndGet)
|
||||
|
|
@ -701,7 +702,8 @@ TEST_F(SettingsDefaultsTest, CardsDisplay_HoverPanelTrigger_SetAndGet)
|
|||
TEST_F(SettingsDefaultsTest, CardsDisplay_HoverZoomTrigger_Default)
|
||||
{
|
||||
CardsDisplaySettings s(settingsPath, nullptr);
|
||||
ASSERT_EQ(s.getHoverZoomTrigger(), 0);
|
||||
// Off by default: the smooth hover zoom is opt-in on upgrade.
|
||||
ASSERT_EQ(s.getHoverZoomTrigger(), CardsDisplaySettings::HoverPanelTriggerOff);
|
||||
}
|
||||
|
||||
TEST_F(SettingsDefaultsTest, CardsDisplay_HoverZoomTrigger_SetAndGet)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue