diff --git a/cockatrice/CMakeLists.txt b/cockatrice/CMakeLists.txt index 2d370d03c..cc58c5b43 100644 --- a/cockatrice/CMakeLists.txt +++ b/cockatrice/CMakeLists.txt @@ -230,6 +230,7 @@ set(cockatrice_SOURCES src/interface/widgets/printing_selector/set_name_and_collectors_number_display_widget.cpp src/interface/widgets/quick_settings/settings_button_widget.cpp src/interface/widgets/quick_settings/settings_popup_widget.cpp + src/interface/widgets/replay/replay_manager.cpp src/interface/widgets/replay/replay_quick_settings_widget.cpp src/interface/widgets/replay/replay_timeline_widget.cpp src/interface/widgets/replay/replay_widget.cpp diff --git a/cockatrice/src/game/abstract_game.cpp b/cockatrice/src/game/abstract_game.cpp index c20003ece..6aa2ab28f 100644 --- a/cockatrice/src/game/abstract_game.cpp +++ b/cockatrice/src/game/abstract_game.cpp @@ -31,7 +31,7 @@ AbstractClient *AbstractGame::getClientForPlayer(int playerId) const } } -void AbstractGame::loadReplay(GameReplay *replay) +void AbstractGame::loadReplay(const GameReplay *replay) { gameMetaInfo->setFromProto(replay->game_info()); gameMetaInfo->setSpectatorsOmniscient(true); diff --git a/cockatrice/src/game/abstract_game.h b/cockatrice/src/game/abstract_game.h index 5115ed5ca..fcf764492 100644 --- a/cockatrice/src/game/abstract_game.h +++ b/cockatrice/src/game/abstract_game.h @@ -53,7 +53,7 @@ public: AbstractClient *getClientForPlayer(int playerId) const; - void loadReplay(GameReplay *replay); + void loadReplay(const GameReplay *replay); CardItem *getCard(int playerId, const QString &zoneName, int cardId) const; diff --git a/cockatrice/src/game/replay.cpp b/cockatrice/src/game/replay.cpp index 69f9d8b20..dcf3e9b9b 100644 --- a/cockatrice/src/game/replay.cpp +++ b/cockatrice/src/game/replay.cpp @@ -2,7 +2,7 @@ #include "../interface/widgets/tabs/tab_game.h" -Replay::Replay(QObject *_parent, GameReplay *_replay, bool isLocalGame) : AbstractGame(_parent) +Replay::Replay(QObject *_parent, const GameReplay *_replay, bool isLocalGame) : AbstractGame(_parent) { gameState = new GameState(this, 0, -1, isLocalGame, {}, false, false, -1, false); connect(gameMetaInfo, &GameMetaInfo::startedChanged, gameState, &GameState::onStartedChanged); diff --git a/cockatrice/src/game/replay.h b/cockatrice/src/game/replay.h index ecb3a10d0..1c269b273 100644 --- a/cockatrice/src/game/replay.h +++ b/cockatrice/src/game/replay.h @@ -15,7 +15,7 @@ class Replay : public AbstractGame Q_OBJECT public: - explicit Replay(QObject *_parent, GameReplay *_replay, bool isLocalGame); + explicit Replay(QObject *_parent, const GameReplay *_replay, bool isLocalGame); }; #endif // COCKATRICE_REPLAY_H diff --git a/cockatrice/src/interface/widgets/replay/replay_manager.cpp b/cockatrice/src/interface/widgets/replay/replay_manager.cpp new file mode 100644 index 000000000..1037d36a8 --- /dev/null +++ b/cockatrice/src/interface/widgets/replay/replay_manager.cpp @@ -0,0 +1,178 @@ +#include "replay_manager.h" + +#include "../../../client/settings/cache_settings.h" + +#include +#include + +static constexpr int TIMER_INTERVAL_MS = 200; + +static QList createReplayTimeline(const GameReplay *replay) +{ + // Create list: event number -> time [ms] + unsigned int lastEventTimestamp = 0; + const int eventCount = replay->event_list_size(); + + QList replayTimeline; + for (int i = 0; i < eventCount; ++i) { + int nextSecondIndex = i + 1; + while (nextSecondIndex < eventCount && + replay->event_list(nextSecondIndex).seconds_elapsed() == lastEventTimestamp) { + ++nextSecondIndex; + } + + // Distribute simultaneous events evenly across 1 second. + const int numberEventsThisSecond = nextSecondIndex - i; + for (int k = 0; k < numberEventsThisSecond; ++k) { + int eventMs = replay->event_list(i + k).seconds_elapsed() * 1000; + int distributionMs = static_cast(static_cast(k) / numberEventsThisSecond * 1000); + replayTimeline.append(eventMs + distributionMs); + } + + if (nextSecondIndex < eventCount) { + lastEventTimestamp = replay->event_list(nextSecondIndex).seconds_elapsed(); + } + i += numberEventsThisSecond - 1; + } + + return replayTimeline; +} + +ReplayManager::ReplayManager(QObject *parent, GameReplay *replay) + : QObject(parent), replay(replay), replayTimeline(createReplayTimeline(replay)) +{ + maxTime = replayTimeline.isEmpty() ? 0 : replayTimeline.last(); + + replayTimer = new QTimer(this); + replayTimer->setInterval(TIMER_INTERVAL_MS); + connect(replayTimer, &QTimer::timeout, this, &ReplayManager::replayTimerTimeout); + + rewindBufferingTimer = new QTimer(this); + rewindBufferingTimer->setSingleShot(true); + connect(rewindBufferingTimer, &QTimer::timeout, this, &ReplayManager::processRewind); +} + +ReplayManager::~ReplayManager() +{ + delete replay; +} + +void ReplayManager::skipToTime(int newTime, bool doRewindBuffering) +{ + // check boundary conditions + if (newTime < 0) { + newTime = 0; + } + if (newTime > maxTime) { + newTime = maxTime; + } + + newTime -= newTime % TIMER_INTERVAL_MS; // Time should always be a multiple of the interval + + const bool isBackwardsSkip = newTime < currentProcessedTime; + currentVisualTime = newTime; + + if (isBackwardsSkip) { + handleBackwardsSkip(doRewindBuffering); + } else { + processNewEvents(FORWARD_SKIP); + } + + timeChanged(currentVisualTime); +} + +/** + * @brief Handles a backwards skip in the replay timeline. + * + * @param doRewindBuffering When true, if multiple backward skips are made in quick succession, only a single rewind + * is processed at the end. When false, the backwards skip will always cause an immediate rewind. + */ +void ReplayManager::handleBackwardsSkip(bool doRewindBuffering) +{ + if (doRewindBuffering) { + // We use a one-shot timer to implement the rewind buffering. + // The rewind only happens once the timer runs out. + // If another backwards skip happens, the timer will just get reset instead of rewinding. + rewindBufferingTimer->stop(); + rewindBufferingTimer->start(SettingsCache::instance().interface().getRewindBufferingMs()); + } else { + // otherwise, process the rewind immediately + processRewind(); + } +} + +void ReplayManager::processRewind() +{ + // stop any queued-up rewinds + rewindBufferingTimer->stop(); + + // process the rewind + currentEvent = 0; + emit rewound(); + processNewEvents(BACKWARD_SKIP); +} + +void ReplayManager::replayTimerTimeout() +{ + currentVisualTime += TIMER_INTERVAL_MS; + + processNewEvents(NORMAL_PLAYBACK); + + timeChanged(currentVisualTime); +} + +/** @brief Processes all unprocessed events up to the current time. */ +void ReplayManager::processNewEvents(PlaybackMode playbackMode) +{ + currentProcessedTime = currentVisualTime; + + while (currentEvent < replayTimeline.size() && replayTimeline[currentEvent] < currentProcessedTime) { + EventProcessingOptions options; + + // backwards skip => always skip reveal windows + // forwards skip => skip reveal windows that don't happen within a big skip of the target + if (playbackMode == BACKWARD_SKIP || currentProcessedTime - replayTimeline[currentEvent] > BIG_SKIP_MS) { + options |= SKIP_REVEAL_WINDOW; + } + + // backwards skip => always skip tap animation + if (playbackMode == BACKWARD_SKIP) { + options |= SKIP_TAP_ANIMATION; + } + + emit eventReplayed(replay->event_list(currentEvent), options); + ++currentEvent; + } + if (currentEvent == replayTimeline.size()) { + emit replayFinished(); + replayTimer->stop(); + } +} + +void ReplayManager::setTimeScaleFactor(qreal _timeScaleFactor) +{ + timeScaleFactor = _timeScaleFactor; + int interval = std::max(1, qRound(TIMER_INTERVAL_MS / timeScaleFactor)); + replayTimer->setInterval(interval); +} + +void ReplayManager::startReplay() +{ + replayTimer->start(); +} + +void ReplayManager::stopReplay() +{ + replayTimer->stop(); +} + +void ReplayManager::setTime(int time) +{ + // don't buffer rewinds from clicks, since clicks usually don't happen fast enough to require buffering + skipToTime(time, false); +} + +void ReplayManager::skipByAmount(int amount) +{ + skipToTime(currentVisualTime + amount, amount < 0); +} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/replay/replay_manager.h b/cockatrice/src/interface/widgets/replay/replay_manager.h new file mode 100644 index 000000000..16d3591ba --- /dev/null +++ b/cockatrice/src/interface/widgets/replay/replay_manager.h @@ -0,0 +1,80 @@ +#ifndef COCKATRICE_REPLAY_MANAGER_H +#define COCKATRICE_REPLAY_MANAGER_H + +#include "../../../game/player/event_processing_options.h" + +#include +#include + +class GameReplay; +class QTimer; + +/** + * @brief This class handles all logic to do with playing back replays + */ +class ReplayManager : public QObject +{ + Q_OBJECT + + enum PlaybackMode + { + NORMAL_PLAYBACK, + FORWARD_SKIP, + BACKWARD_SKIP + }; + + GameReplay *replay; + QList replayTimeline; ///< timestamp of each event, with the indexes corresponding + int maxTime; + + QTimer *replayTimer; + QTimer *rewindBufferingTimer; + + qreal timeScaleFactor = 1.0; + + int currentVisualTime = 0; ///< time currently displayed by the timeline + int currentProcessedTime = 0; ///< time that events are currently processed up to. Could differ from visual time due + ///< to rewind buffering + int currentEvent = 0; ///< current event's index + + void skipToTime(int newTime, bool doRewindBuffering); + void handleBackwardsSkip(bool doRewindBuffering); + void processRewind(); + void processNewEvents(PlaybackMode playbackMode); + +private slots: + void replayTimerTimeout(); + +public: + static constexpr int SMALL_SKIP_MS = 1000; + static constexpr int BIG_SKIP_MS = 10000; + + /** + * @param parent The parent QObject + * @param replay Cannot be null. Takes ownership of the object. + */ + explicit ReplayManager(QObject *parent, GameReplay *replay); + + ~ReplayManager() override; + + const QList &getReplayTimeline() const + { + return replayTimeline; + } + + void setTimeScaleFactor(qreal _timeScaleFactor); + +public slots: + void startReplay(); + void stopReplay(); + void setTime(int time); + void skipByAmount(int amount); // use a negative amount to skip backwards + +signals: + void timeChanged(int time); + void eventReplayed(const GameEventContainer &cont, EventProcessingOptions options); + void replayFinished(); + void rewound(); +}; + +#endif // COCKATRICE_REPLAY_MANAGER_H diff --git a/cockatrice/src/interface/widgets/replay/replay_timeline_widget.cpp b/cockatrice/src/interface/widgets/replay/replay_timeline_widget.cpp index 9c699d300..b5c7bf301 100644 --- a/cockatrice/src/interface/widgets/replay/replay_timeline_widget.cpp +++ b/cockatrice/src/interface/widgets/replay/replay_timeline_widget.cpp @@ -4,26 +4,19 @@ #include #include -#include -#include -ReplayTimelineWidget::ReplayTimelineWidget(QWidget *parent) - : QWidget(parent), maxBinValue(1), maxTime(1), timeScaleFactor(1.0), currentVisualTime(0), currentProcessedTime(0), - currentEvent(0) +static constexpr int BIN_LENGTH = 5000; +static constexpr int MIN_RESOLUTION_MS = 1000; + +ReplayTimelineWidget::ReplayTimelineWidget(QWidget *parent) : QWidget(parent) { - replayTimer = new QTimer(this); - replayTimer->setInterval(TIMER_INTERVAL_MS); - connect(replayTimer, &QTimer::timeout, this, &ReplayTimelineWidget::replayTimerTimeout); - - rewindBufferingTimer = new QTimer(this); - rewindBufferingTimer->setSingleShot(true); - connect(rewindBufferingTimer, &QTimer::timeout, this, &ReplayTimelineWidget::processRewind); } -void ReplayTimelineWidget::setTimeline(const QList &_replayTimeline) +void ReplayTimelineWidget::setTimeline(const QList &replayTimeline) { - replayTimeline = _replayTimeline; histogram.clear(); + currentTime = 0; + int binEndTime = BIN_LENGTH - 1; int binValue = 0; for (int i : replayTimeline) { @@ -66,7 +59,7 @@ void ReplayTimelineWidget::paintEvent(QPaintEvent * /* event */) painter.fillPath(path, Qt::black); const QColor barColor = QColor::fromHsv(120, 255, 255, 100); - quint64 w = (quint64)(width() - 1) * (quint64)currentVisualTime / maxTime; + quint64 w = (quint64)(width() - 1) * (quint64)currentTime / maxTime; painter.fillRect(0, 0, static_cast(w), height() - 1, barColor); } @@ -77,63 +70,24 @@ void ReplayTimelineWidget::mousePressEvent(QMouseEvent *event) #else int newTime = static_cast((qint64)maxTime * (qint64)event->x() / width()); #endif - // don't buffer rewinds from clicks, since clicks usually don't happen fast enough to require buffering - skipToTime(newTime, false); + emit timeClicked(newTime); } -void ReplayTimelineWidget::skipToTime(int newTime, bool doRewindBuffering) +void ReplayTimelineWidget::setCurrentTime(int time) { - // check boundary conditions - if (newTime < 0) { - newTime = 0; - } - if (newTime > maxTime) { - newTime = maxTime; + int newTime = qBound(0, time, maxTime); + + if (currentTime == newTime) { + return; } - newTime -= newTime % TIMER_INTERVAL_MS; // Time should always be a multiple of the interval + bool doUpdate = currentTime / MIN_RESOLUTION_MS != newTime / MIN_RESOLUTION_MS; - const bool isBackwardsSkip = newTime < currentProcessedTime; - currentVisualTime = newTime; + currentTime = newTime; - if (isBackwardsSkip) { - handleBackwardsSkip(doRewindBuffering); - } else { - processNewEvents(FORWARD_SKIP); + if (doUpdate) { + update(); } - - update(); -} - -/** - * @brief Handles a backwards skip in the replay timeline. - * - * @param doRewindBuffering When true, if multiple backward skips are made in quick succession, only a single rewind - * is processed at the end. When false, the backwards skip will always cause an immediate rewind. - */ -void ReplayTimelineWidget::handleBackwardsSkip(bool doRewindBuffering) -{ - if (doRewindBuffering) { - // We use a one-shot timer to implement the rewind buffering. - // The rewind only happens once the timer runs out. - // If another backwards skip happens, the timer will just get reset instead of rewinding. - rewindBufferingTimer->stop(); - rewindBufferingTimer->start(SettingsCache::instance().interface().getRewindBufferingMs()); - } else { - // otherwise, process the rewind immediately - processRewind(); - } -} - -void ReplayTimelineWidget::processRewind() -{ - // stop any queued-up rewinds - rewindBufferingTimer->stop(); - - // process the rewind - currentEvent = 0; - emit rewound(); - processNewEvents(BACKWARD_SKIP); } QSize ReplayTimelineWidget::sizeHint() const @@ -145,64 +99,3 @@ QSize ReplayTimelineWidget::minimumSizeHint() const { return {400, 50}; } - -void ReplayTimelineWidget::replayTimerTimeout() -{ - currentVisualTime += TIMER_INTERVAL_MS; - - processNewEvents(NORMAL_PLAYBACK); - - if (!(currentVisualTime % 1000)) { - update(); - } -} - -/** @brief Processes all unprocessed events up to the current time. */ -void ReplayTimelineWidget::processNewEvents(PlaybackMode playbackMode) -{ - currentProcessedTime = currentVisualTime; - - while ((currentEvent < replayTimeline.size()) && (replayTimeline[currentEvent] < currentProcessedTime)) { - EventProcessingOptions options; - - // backwards skip => always skip reveal windows - // forwards skip => skip reveal windows that don't happen within a big skip of the target - if (playbackMode == BACKWARD_SKIP || currentProcessedTime - replayTimeline[currentEvent] > BIG_SKIP_MS) { - options |= SKIP_REVEAL_WINDOW; - } - - // backwards skip => always skip tap animation - if (playbackMode == BACKWARD_SKIP) { - options |= SKIP_TAP_ANIMATION; - } - - emit processNextEvent(options); - ++currentEvent; - } - if (currentEvent == replayTimeline.size()) { - emit replayFinished(); - replayTimer->stop(); - } -} - -void ReplayTimelineWidget::setTimeScaleFactor(qreal _timeScaleFactor) -{ - timeScaleFactor = _timeScaleFactor; - int interval = std::max(1, qRound(TIMER_INTERVAL_MS / timeScaleFactor)); - replayTimer->setInterval(interval); -} - -void ReplayTimelineWidget::startReplay() -{ - replayTimer->start(); -} - -void ReplayTimelineWidget::stopReplay() -{ - replayTimer->stop(); -} - -void ReplayTimelineWidget::skipByAmount(int amount) -{ - skipToTime(currentVisualTime + amount, amount < 0); -} \ No newline at end of file diff --git a/cockatrice/src/interface/widgets/replay/replay_timeline_widget.h b/cockatrice/src/interface/widgets/replay/replay_timeline_widget.h index 6cdb8bcb2..47d19a741 100644 --- a/cockatrice/src/interface/widgets/replay/replay_timeline_widget.h +++ b/cockatrice/src/interface/widgets/replay/replay_timeline_widget.h @@ -18,57 +18,25 @@ class QTimer; class ReplayTimelineWidget : public QWidget { Q_OBJECT + signals: - void processNextEvent(EventProcessingOptions options); - void replayFinished(); - void rewound(); + void timeClicked(int newTime); private: - enum PlaybackMode - { - NORMAL_PLAYBACK, - FORWARD_SKIP, - BACKWARD_SKIP - }; - - static constexpr int TIMER_INTERVAL_MS = 200; - static constexpr int BIN_LENGTH = 5000; - - QTimer *replayTimer; - QTimer *rewindBufferingTimer; - QList replayTimeline; QList histogram; - int maxBinValue, maxTime; - qreal timeScaleFactor; - int currentVisualTime; // time currently displayed by the timeline - int currentProcessedTime; // time that events are currently processed up to. Could differ from visual time due to - // rewind buffering - int currentEvent; + int maxBinValue = 1; + int maxTime = 1; - void skipToTime(int newTime, bool doRewindBuffering); - void handleBackwardsSkip(bool doRewindBuffering); - void processRewind(); - void processNewEvents(PlaybackMode playbackMode); -private slots: - void replayTimerTimeout(); + int currentTime = 0; public: - static constexpr int SMALL_SKIP_MS = 1000; - static constexpr int BIG_SKIP_MS = 10000; - explicit ReplayTimelineWidget(QWidget *parent = nullptr); - void setTimeline(const QList &_replayTimeline); + void setTimeline(const QList &replayTimeline); [[nodiscard]] QSize sizeHint() const override; [[nodiscard]] QSize minimumSizeHint() const override; - void setTimeScaleFactor(qreal _timeScaleFactor); - [[nodiscard]] int getCurrentEvent() const - { - return currentEvent; - } + public slots: - void startReplay(); - void stopReplay(); - void skipByAmount(int amount); // use a negative amount to skip backwards + void setCurrentTime(int time); protected: void paintEvent(QPaintEvent *event) override; diff --git a/cockatrice/src/interface/widgets/replay/replay_widget.cpp b/cockatrice/src/interface/widgets/replay/replay_widget.cpp index 4dc7b1380..f5768a7aa 100644 --- a/cockatrice/src/interface/widgets/replay/replay_widget.cpp +++ b/cockatrice/src/interface/widgets/replay/replay_widget.cpp @@ -1,70 +1,50 @@ #include "replay_widget.h" +#include "../../../client/settings/cache_settings.h" #include "../../../client/settings/shortcuts_settings.h" #include "../interface/widgets/tabs/tab_game.h" +#include "replay_manager.h" #include "replay_quick_settings_widget.h" #include #include -ReplayWidget::ReplayWidget(TabGame *parent, GameReplay *_replay) - : QWidget(parent), game(parent), replay(_replay), replayPlayButton(nullptr), replayFastForwardButton(nullptr), - aReplaySkipForward(nullptr), aReplaySkipBackward(nullptr), aReplaySkipForwardBig(nullptr), - aReplaySkipBackwardBig(nullptr) +ReplayWidget::ReplayWidget(QWidget *parent, GameReplay *replay) + : QWidget(parent), replayPlayButton(nullptr), replayFastForwardButton(nullptr), aReplaySkipForward(nullptr), + aReplaySkipBackward(nullptr), aReplaySkipForwardBig(nullptr), aReplaySkipBackwardBig(nullptr) { - if (replay) { - game->getGame()->loadReplay(replay); - - // Create list: event number -> time [ms] - // Distribute simultaneous events evenly across 1 second. - unsigned int lastEventTimestamp = 0; - const int eventCount = replay->event_list_size(); - for (int i = 0; i < eventCount; ++i) { - int j = i + 1; - while ((j < eventCount) && (replay->event_list(j).seconds_elapsed() == lastEventTimestamp)) { - ++j; - } - - const int numberEventsThisSecond = j - i; - for (int k = 0; k < numberEventsThisSecond; ++k) { - replayTimeline.append(replay->event_list(i + k).seconds_elapsed() * 1000 + - (int)((qreal)k / (qreal)numberEventsThisSecond * 1000)); - } - - if (j < eventCount) { - lastEventTimestamp = replay->event_list(j).seconds_elapsed(); - } - i += numberEventsThisSecond - 1; - } - } + // replay manager + replayManager = new ReplayManager(this, replay); + connect(replayManager, &ReplayManager::eventReplayed, this, &ReplayWidget::eventReplayed); + connect(replayManager, &ReplayManager::replayFinished, this, &ReplayWidget::replayFinished); + connect(replayManager, &ReplayManager::rewound, this, &ReplayWidget::rewound); // timeline widget timelineWidget = new ReplayTimelineWidget; - timelineWidget->setTimeline(replayTimeline); - connect(timelineWidget, &ReplayTimelineWidget::processNextEvent, this, &ReplayWidget::replayNextEvent); - connect(timelineWidget, &ReplayTimelineWidget::replayFinished, this, &ReplayWidget::replayFinished); - connect(timelineWidget, &ReplayTimelineWidget::rewound, this, &ReplayWidget::replayRewind); + timelineWidget->setTimeline(replayManager->getReplayTimeline()); + connect(replayManager, &ReplayManager::timeChanged, timelineWidget, &ReplayTimelineWidget::setCurrentTime); + connect(timelineWidget, &ReplayTimelineWidget::timeClicked, replayManager, &ReplayManager::setTime); // timeline skip shortcuts aReplaySkipForward = new QAction(timelineWidget); timelineWidget->addAction(aReplaySkipForward); connect(aReplaySkipForward, &QAction::triggered, this, - [this] { timelineWidget->skipByAmount(ReplayTimelineWidget::SMALL_SKIP_MS); }); + [this] { replayManager->skipByAmount(ReplayManager::SMALL_SKIP_MS); }); aReplaySkipBackward = new QAction(timelineWidget); timelineWidget->addAction(aReplaySkipBackward); connect(aReplaySkipBackward, &QAction::triggered, this, - [this] { timelineWidget->skipByAmount(-ReplayTimelineWidget::SMALL_SKIP_MS); }); + [this] { replayManager->skipByAmount(-ReplayManager::SMALL_SKIP_MS); }); aReplaySkipForwardBig = new QAction(timelineWidget); timelineWidget->addAction(aReplaySkipForwardBig); connect(aReplaySkipForwardBig, &QAction::triggered, this, - [this] { timelineWidget->skipByAmount(ReplayTimelineWidget::BIG_SKIP_MS); }); + [this] { replayManager->skipByAmount(ReplayManager::BIG_SKIP_MS); }); aReplaySkipBackwardBig = new QAction(timelineWidget); timelineWidget->addAction(aReplaySkipBackwardBig); connect(aReplaySkipBackwardBig, &QAction::triggered, this, - [this] { timelineWidget->skipByAmount(-ReplayTimelineWidget::BIG_SKIP_MS); }); + [this] { replayManager->skipByAmount(-ReplayManager::BIG_SKIP_MS); }); // buttons replayPlayButton = new QToolButton; @@ -97,18 +77,11 @@ ReplayWidget::ReplayWidget(TabGame *parent, GameReplay *_replay) setObjectName("replayControlWidget"); setLayout(replayControlLayout); - connect(this, &ReplayWidget::requestChatAndPhaseReset, game, &TabGame::resetChatAndPhase); - connect(&SettingsCache::instance().shortcuts(), &ShortcutsSettings::shortCutChanged, this, &ReplayWidget::refreshShortcuts); refreshShortcuts(); } -void ReplayWidget::replayNextEvent(EventProcessingOptions options) -{ - emit eventReplayed(replay->event_list(timelineWidget->getCurrentEvent()), options); -} - void ReplayWidget::replayFinished() { replayPlayButton->setChecked(false); @@ -117,24 +90,16 @@ void ReplayWidget::replayFinished() void ReplayWidget::replayPlayButtonToggled(bool checked) { if (checked) { // start replay - timelineWidget->startReplay(); + replayManager->startReplay(); } else { // pause replay - timelineWidget->stopReplay(); + replayManager->stopReplay(); } } void ReplayWidget::updateTimeScaleFactor(bool isFastForward) { qreal factor = isFastForward ? SettingsCache::instance().interface().getFastForwardSpeed() : 1.0; - timelineWidget->setTimeScaleFactor(factor); -} - -/** - * @brief Handles everything that needs to be reset when doing a replay rewind. - */ -void ReplayWidget::replayRewind() -{ - emit requestChatAndPhaseReset(); + replayManager->setTimeScaleFactor(factor); } void ReplayWidget::refreshShortcuts() diff --git a/cockatrice/src/interface/widgets/replay/replay_widget.h b/cockatrice/src/interface/widgets/replay/replay_widget.h index eca356e9e..6d2b7a043 100644 --- a/cockatrice/src/interface/widgets/replay/replay_widget.h +++ b/cockatrice/src/interface/widgets/replay/replay_widget.h @@ -14,11 +14,12 @@ #include #include +class ReplayManager; class ReplayQuickSettingsWidget; class TabGame; /** - * @brief The top-level that is put in the replay dock widget. + * @brief The top-level widget that is put in the replay dock widget. * Contains the replay timeline as well as the buttons. */ class ReplayWidget : public QWidget @@ -26,29 +27,28 @@ class ReplayWidget : public QWidget Q_OBJECT public: - ReplayWidget(TabGame *parent, GameReplay *replay); - TabGame *game; - GameReplay *replay; + /** + * @param parent The parent widget + * @param replay Cannot be null. Takes ownership of the replay. + */ + ReplayWidget(QWidget *parent, GameReplay *replay); signals: - void requestChatAndPhaseReset(); + void rewound(); void eventReplayed(const GameEventContainer &cont, EventProcessingOptions options); private: - // Replay related members - int currentReplayStep = 0; - QList replayTimeline; + ReplayManager *replayManager; + ReplayTimelineWidget *timelineWidget; QToolButton *replayPlayButton, *replayFastForwardButton; ReplayQuickSettingsWidget *settingsWidget; QAction *aReplaySkipForward, *aReplaySkipBackward, *aReplaySkipForwardBig, *aReplaySkipBackwardBig; private slots: - void replayNextEvent(EventProcessingOptions options); void replayFinished(); void replayPlayButtonToggled(bool checked); void updateTimeScaleFactor(bool checked); - void replayRewind(); void refreshShortcuts(); }; diff --git a/cockatrice/src/interface/widgets/tabs/tab_game.cpp b/cockatrice/src/interface/widgets/tabs/tab_game.cpp index c9ebf06c8..d3f3a1735 100644 --- a/cockatrice/src/interface/widgets/tabs/tab_game.cpp +++ b/cockatrice/src/interface/widgets/tabs/tab_game.cpp @@ -265,9 +265,6 @@ void TabGame::emitUserEvent() TabGame::~TabGame() { - if (replayWidget) { - delete replayWidget->replay; - } for (auto &player : game->getPlayerManager()->getPlayers()) { player->clear(); } @@ -1183,6 +1180,7 @@ void TabGame::createReplayDock(GameReplay *replay) replayDock->setWidget(replayWidget); replayDock->setFloating(false); + connect(replayWidget, &ReplayWidget::rewound, this, &TabGame::resetChatAndPhase); connect(replayWidget, &ReplayWidget::eventReplayed, game->getGameEventHandler(), [this](const auto &event, auto options) { game->getGameEventHandler()->processGameEventContainer(event, nullptr, options);