From 44fbbb690611aa1bc5ad944d9daac6b35667844c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Lukas=20Br=C3=BCbach?= <44814898+BruebachL@users.noreply.github.com> Date: Sun, 8 Nov 2026 21:03:06 +0100 Subject: [PATCH] [Interface] Give every hover panel action a semantic color The quick actions are the panel's whole point, so a neutral glyph made the most-used buttons the hardest to scan. Every action now names a game fact with its hue: gain, loss, information, hidden information, a mark on the card or a move. The line glyph stays identical, so the colour is the only difference between neighbours. The grid is also ordered by family so a row reads as one idea: the two state toggles, the two gains, the two marks, the two hidden-information actions and the two losses each share a row. Conditional actions (Peek, Unattach) sit at the end of their pair, so a missing one leaves a hole at the section edge instead of splitting a row. In a zone, Play and Play Face Down stay adjacent on purpose: the same action, one of them hiding information. The move tab leads with the three library placements (Movement), then the destinations. actionGridIcon is removed; semanticActionIcon is now the only grid ink. The preview renders the real pages so the whole map can be judged at once. --- .../widgets/cards/card_hover_widget.cpp | 91 ++++++++++--------- .../icon_treatment_preview_test.cpp | 46 +++++----- 2 files changed, 71 insertions(+), 66 deletions(-) diff --git a/cockatrice/src/interface/widgets/cards/card_hover_widget.cpp b/cockatrice/src/interface/widgets/cards/card_hover_widget.cpp index 6f0973d74..4e0f93480 100644 --- a/cockatrice/src/interface/widgets/cards/card_hover_widget.cpp +++ b/cockatrice/src/interface/widgets/cards/card_hover_widget.cpp @@ -86,14 +86,10 @@ static QIcon categoryTabIcon(const QString &prefix) return loadThemedIcon(prefix, IconTint::quiet()); } -static QIcon actionGridIcon(const QString &prefix) -{ - return loadThemedIcon(prefix, IconTint::primary()); -} - -// A semantic action is the exception the icon rules allow: the hue names an -// outcome (play, gain, loss, hidden information). It keeps the grid's line -// glyph so hue is the only difference from its neighbours. +// Every action carries a hue naming a game fact the glyph cannot: a gain, a +// loss, information, hidden information, a mark on the card or a move. Keeping +// one line glyph per action means a row of different families still reads as a +// single grid instead of a set of buttons from different toolbars. static QIcon semanticActionIcon(const QString &prefix, AppColor::Role role) { return loadThemedIcon(prefix, IconTint(role, IconEmphasis::Primary)); @@ -850,17 +846,17 @@ void CardHoverWidget::buildMainTab(const QList &cardList, const QSt ButtonGrid grid(layout); grid.addSection(createSectionHeader(tr("Revealed"), mainPage)); grid.addButton(createGridButton( - tr("Hide"), actionGridIcon(QStringLiteral("icons/arrow_left")), + tr("Hide"), semanticActionIcon(QStringLiteral("icons/arrow_left"), AppColor::Informational), [actions, cardList]() { actions->actHide(cardList); }, mainPage)); grid.addButton(createGridButton(tr("Clone"), semanticActionIcon(QStringLiteral("icons/increment"), AppColor::Positive), invoke(cmClone), mainPage)); grid.addSection(createSectionHeader(tr("Select"), mainPage)); grid.addButton(createGridButton( - tr("Select All"), actionGridIcon(QStringLiteral("icons/search")), + tr("Select All"), semanticActionIcon(QStringLiteral("icons/search"), AppColor::Informational), [gameScene, card]() { gameScene->selectAllInZone(card); }, mainPage)); grid.addButton(createGridButton( - tr("Select Column"), actionGridIcon(QStringLiteral("icons/filter")), + tr("Select Column"), semanticActionIcon(QStringLiteral("icons/filter"), AppColor::Informational), [gameScene, card]() { gameScene->selectSameColumn(card); }, mainPage)); layout->addStretch(1); return; @@ -869,44 +865,49 @@ void CardHoverWidget::buildMainTab(const QList &cardList, const QSt if (zoneName == ZoneNames::TABLE) { ButtonGrid grid(layout); grid.addSection(createSectionHeader(tr("Battlefield"), mainPage)); - grid.addButton( - createGridButton(tr("Tap / Untap"), actionGridIcon(QStringLiteral("icons/sync")), invoke(cmTap), mainPage)); + grid.addButton(createGridButton(tr("Tap / Untap"), + semanticActionIcon(QStringLiteral("icons/sync"), AppColor::Informational), + invoke(cmTap), mainPage)); auto *doesntUntap = createGridButton( - tr("Skip untapping"), actionGridIcon(QStringLiteral("icons/lock")), + tr("Skip untapping"), semanticActionIcon(QStringLiteral("icons/lock"), AppColor::Informational), [actions, cardList]() { actions->cardMenuAction(cardList, cmDoesntUntap); }, mainPage); doesntUntap->setCheckable(true); doesntUntap->setChecked(card->getDoesntUntap()); grid.addButton(doesntUntap); - grid.addButton( - createGridButton(tr("Turn Over"), actionGridIcon(QStringLiteral("icons/swap")), invoke(cmFlip), mainPage)); - if (card->getFaceDown()) { - grid.addButton(createGridButton(tr("Peek at card face"), actionGridIcon(QStringLiteral("icons/view")), - invoke(cmPeek), mainPage)); - } grid.addButton(createGridButton(tr("Clone"), semanticActionIcon(QStringLiteral("icons/increment"), AppColor::Positive), invoke(cmClone), mainPage)); - grid.addButton( - createGridButton(tr("Attach to card..."), actionGridIcon(QStringLiteral("icons/pin")), attach, mainPage)); + grid.addButton(createGridButton(tr("Attach to card..."), + semanticActionIcon(QStringLiteral("icons/pin"), AppColor::Positive), attach, + mainPage)); + grid.addButton(createGridButton(tr("Draw arrow..."), + semanticActionIcon(QStringLiteral("icons/share"), AppColor::Marked), drawArrow, + mainPage)); + grid.addButton(createGridButton( + tr("Set annotation..."), semanticActionIcon(QStringLiteral("icons/pencil"), AppColor::Marked), + [actions, cardList]() { actions->actRequestSetAnnotationDialog(cardList); }, mainPage)); + grid.addButton(createGridButton(tr("Turn Over"), + semanticActionIcon(QStringLiteral("icons/swap"), AppColor::Hidden), + invoke(cmFlip), mainPage)); + if (card->getFaceDown()) { + grid.addButton(createGridButton(tr("Peek at card face"), + semanticActionIcon(QStringLiteral("icons/view"), AppColor::Hidden), + invoke(cmPeek), mainPage)); + } if (card->getAttachedTo()) { grid.addButton(createGridButton( - tr("Unattach"), actionGridIcon(QStringLiteral("icons/arrow_undo")), + tr("Unattach"), semanticActionIcon(QStringLiteral("icons/arrow_undo"), AppColor::Negative), [actions, cardList]() { actions->actUnattach(cardList); }, mainPage)); } - grid.addButton( - createGridButton(tr("Draw arrow..."), actionGridIcon(QStringLiteral("icons/share")), drawArrow, mainPage)); - grid.addButton(createGridButton( - tr("Set annotation..."), actionGridIcon(QStringLiteral("icons/pencil")), - [actions, cardList]() { actions->actRequestSetAnnotationDialog(cardList); }, mainPage)); grid.addButton(createGridButton( tr("Reduce life"), semanticActionIcon(QStringLiteral("icons/arrow_down"), AppColor::Negative), [actions, cardList]() { actions->actReduceLifeByPower(cardList); }, mainPage)); grid.addSection(createSectionHeader(tr("Select"), mainPage)); grid.addButton(createGridButton( - tr("Select All"), actionGridIcon(QStringLiteral("icons/search")), + tr("Select All"), semanticActionIcon(QStringLiteral("icons/search"), AppColor::Informational), [gameScene, card]() { gameScene->selectAllInZone(card); }, mainPage)); grid.addButton(createGridButton( - tr("Select Row"), actionGridIcon(QStringLiteral("icons/filter")), + tr("Select Row"), semanticActionIcon(QStringLiteral("icons/filter"), AppColor::Informational), [gameScene, card]() { gameScene->selectSameRow(card); }, mainPage)); } else if (!zoneName.isEmpty()) { ButtonGrid grid(layout); @@ -915,25 +916,27 @@ void CardHoverWidget::buildMainTab(const QList &cardList, const QSt tr("Play"), semanticActionIcon(QStringLiteral("icons/arrow_right"), AppColor::Positive), [actions, cardList]() { actions->actPlay(cardList); }, mainPage)); grid.addButton(createGridButton( - tr("Play Face Down"), semanticActionIcon(QStringLiteral("icons/arrow_right"), AppColor::Informational), + tr("Play Face Down"), semanticActionIcon(QStringLiteral("icons/arrow_right"), AppColor::Hidden), [actions, cardList]() { actions->actPlayFacedown(cardList); }, mainPage)); grid.addButton(createGridButton(tr("Clone"), semanticActionIcon(QStringLiteral("icons/increment"), AppColor::Positive), invoke(cmClone), mainPage)); if (zoneName == ZoneNames::STACK || zoneName == ZoneNames::HAND || zoneName == ZoneNames::GRAVE || zoneName == ZoneNames::EXILE) { - grid.addButton(createGridButton(tr("Attach to card..."), actionGridIcon(QStringLiteral("icons/pin")), - attach, mainPage)); - grid.addButton(createGridButton(tr("Draw arrow..."), actionGridIcon(QStringLiteral("icons/share")), + grid.addButton(createGridButton(tr("Attach to card..."), + semanticActionIcon(QStringLiteral("icons/pin"), AppColor::Positive), attach, + mainPage)); + grid.addButton(createGridButton(tr("Draw arrow..."), + semanticActionIcon(QStringLiteral("icons/share"), AppColor::Marked), drawArrow, mainPage)); } grid.addSection(createSectionHeader(tr("Select"), mainPage)); grid.addButton(createGridButton( - tr("Select All"), actionGridIcon(QStringLiteral("icons/search")), + tr("Select All"), semanticActionIcon(QStringLiteral("icons/search"), AppColor::Informational), [gameScene, card]() { gameScene->selectAllInZone(card); }, mainPage)); if (qobject_cast(card->getZone())) { grid.addButton(createGridButton( - tr("Select Column"), actionGridIcon(QStringLiteral("icons/filter")), + tr("Select Column"), semanticActionIcon(QStringLiteral("icons/filter"), AppColor::Informational), [gameScene, card]() { gameScene->selectSameColumn(card); }, mainPage)); } } else { @@ -960,15 +963,17 @@ void CardHoverWidget::buildMoveTab(const QList &cardList, const QSt ButtonGrid grid(layout); grid.addSection(createSectionHeader(tr("Move to"), movePage)); - grid.addButton(createGridButton(tr("Top of library"), actionGridIcon(QStringLiteral("icons/arrow_to_top")), + grid.addButton(createGridButton(tr("Top of library"), + semanticActionIcon(QStringLiteral("icons/arrow_to_top"), AppColor::Movement), invoke(cmMoveToTopLibrary), movePage)); grid.addButton(createGridButton( - tr("X from top..."), actionGridIcon(QStringLiteral("icons/search")), + tr("X from top..."), semanticActionIcon(QStringLiteral("icons/search"), AppColor::Movement), [actions]() { actions->actRequestMoveCardXCardsFromTopDialog(); }, movePage, tr("X from top of library..."))); - grid.addButton(createGridButton(tr("Bottom of library"), actionGridIcon(QStringLiteral("icons/arrow_to_bottom")), + grid.addButton(createGridButton(tr("Bottom of library"), + semanticActionIcon(QStringLiteral("icons/arrow_to_bottom"), AppColor::Movement), invoke(cmMoveToBottomLibrary), movePage)); - grid.addButton( - createGridButton(tr("Table"), actionGridIcon(QStringLiteral("icons/player")), invoke(cmMoveToTable), movePage)); + grid.addButton(createGridButton(tr("Table"), semanticActionIcon(QStringLiteral("icons/player"), AppColor::Positive), + invoke(cmMoveToTable), movePage)); grid.addButton(createGridButton(tr("Hand"), semanticActionIcon(QStringLiteral("icons/arrow_left"), AppColor::Positive), invoke(cmMoveToHand), movePage)); @@ -986,7 +991,7 @@ void CardHoverWidget::buildMoveTab(const QList &cardList, const QSt if (game && game->getPlayerManager()) { grid.addSection(createSectionHeader(tr("Reveal to..."), movePage)); grid.addButton(createGridButton( - tr("All players"), actionGridIcon(QStringLiteral("icons/share")), + tr("All players"), semanticActionIcon(QStringLiteral("icons/share"), AppColor::Informational), [actions, cardList]() { auto *fake = new QAction(nullptr); fake->setData(-1); @@ -1002,7 +1007,7 @@ void CardHoverWidget::buildMoveTab(const QList &cardList, const QSt QString name = other->getPlayerInfo()->getName(); int id = other->getPlayerInfo()->getId(); grid.addButton(createGridButton( - name, actionGridIcon(QStringLiteral("icons/player")), + name, semanticActionIcon(QStringLiteral("icons/player"), AppColor::Informational), [actions, cardList, id]() { auto *fake = new QAction(nullptr); fake->setData(id); diff --git a/tests/theme_migration/icon_treatment_preview_test.cpp b/tests/theme_migration/icon_treatment_preview_test.cpp index 04d5f85da..0af177560 100644 --- a/tests/theme_migration/icon_treatment_preview_test.cpp +++ b/tests/theme_migration/icon_treatment_preview_test.cpp @@ -323,8 +323,8 @@ TEST(IconTreatmentPreview, HoverPanelGridWritesPngsWhenRequested) constexpr int GLYPH = 22; constexpr qreal DPR = 2.0; - // The two pages a selected card shows, with every conditional action - // present so the whole map is visible at once. + // The pages a selected card shows, with every conditional action present + // so the whole map is visible at once. const QList pages = { PreviewPage{.title = QStringLiteral("Main — on the battlefield"), .sections = @@ -334,31 +334,31 @@ TEST(IconTreatmentPreview, HoverPanelGridWritesPngsWhenRequested) { PreviewAction{.label = QStringLiteral("Tap / Untap"), .glyph = "sync", - .role = AppColor::IconStrong}, + .role = AppColor::InformationalStrong}, PreviewAction{.label = QStringLiteral("Skip untapping"), .glyph = "lock", - .role = AppColor::IconStrong}, - PreviewAction{.label = QStringLiteral("Turn Over"), - .glyph = "swap", - .role = AppColor::IconStrong}, - PreviewAction{.label = QStringLiteral("Peek at card face"), - .glyph = "view", - .role = AppColor::HiddenStrong}, + .role = AppColor::InformationalStrong}, PreviewAction{.label = QStringLiteral("Clone"), .glyph = "increment", .role = AppColor::PositiveStrong}, PreviewAction{.label = QStringLiteral("Attach to card..."), .glyph = "pin", - .role = AppColor::IconStrong}, - PreviewAction{.label = QStringLiteral("Unattach"), - .glyph = "arrow_undo", - .role = AppColor::NegativeStrong}, + .role = AppColor::PositiveStrong}, PreviewAction{.label = QStringLiteral("Draw arrow..."), .glyph = "share", .role = AppColor::MarkedStrong}, PreviewAction{.label = QStringLiteral("Set annotation..."), .glyph = "pencil", .role = AppColor::MarkedStrong}, + PreviewAction{.label = QStringLiteral("Turn Over"), + .glyph = "swap", + .role = AppColor::HiddenStrong}, + PreviewAction{.label = QStringLiteral("Peek at card face"), + .glyph = "view", + .role = AppColor::HiddenStrong}, + PreviewAction{.label = QStringLiteral("Unattach"), + .glyph = "arrow_undo", + .role = AppColor::NegativeStrong}, PreviewAction{.label = QStringLiteral("Reduce life"), .glyph = "arrow_down", .role = AppColor::NegativeStrong}, @@ -368,10 +368,10 @@ TEST(IconTreatmentPreview, HoverPanelGridWritesPngsWhenRequested) { PreviewAction{.label = QStringLiteral("Select All"), .glyph = "search", - .role = AppColor::IconStrong}, + .role = AppColor::InformationalStrong}, PreviewAction{.label = QStringLiteral("Select Row"), .glyph = "filter", - .role = AppColor::IconStrong}, + .role = AppColor::InformationalStrong}, }}, }}, PreviewPage{.title = QStringLiteral("Main — in a zone"), @@ -391,7 +391,7 @@ TEST(IconTreatmentPreview, HoverPanelGridWritesPngsWhenRequested) .role = AppColor::PositiveStrong}, PreviewAction{.label = QStringLiteral("Attach to card..."), .glyph = "pin", - .role = AppColor::IconStrong}, + .role = AppColor::PositiveStrong}, PreviewAction{.label = QStringLiteral("Draw arrow..."), .glyph = "share", .role = AppColor::MarkedStrong}, @@ -401,10 +401,10 @@ TEST(IconTreatmentPreview, HoverPanelGridWritesPngsWhenRequested) { PreviewAction{.label = QStringLiteral("Select All"), .glyph = "search", - .role = AppColor::IconStrong}, + .role = AppColor::InformationalStrong}, PreviewAction{.label = QStringLiteral("Select Column"), .glyph = "filter", - .role = AppColor::IconStrong}, + .role = AppColor::InformationalStrong}, }}, }}, PreviewPage{.title = QStringLiteral("Move — card in a zone"), @@ -415,16 +415,16 @@ TEST(IconTreatmentPreview, HoverPanelGridWritesPngsWhenRequested) { PreviewAction{.label = QStringLiteral("Top of library"), .glyph = "arrow_to_top", - .role = AppColor::IconStrong}, + .role = AppColor::MovementStrong}, PreviewAction{.label = QStringLiteral("X from top..."), .glyph = "search", - .role = AppColor::IconStrong}, + .role = AppColor::MovementStrong}, PreviewAction{.label = QStringLiteral("Bottom of library"), .glyph = "arrow_to_bottom", - .role = AppColor::IconStrong}, + .role = AppColor::MovementStrong}, PreviewAction{.label = QStringLiteral("Table"), .glyph = "player", - .role = AppColor::IconStrong}, + .role = AppColor::PositiveStrong}, PreviewAction{.label = QStringLiteral("Hand"), .glyph = "arrow_left", .role = AppColor::PositiveStrong},