diff --git a/cockatrice/src/game_graphics/board/arrow_item.cpp b/cockatrice/src/game_graphics/board/arrow_item.cpp index 665bfea89..97db16aff 100644 --- a/cockatrice/src/game_graphics/board/arrow_item.cpp +++ b/cockatrice/src/game_graphics/board/arrow_item.cpp @@ -4,25 +4,43 @@ #include "../../client/settings/cache_settings.h" #include "../../game/player/player_actions.h" #include "../../game/player/player_logic.h" +#include "../game_scene.h" #include "../player/player_target.h" #include "../z_values.h" #include "../zones/card_zone.h" #include "card_item.h" #include +#include #include #include #include -#include #include #include #include #include #include +#include #include #include #include +namespace +{ +constexpr qreal kMinStrokeDurationMs = 200.0; +constexpr qreal kMaxStrokeDurationMs = 450.0; +constexpr qreal kMsPerPixel = 0.8; +constexpr qreal kGlowFadeDurationMs = 120.0; +constexpr qreal kSheenHalfWidth = 14.0; + +/// @brief Ease-out cubic, for a natural "slow in / slow out" reveal. +qreal easeOutCubic(qreal t) +{ + const qreal inverse = 1.0 - t; + return 1.0 - inverse * inverse * inverse; +} +} // namespace + ArrowItem::ArrowItem(QSharedPointer _data, ArrowTarget *_startItem, ArrowTarget *_targetItem) : data(std::move(_data)), startItem(_startItem), targetItem(_targetItem) { @@ -48,6 +66,13 @@ ArrowItem::ArrowItem(QSharedPointer _data, ArrowTarget *_startI } } +ArrowItem::~ArrowItem() +{ + if (auto *scene = qobject_cast(this->scene())) { + scene->unregisterAnimationItem(this); + } +} + void ArrowItem::onTargetDestroyed() { emit requestDeletion(data->creatorId, data->id); @@ -92,7 +117,10 @@ void ArrowItem::updatePath(const QPointF &endPoint) prepareGeometryChange(); if (lineLength < 30) { path = QPainterPath(); + bodyPath = QPainterPath(); + headPath = QPainterPath(); centerLine = QPainterPath(); + headBaseFraction = 1.0; } else { QPointF c(lineLength / 2, qTan(phi * M_PI / 180) * lineLength); @@ -100,9 +128,9 @@ void ArrowItem::updatePath(const QPointF &endPoint) centerLine.moveTo(0, 0); centerLine.quadTo(c, QPointF(lineLength, 0)); - double percentage = 1 - headLength / lineLength; - QPointF arrowBodyEndPoint = centerLine.pointAtPercent(percentage); - QLineF testLine(arrowBodyEndPoint, centerLine.pointAtPercent(percentage + 0.001)); + headBaseFraction = 1 - headLength / lineLength; + QPointF arrowBodyEndPoint = centerLine.pointAtPercent(headBaseFraction); + QLineF testLine(arrowBodyEndPoint, centerLine.pointAtPercent(headBaseFraction + 0.001)); qreal alpha = testLine.angle() - 90; QPointF endPoint1 = arrowBodyEndPoint + arrowWidth / 2 * QPointF(qCos(alpha * M_PI / 180), -qSin(alpha * M_PI / 180)); @@ -113,14 +141,29 @@ void ArrowItem::updatePath(const QPointF &endPoint) QPointF point2 = endPoint2 + (headWidth - arrowWidth) / 2 * QPointF(-qCos(alpha * M_PI / 180), qSin(alpha * M_PI / 180)); - path = QPainterPath(-arrowWidth / 2 * QPointF(qCos((phi - 90) * M_PI / 180), qSin((phi - 90) * M_PI / 180))); + QPointF start1 = -arrowWidth / 2 * QPointF(qCos((phi - 90) * M_PI / 180), qSin((phi - 90) * M_PI / 180)); + QPointF start2 = arrowWidth / 2 * QPointF(qCos((phi - 90) * M_PI / 180), qSin((phi - 90) * M_PI / 180)); + + path = QPainterPath(start1); path.quadTo(c, endPoint1); path.lineTo(point1); path.lineTo(QPointF(lineLength, 0)); path.lineTo(point2); path.lineTo(endPoint2); - path.quadTo(c, arrowWidth / 2 * QPointF(qCos((phi - 90) * M_PI / 180), qSin((phi - 90) * M_PI / 180))); - path.lineTo(-arrowWidth / 2 * QPointF(qCos((phi - 90) * M_PI / 180), qSin((phi - 90) * M_PI / 180))); + path.quadTo(c, start2); + path.lineTo(start1); + + bodyPath = QPainterPath(start1); + bodyPath.quadTo(c, endPoint1); + bodyPath.lineTo(endPoint2); + bodyPath.quadTo(c, start2); + bodyPath.lineTo(start1); + + headPath = QPainterPath(endPoint1); + headPath.lineTo(point1); + headPath.lineTo(QPointF(lineLength, 0)); + headPath.lineTo(point2); + headPath.lineTo(endPoint2); } setPos(startPoint); @@ -129,28 +172,40 @@ void ArrowItem::updatePath(const QPointF &endPoint) void ArrowItem::startDrawAnimation() { - if (!SettingsCache::instance().userInterface().getAnimationsEnabled() || centerLine.isEmpty()) { + if (!SettingsCache::instance().cardsDisplay().getArrowDrawAnimation() || centerLine.isEmpty()) { return; } + strokeDurationMs = qBound(kMinStrokeDurationMs, centerLine.length() * kMsPerPixel, kMaxStrokeDurationMs); + glowFadeDurationMs = kGlowFadeDurationMs; + animationClock.start(); drawProgress = 0.0; - if (drawTimer == nullptr) { - drawTimer = new QTimer(this); - connect(drawTimer, &QTimer::timeout, this, [this] { - if (!SettingsCache::instance().userInterface().getAnimationsEnabled()) { - drawProgress = 1.0; - drawTimer->stop(); - } else { - drawProgress += 0.08; - if (drawProgress >= 1.0) { - drawProgress = 1.0; - drawTimer->stop(); - } - } - update(); - }); + glowAlpha = 1.0; + update(); + if (auto *scene = qobject_cast(this->scene())) { + scene->registerAnimationItem(this); } - drawTimer->start(20); +} + +bool ArrowItem::animationEvent() +{ + const qint64 elapsed = animationClock.elapsed(); + if (elapsed >= strokeDurationMs + glowFadeDurationMs) { + drawProgress = 1.0; + glowAlpha = 0.0; + update(); + return false; + } + + if (elapsed < strokeDurationMs) { + drawProgress = easeOutCubic(qBound(0.0, elapsed / strokeDurationMs, 1.0)); + glowAlpha = 1.0; + } else { + drawProgress = 1.0; + glowAlpha = 1.0 - (elapsed - strokeDurationMs) / glowFadeDurationMs; + } + update(); + return true; } void ArrowItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*option*/, QWidget * /*widget*/) @@ -163,25 +218,46 @@ void ArrowItem::paint(QPainter *painter, const QStyleOptionGraphicsItem * /*opti } painter->save(); - if (drawProgress < 1.0 && !path.isEmpty()) { - QRectF pr = path.boundingRect(); - qreal revealX = pr.left() + pr.width() * drawProgress; - QPainterPath clip; - clip.addRect(QRectF(pr.left() - 100, pr.top() - 100, revealX - pr.left() + 200, pr.height() + 200)); - painter->setClipPath(clip); - } painter->setBrush(paintColor); - painter->drawPath(path); - if (drawProgress < 1.0 && !centerLine.isEmpty()) { - const QPointF impulse = centerLine.pointAtPercent(drawProgress); - QRadialGradient glow(impulse, 10); - glow.setColorAt(0, QColor(255, 255, 255, 230)); - glow.setColorAt(0.4, paintColor); - glow.setColorAt(1, QColor(paintColor.red(), paintColor.green(), paintColor.blue(), 0)); + if (drawProgress >= 1.0 || path.isEmpty()) { + painter->drawPath(path); + } else if (drawProgress < headBaseFraction) { + // The reveal edge and the sheen share the same arc-length parameterization, + // so the stroke stays exactly in sync with the trailing sheen. + const qreal revealX = centerLine.pointAtPercent(drawProgress).x(); + QPainterPath clip; + clip.addRect(QRectF(-glowExtent, path.boundingRect().top() - glowExtent, revealX + glowExtent, + path.boundingRect().height() + 2 * glowExtent)); + painter->setClipPath(clip); + painter->drawPath(bodyPath); + } else { + // Once the reveal reaches the head base, pop the whole head in with a fade + // instead of slicing the triangle into a growing stub. + painter->drawPath(bodyPath); + const qreal headFadeIn = (drawProgress - headBaseFraction) / (1.0 - headBaseFraction); + painter->setOpacity(headFadeIn); + painter->drawPath(headPath); + painter->setOpacity(1.0); + } + + if (glowAlpha > 0.0 && !centerLine.isEmpty()) { + // Sweep a bright band across the arrow instead of a fuzzy glow dot. Clipping to the + // silhouette keeps it flat against the shaft so it reads as a light reflection. + const qreal anticipation = qMin(1.0, drawProgress / 0.08); + const QPointF sweep = centerLine.pointAtPercent(qMin(drawProgress, 1.0)); + QLinearGradient sheen(sweep.x() - kSheenHalfWidth, 0.0, sweep.x() + kSheenHalfWidth, 0.0); + sheen.setColorAt(0.0, QColor(paintColor.red(), paintColor.green(), paintColor.blue(), 0)); + sheen.setColorAt(0.5, QColor(255, 255, 255, 200)); + sheen.setColorAt(1.0, QColor(paintColor.red(), paintColor.green(), paintColor.blue(), 0)); + painter->save(); painter->setPen(Qt::NoPen); - painter->setBrush(glow); - painter->drawEllipse(impulse, 8, 8); + painter->setClipPath(path); + painter->setBrush(sheen); + painter->setOpacity(glowAlpha * anticipation); + painter->drawRect(QRectF(sweep.x() - kSheenHalfWidth - glowExtent, path.boundingRect().top() - glowExtent, + (kSheenHalfWidth + glowExtent) * 2.0, path.boundingRect().height() + glowExtent * 2.0)); + painter->restore(); } painter->restore(); } diff --git a/cockatrice/src/game_graphics/board/arrow_item.h b/cockatrice/src/game_graphics/board/arrow_item.h index 1e5082d35..d4d45c1cb 100644 --- a/cockatrice/src/game_graphics/board/arrow_item.h +++ b/cockatrice/src/game_graphics/board/arrow_item.h @@ -4,14 +4,15 @@ #include "../../game/board/arrow_data.h" #include "arrow_target.h" +#include #include +#include #include #include class CardItem; class QGraphicsSceneMouseEvent; class PlayerLogic; -class QTimer; class ArrowItem : public QObject, public QGraphicsItem { @@ -22,9 +23,17 @@ signals: private: QPainterPath path; + QPainterPath bodyPath; + QPainterPath headPath; QPainterPath centerLine; + qreal headBaseFraction = 1.0; + QElapsedTimer animationClock; + qreal strokeDurationMs = 0; + qreal glowFadeDurationMs = 0; qreal drawProgress = 1.0; - QTimer *drawTimer = nullptr; + qreal glowAlpha = 0.0; + + static constexpr qreal glowExtent = 12.0; protected: QSharedPointer data; @@ -37,17 +46,19 @@ protected: public: ArrowItem(QSharedPointer _data, ArrowTarget *_startItem, ArrowTarget *_targetItem); + ~ArrowItem() override; void onTargetDestroyed(); void delArrow(); void updatePath(); void updatePath(const QPointF &endPoint); void startDrawAnimation(); + bool animationEvent(); void paint(QPainter *painter, const QStyleOptionGraphicsItem *option, QWidget *widget) override; [[nodiscard]] QRectF boundingRect() const override { - return path.boundingRect(); + return path.boundingRect().adjusted(-glowExtent, -glowExtent, glowExtent, glowExtent); } [[nodiscard]] QPainterPath shape() const override { @@ -111,4 +122,4 @@ protected: void mouseReleaseEvent(QGraphicsSceneMouseEvent *event) override; }; -#endif \ No newline at end of file +#endif diff --git a/cockatrice/src/game_graphics/game_scene.cpp b/cockatrice/src/game_graphics/game_scene.cpp index b214dc2d8..94a9910bc 100644 --- a/cockatrice/src/game_graphics/game_scene.cpp +++ b/cockatrice/src/game_graphics/game_scene.cpp @@ -45,18 +45,19 @@ GameScene::GameScene(PhasesToolbar *_phasesToolbar, QObject *parent) GameScene::~GameScene() { - delete animationTimer; - // Delete all ArrowItems before QGraphicsScene's base destructor runs. // QGraphicsScene::~QGraphicsScene() destroys items in arbitrary order. // If a PlayerTarget is destroyed before an ArrowItem pointing to it, // ArrowItem::onTargetDestroyed fires and emits on the partially-destroyed // GameScene, causing a segfault. + // The arrows must also be deleted before animationTimer so their + // destructors can unregister from the still-valid timer. for (auto *item : items()) { if (auto *arrow = qgraphicsitem_cast(item)) { delete arrow; } } + delete animationTimer; // DO NOT call clearViews() here // clearViews calls close() on the zoneViews, which sends signals; sending signals in destructors leads to segfaults @@ -495,8 +496,8 @@ void GameScene::addArrow(QSharedPointer data) } auto *arrow = new ArrowItem(data, startCard, targetItem); - arrow->startDrawAnimation(); addItem(arrow); + arrow->startDrawAnimation(); arrowRegistry.insert(data, arrow); connect(arrow, &ArrowItem::requestDeletion, this, &GameScene::requestArrowDeletion); } @@ -744,7 +745,14 @@ void GameScene::timerEvent(QTimerEvent * /*event*/) i.remove(); } } - if (cardsToAnimate.isEmpty()) { + QMutableSetIterator j(arrowsToAnimate); + while (j.hasNext()) { + j.next(); + if (!j.value()->animationEvent()) { + j.remove(); + } + } + if (cardsToAnimate.isEmpty() && arrowsToAnimate.isEmpty()) { animationTimer->stop(); } } @@ -760,7 +768,23 @@ void GameScene::registerAnimationItem(AbstractCardItem *card) void GameScene::unregisterAnimationItem(AbstractCardItem *card) { cardsToAnimate.remove(static_cast(card)); - if (cardsToAnimate.isEmpty()) { + if (cardsToAnimate.isEmpty() && arrowsToAnimate.isEmpty()) { + animationTimer->stop(); + } +} + +void GameScene::registerAnimationItem(ArrowItem *arrow) +{ + arrowsToAnimate.insert(arrow); + if (!animationTimer->isActive()) { + animationTimer->start(10, this); + } +} + +void GameScene::unregisterAnimationItem(ArrowItem *arrow) +{ + arrowsToAnimate.remove(arrow); + if (cardsToAnimate.isEmpty() && arrowsToAnimate.isEmpty()) { animationTimer->stop(); } } diff --git a/cockatrice/src/game_graphics/game_scene.h b/cockatrice/src/game_graphics/game_scene.h index 74e979556..778e025c9 100644 --- a/cockatrice/src/game_graphics/game_scene.h +++ b/cockatrice/src/game_graphics/game_scene.h @@ -52,6 +52,7 @@ private: QPointer hoveredCard; ///< Currently hovered card QBasicTimer *animationTimer; ///< Timer for card animations QSet cardsToAnimate; ///< Cards currently animating + QSet arrowsToAnimate; ///< Arrows currently animating int playerRotation; ///< Rotation offset for player layout /** @@ -187,6 +188,12 @@ public: /** @brief Unregisters a card from animation updates. */ void unregisterAnimationItem(AbstractCardItem *card); + + /** @brief Registers an arrow for animation updates. */ + void registerAnimationItem(ArrowItem *arrow); + + /** @brief Unregisters an arrow from animation updates. */ + void unregisterAnimationItem(ArrowItem *arrow); void startRubberBand(const QPointF &selectionOrigin); void resizeRubberBand(const QPointF &cursorPoint, int selectedCount); void stopRubberBand(); diff --git a/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp index 57e90db61..50f2fad35 100644 --- a/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp +++ b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.cpp @@ -116,13 +116,25 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() connect(&tapAnimationCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().cardsDisplay(), &CardsDisplaySettings::setTapAnimation); - animationsEnabledCheckBox.setChecked(SettingsCache::instance().userInterface().getAnimationsEnabled()); - connect(&animationsEnabledCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().userInterface(), - &InterfaceSettings::setAnimationsEnabled); + arrowDrawAnimationCheckBox.setChecked(SettingsCache::instance().cardsDisplay().getArrowDrawAnimation()); + connect(&arrowDrawAnimationCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().cardsDisplay(), + &CardsDisplaySettings::setArrowDrawAnimation); + + visualDeckStorageSelectionAnimationCheckBox.setChecked( + SettingsCache::instance().visualDeckStorage().getVisualDeckStorageSelectionAnimation()); + connect(&visualDeckStorageSelectionAnimationCheckBox, &QCheckBox::QT_STATE_CHANGED, + &SettingsCache::instance().visualDeckStorage(), + &VisualDeckStorageSettings::setVisualDeckStorageSelectionAnimation); + + connect(&enableAllAnimationsButton, &QPushButton::clicked, this, &UserInterfaceSettingsPage::enableAllAnimations); + connect(&disableAllAnimationsButton, &QPushButton::clicked, this, &UserInterfaceSettingsPage::disableAllAnimations); auto *animationGrid = new QGridLayout; - animationGrid->addWidget(&animationsEnabledCheckBox, 0, 0); + animationGrid->addWidget(&enableAllAnimationsButton, 0, 0); + animationGrid->addWidget(&disableAllAnimationsButton, 0, 1); animationGrid->addWidget(&tapAnimationCheckBox, 1, 0); + animationGrid->addWidget(&arrowDrawAnimationCheckBox, 2, 0); + animationGrid->addWidget(&visualDeckStorageSelectionAnimationCheckBox, 3, 0); animationGroupBox = new QGroupBox; animationGroupBox->setLayout(animationGrid); @@ -137,12 +149,6 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() connect(&visualDeckStorageInGameCheckBox, &QCheckBox::QT_STATE_CHANGED, &SettingsCache::instance().visualDeckStorage(), &VisualDeckStorageSettings::setVisualDeckStorageInGame); - visualDeckStorageSelectionAnimationCheckBox.setChecked( - SettingsCache::instance().visualDeckStorage().getVisualDeckStorageSelectionAnimation()); - connect(&visualDeckStorageSelectionAnimationCheckBox, &QCheckBox::QT_STATE_CHANGED, - &SettingsCache::instance().visualDeckStorage(), - &VisualDeckStorageSettings::setVisualDeckStorageSelectionAnimation); - visualDeckStoragePromptForConversionSelector.addItem(""); // these will be set in retranslateUI visualDeckStoragePromptForConversionSelector.addItem(""); visualDeckStoragePromptForConversionSelector.addItem(""); @@ -221,15 +227,14 @@ UserInterfaceSettingsPage::UserInterfaceSettingsPage() auto *deckEditorGrid = new QGridLayout; deckEditorGrid->addWidget(&openDeckInNewTabCheckBox, 0, 0); deckEditorGrid->addWidget(&visualDeckStorageInGameCheckBox, 1, 0); - deckEditorGrid->addWidget(&visualDeckStorageSelectionAnimationCheckBox, 2, 0); - deckEditorGrid->addWidget(&visualDeckStoragePromptForConversionLabel, 3, 0); - deckEditorGrid->addWidget(&visualDeckStoragePromptForConversionSelector, 3, 1); - deckEditorGrid->addWidget(&defaultDeckEditorTypeLabel, 4, 0); - deckEditorGrid->addWidget(&defaultDeckEditorTypeSelector, 4, 1); - deckEditorGrid->addWidget(&commanderSpellbookIntegrationEnabledLabel, 5, 0); - deckEditorGrid->addWidget(&commanderSpellbookIntegrationEnabledSelector, 5, 1); - deckEditorGrid->addWidget(labelWidget, 6, 0); - deckEditorGrid->addWidget(&commanderSpellbookIntegrationBracketNamingSelector, 6, 1); + deckEditorGrid->addWidget(&visualDeckStoragePromptForConversionLabel, 2, 0); + deckEditorGrid->addWidget(&visualDeckStoragePromptForConversionSelector, 2, 1); + deckEditorGrid->addWidget(&defaultDeckEditorTypeLabel, 3, 0); + deckEditorGrid->addWidget(&defaultDeckEditorTypeSelector, 3, 1); + deckEditorGrid->addWidget(&commanderSpellbookIntegrationEnabledLabel, 4, 0); + deckEditorGrid->addWidget(&commanderSpellbookIntegrationEnabledSelector, 4, 1); + deckEditorGrid->addWidget(labelWidget, 5, 0); + deckEditorGrid->addWidget(&commanderSpellbookIntegrationBracketNamingSelector, 5, 1); deckEditorGroupBox = new QGroupBox; deckEditorGroupBox->setLayout(deckEditorGrid); @@ -273,6 +278,20 @@ void UserInterfaceSettingsPage::setNotificationEnabled(QT_STATE_CHANGED_T i) } } +void UserInterfaceSettingsPage::enableAllAnimations() +{ + tapAnimationCheckBox.setChecked(true); + arrowDrawAnimationCheckBox.setChecked(true); + visualDeckStorageSelectionAnimationCheckBox.setChecked(true); +} + +void UserInterfaceSettingsPage::disableAllAnimations() +{ + tapAnimationCheckBox.setChecked(false); + arrowDrawAnimationCheckBox.setChecked(false); + visualDeckStorageSelectionAnimationCheckBox.setChecked(false); +} + void UserInterfaceSettingsPage::updateCommanderSpellbookUiState() { const int mode = SettingsCache::instance().deckEditor().getCommanderSpellbookIntegrationEnabled(); @@ -315,12 +334,14 @@ void UserInterfaceSettingsPage::retranslateUi() specNotificationsEnabledCheckBox.setText(tr("Notify in the taskbar for game events while you are spectating")); buddyConnectNotificationsEnabledCheckBox.setText(tr("Notify in the taskbar when users in your buddy list connect")); animationGroupBox->setTitle(tr("Animation settings")); - animationsEnabledCheckBox.setText(tr("Enable game &animations")); + enableAllAnimationsButton.setText(tr("&Enable all animations")); + disableAllAnimationsButton.setText(tr("&Disable all animations")); tapAnimationCheckBox.setText(tr("&Tap/untap animation")); + arrowDrawAnimationCheckBox.setText(tr("&Arrow draw animation")); + visualDeckStorageSelectionAnimationCheckBox.setText(tr("Use selection animation for Visual Deck Storage")); deckEditorGroupBox->setTitle(tr("Deck editor/storage settings")); openDeckInNewTabCheckBox.setText(tr("Open deck in new tab by default")); visualDeckStorageInGameCheckBox.setText(tr("Use visual deck storage in game lobby")); - visualDeckStorageSelectionAnimationCheckBox.setText(tr("Use selection animation for Visual Deck Storage")); visualDeckStoragePromptForConversionLabel.setText( tr("When adding a tag in the visual deck storage to a .txt deck:")); visualDeckStoragePromptForConversionSelector.setItemText(visualDeckStoragePromptForConversionIndexNone, diff --git a/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.h b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.h index 9816305b6..fd7a54b80 100644 --- a/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.h +++ b/cockatrice/src/interface/widgets/settings_page/user_interface_settings_page.h @@ -7,6 +7,7 @@ #include #include #include +#include #include #include #include @@ -17,6 +18,8 @@ class UserInterfaceSettingsPage : public AbstractSettingsPage Q_OBJECT private slots: void setNotificationEnabled(QT_STATE_CHANGED_T); + void enableAllAnimations(); + void disableAllAnimations(); void updateCommanderSpellbookUiState(); private: @@ -35,7 +38,9 @@ private: QCheckBox useTearOffMenusCheckBox; QCheckBox keepGameChatFocusCheckBox; QCheckBox tapAnimationCheckBox; - QCheckBox animationsEnabledCheckBox; + QCheckBox arrowDrawAnimationCheckBox; + QPushButton enableAllAnimationsButton; + QPushButton disableAllAnimationsButton; QCheckBox openDeckInNewTabCheckBox; QLabel visualDeckStoragePromptForConversionLabel; QComboBox visualDeckStoragePromptForConversionSelector; diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp index 4b4dee55b..d56d76b8d 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.cpp @@ -212,7 +212,8 @@ void VisualDeckStorageWidget::updateTagsVisibility(const bool visible) } } -void VisualDeckStorageWidget::updateSelectionAnimationEnabled(const bool enabled) +void VisualDeckStorageWidget::updateSelectionAnimationEnabled() { - deckPreviewSelectionAnimationEnabled = enabled; + deckPreviewSelectionAnimationEnabled = + SettingsCache::instance().visualDeckStorage().getVisualDeckStorageSelectionAnimation(); } \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.h b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.h index c3c0ae91b..607e9618e 100644 --- a/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.h +++ b/cockatrice/src/interface/widgets/visual_deck_storage/visual_deck_storage_widget.h @@ -45,7 +45,7 @@ public slots: void updateColorFilter(); void updateSearchFilter(); void updateTagsVisibility(bool visible); - void updateSelectionAnimationEnabled(bool enabled); + void updateSelectionAnimationEnabled(); void updateSortOrder(); void resizeEvent(QResizeEvent *event) override; void showEvent(QShowEvent *event) override; diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/interface_cards_display_settings_provider.h b/libcockatrice_interfaces/libcockatrice/interfaces/interface_cards_display_settings_provider.h index 3ee3d2aef..3f2cbbe8e 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/interface_cards_display_settings_provider.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/interface_cards_display_settings_provider.h @@ -15,6 +15,7 @@ public: [[nodiscard]] virtual bool getIncludeRebalancedCards() const = 0; [[nodiscard]] virtual bool getPrintingSelectorNavigationButtonsVisible() const = 0; [[nodiscard]] virtual bool getTapAnimation() const = 0; + [[nodiscard]] virtual bool getArrowDrawAnimation() const = 0; [[nodiscard]] virtual bool getAutoRotateSidewaysLayoutCards() const = 0; [[nodiscard]] virtual bool getScaleCards() const = 0; [[nodiscard]] virtual int getStackCardOverlapPercent() const = 0; diff --git a/libcockatrice_interfaces/libcockatrice/interfaces/interface_interface_settings_provider.h b/libcockatrice_interfaces/libcockatrice/interfaces/interface_interface_settings_provider.h index 0d99e6b8f..ab2caa0d7 100644 --- a/libcockatrice_interfaces/libcockatrice/interfaces/interface_interface_settings_provider.h +++ b/libcockatrice_interfaces/libcockatrice/interfaces/interface_interface_settings_provider.h @@ -39,7 +39,6 @@ public: [[nodiscard]] virtual bool getShowStatusBar() const = 0; [[nodiscard]] virtual bool getShowShortcuts() const = 0; [[nodiscard]] virtual bool getShowGameSelectorFilterToolbar() const = 0; - [[nodiscard]] virtual bool getAnimationsEnabled() const = 0; }; #endif // COCKATRICE_INTERFACE_INTERFACE_SETTINGS_PROVIDER_H diff --git a/libcockatrice_settings/libcockatrice/settings/cards_display_settings.cpp b/libcockatrice_settings/libcockatrice/settings/cards_display_settings.cpp index 6ec1af962..f528a7c4b 100644 --- a/libcockatrice_settings/libcockatrice/settings/cards_display_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/cards_display_settings.cpp @@ -50,6 +50,11 @@ bool CardsDisplaySettings::getTapAnimation() const return getValue("tapAnimation", QString(), QString(), true).toBool(); } +bool CardsDisplaySettings::getArrowDrawAnimation() const +{ + return getValue("arrowDrawAnimation", QString(), QString(), true).toBool(); +} + bool CardsDisplaySettings::getAutoRotateSidewaysLayoutCards() const { return getValue("autoRotateSidewaysLayoutCards", QString(), QString(), true).toBool(); @@ -159,6 +164,11 @@ void CardsDisplaySettings::setTapAnimation(bool _tapAnimation) setValue(_tapAnimation, "tapAnimation"); } +void CardsDisplaySettings::setArrowDrawAnimation(bool _arrowDrawAnimation) +{ + setValue(_arrowDrawAnimation, "arrowDrawAnimation"); +} + void CardsDisplaySettings::setAutoRotateSidewaysLayoutCards(bool _autoRotateSidewaysLayoutCards) { setValue(_autoRotateSidewaysLayoutCards, "autoRotateSidewaysLayoutCards"); diff --git a/libcockatrice_settings/libcockatrice/settings/cards_display_settings.h b/libcockatrice_settings/libcockatrice/settings/cards_display_settings.h index 15a3e3ff4..dbafa32ae 100644 --- a/libcockatrice_settings/libcockatrice/settings/cards_display_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/cards_display_settings.h @@ -20,6 +20,7 @@ public: [[nodiscard]] bool getIncludeRebalancedCards() const override; [[nodiscard]] bool getPrintingSelectorNavigationButtonsVisible() const override; [[nodiscard]] bool getTapAnimation() const override; + [[nodiscard]] bool getArrowDrawAnimation() const override; [[nodiscard]] bool getAutoRotateSidewaysLayoutCards() const override; [[nodiscard]] bool getScaleCards() const override; [[nodiscard]] int getStackCardOverlapPercent() const override; @@ -40,6 +41,7 @@ public: void setIncludeRebalancedCards(bool _includeRebalancedCards); void setPrintingSelectorNavigationButtonsVisible(bool _navigationButtonsVisible); void setTapAnimation(bool _tapAnimation); + void setArrowDrawAnimation(bool _arrowDrawAnimation); void setAutoRotateSidewaysLayoutCards(bool _autoRotateSidewaysLayoutCards); void setCardScaling(bool _scaleCards); void setStackCardOverlapPercent(int _verticalCardOverlapPercent); diff --git a/libcockatrice_settings/libcockatrice/settings/interface_settings.cpp b/libcockatrice_settings/libcockatrice/settings/interface_settings.cpp index 2b6ecc1f7..29c57c57e 100644 --- a/libcockatrice_settings/libcockatrice/settings/interface_settings.cpp +++ b/libcockatrice_settings/libcockatrice/settings/interface_settings.cpp @@ -160,11 +160,6 @@ bool InterfaceSettings::getShowGameSelectorFilterToolbar() const return getValue("showGameSelectorFilterToolbar", QString(), QString(), true).toBool(); } -bool InterfaceSettings::getAnimationsEnabled() const -{ - return getValue("animationsEnabled", QString(), QString(), true).toBool(); -} - void InterfaceSettings::setUseTearOffMenus(bool _useTearOffMenus) { setValue(_useTearOffMenus, "useTearOffMenus"); @@ -331,9 +326,3 @@ void InterfaceSettings::setShowGameSelectorFilterToolbar(bool _showGameSelectorF setValue(_showGameSelectorFilterToolbar, "showGameSelectorFilterToolbar"); emit showGameSelectorFilterToolbarChanged(_showGameSelectorFilterToolbar); } - -void InterfaceSettings::setAnimationsEnabled(bool _animationsEnabled) -{ - setValue(_animationsEnabled, "animationsEnabled"); - emit animationsEnabledChanged(_animationsEnabled); -} diff --git a/libcockatrice_settings/libcockatrice/settings/interface_settings.h b/libcockatrice_settings/libcockatrice/settings/interface_settings.h index 4e0f19460..982976310 100644 --- a/libcockatrice_settings/libcockatrice/settings/interface_settings.h +++ b/libcockatrice_settings/libcockatrice/settings/interface_settings.h @@ -42,7 +42,6 @@ public: [[nodiscard]] bool getShowStatusBar() const override; [[nodiscard]] bool getShowShortcuts() const override; [[nodiscard]] bool getShowGameSelectorFilterToolbar() const override; - [[nodiscard]] bool getAnimationsEnabled() const override; void setUseTearOffMenus(bool _useTearOffMenus); void setCardViewInitialRowsMax(int _cardViewInitialRowsMax); @@ -75,7 +74,6 @@ public: void setShowStatusBar(bool _showStatusBar); void setShowShortcuts(bool _showShortcuts); void setShowGameSelectorFilterToolbar(bool _showGameSelectorFilterToolbar); - void setAnimationsEnabled(bool _animationsEnabled); signals: void useTearOffMenusChanged(bool state); @@ -87,7 +85,6 @@ signals: void tallyTypeChanged(int type); void showStatusBarChanged(bool state); void showGameSelectorFilterToolbarChanged(bool state); - void animationsEnabledChanged(bool state); public: explicit InterfaceSettings(const QString &settingPath, QObject *parent = nullptr); diff --git a/tests/settings/settings_defaults_test.cpp b/tests/settings/settings_defaults_test.cpp index dfdad4780..041a60d6f 100644 --- a/tests/settings/settings_defaults_test.cpp +++ b/tests/settings/settings_defaults_test.cpp @@ -476,6 +476,12 @@ TEST_F(SettingsDefaultsTest, CardsDisplay_SampleHandSize_Default) ASSERT_EQ(s.getSampleHandSize(), 7); } +TEST_F(SettingsDefaultsTest, CardsDisplay_ArrowDrawAnimation_Default) +{ + CardsDisplaySettings s(settingsPath, nullptr); + ASSERT_EQ(s.getArrowDrawAnimation(), true); +} + // --- VisualDeckStorageSettings --- TEST_F(SettingsDefaultsTest, VisualDeckStorage_SortingOrder_Default)