[Replay] Show the history sidebar in replay tabs

Add the Game History dock to the left of replay tabs, wire it to the replay timeline, and register it with the View menu and layout reset.
This commit is contained in:
Lukas Brübach 2026-10-20 22:15:15 +02:00 • committed by BruebachL
parent 11f0dfac2d
commit a2dd8551f2
5 changed files with 61 additions and 2 deletions

View file

@ -66,6 +66,16 @@ public:
return replayTimeline; return replayTimeline;
} }
/**
* @brief Index of the last event the game state has processed.
*
* @return The event index, or -1 while no event has been replayed yet
*/
[[nodiscard]] int getLastProcessedEventIndex() const
{
return currentEvent - 1;
}
void setTimeScaleFactor(qreal _timeScaleFactor); void setTimeScaleFactor(qreal _timeScaleFactor);
void setSkipEmptySections(bool value); void setSkipEmptySections(bool value);

View file

@ -19,6 +19,11 @@
class GameReplay; class GameReplay;
int ReplayWidget::getLastProcessedEventIndex() const
{
return replayManager->getLastProcessedEventIndex();
}
ReplayWidget::ReplayWidget(QWidget *parent, GameReplay *replay) ReplayWidget::ReplayWidget(QWidget *parent, GameReplay *replay)
: QWidget(parent), replayPlayButton(nullptr), replayFastForwardButton(nullptr), aReplaySkipForward(nullptr), : QWidget(parent), replayPlayButton(nullptr), replayFastForwardButton(nullptr), aReplaySkipForward(nullptr),
aReplaySkipBackward(nullptr), aReplaySkipForwardBig(nullptr), aReplaySkipBackwardBig(nullptr) aReplaySkipBackward(nullptr), aReplaySkipForwardBig(nullptr), aReplaySkipBackwardBig(nullptr)

View file

@ -38,6 +38,11 @@ public:
[[nodiscard]] const QList<int> &getReplayTimeline() const; [[nodiscard]] const QList<int> &getReplayTimeline() const;
/**
* @brief Index of the last replay event the game state has processed, or -1 before the first event.
*/
[[nodiscard]] int getLastProcessedEventIndex() const;
public slots: public slots:
void seekToEvent(int eventIndex); void seekToEvent(int eventIndex);

View file

@ -26,6 +26,7 @@
#include "../interface/widgets/utility/line_edit_completer.h" #include "../interface/widgets/utility/line_edit_completer.h"
#include "../main.h" #include "../main.h"
#include "../menus/tearoff_menu.h" #include "../menus/tearoff_menu.h"
#include "../replay/replay_event_sidebar.h"
#include "../replay/replay_widget.h" #include "../replay/replay_widget.h"
#include "../utility/visibility_change_listener.h" #include "../utility/visibility_change_listener.h"
#include "card/card_completer_proxy_model.h" #include "card/card_completer_proxy_model.h"
@ -97,7 +98,7 @@ class Message;
} // namespace google } // namespace google
TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay) TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay)
: Tab(_tabSupervisor), sayLabel(nullptr), sayEdit(nullptr) : Tab(_tabSupervisor), sayLabel(nullptr), sayEdit(nullptr), replayDock(nullptr), historyDock(nullptr)
{ {
// THIS CTOR IS USED ON REPLAY // THIS CTOR IS USED ON REPLAY
game = new Replay(this, _replay, tabSupervisor->getIsLocalGame(), &SettingsCache::instance().userInterface(), game = new Replay(this, _replay, tabSupervisor->getIsLocalGame(), &SettingsCache::instance().userInterface(),
@ -113,6 +114,7 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor, GameReplay *_replay)
addDockWidget(Qt::RightDockWidgetArea, cardInfoDock); addDockWidget(Qt::RightDockWidgetArea, cardInfoDock);
addDockWidget(Qt::RightDockWidgetArea, playerListDock); addDockWidget(Qt::RightDockWidgetArea, playerListDock);
addDockWidget(Qt::RightDockWidgetArea, messageLayoutDock); addDockWidget(Qt::RightDockWidgetArea, messageLayoutDock);
addDockWidget(Qt::LeftDockWidgetArea, historyDock);
addDockWidget(Qt::BottomDockWidgetArea, replayDock); addDockWidget(Qt::BottomDockWidgetArea, replayDock);
mainWidget = new QStackedWidget(this); mainWidget = new QStackedWidget(this);
@ -154,6 +156,7 @@ TabGame::TabGame(TabSupervisor *_tabSupervisor,
createPlayAreaWidget(); createPlayAreaWidget();
createDeckViewContainerWidget(); createDeckViewContainerWidget();
replayDock = nullptr; replayDock = nullptr;
historyDock = nullptr;
addDockWidget(Qt::RightDockWidgetArea, cardInfoDock); addDockWidget(Qt::RightDockWidgetArea, cardInfoDock);
addDockWidget(Qt::RightDockWidgetArea, playerListDock); addDockWidget(Qt::RightDockWidgetArea, playerListDock);
@ -345,6 +348,9 @@ void TabGame::retranslateUi()
} }
cardInfoDock->setWindowTitle(tr("Card Info") + (cardInfoDock->isWindow() ? tabText : QString())); cardInfoDock->setWindowTitle(tr("Card Info") + (cardInfoDock->isWindow() ? tabText : QString()));
messageLayoutDock->setWindowTitle(tr("Messages") + (messageLayoutDock->isWindow() ? tabText : QString())); messageLayoutDock->setWindowTitle(tr("Messages") + (messageLayoutDock->isWindow() ? tabText : QString()));
if (historyDock) {
historyDock->setWindowTitle(tr("Game History") + (historyDock->isWindow() ? tabText : QString()));
}
if (replayDock) { if (replayDock) {
replayDock->setWindowTitle(tr("Replay Timeline") + (replayDock->isWindow() ? tabText : QString())); replayDock->setWindowTitle(tr("Replay Timeline") + (replayDock->isWindow() ? tabText : QString()));
} }
@ -418,6 +424,9 @@ void TabGame::retranslateUi()
dockToActions[messageLayoutDock].menu->setTitle(tr("Messages")); dockToActions[messageLayoutDock].menu->setTitle(tr("Messages"));
dockToActions[playerListDock].menu->setTitle(tr("Player List")); dockToActions[playerListDock].menu->setTitle(tr("Player List"));
if (historyDock) {
dockToActions[historyDock].menu->setTitle(tr("Game History"));
}
if (replayDock) { if (replayDock) {
dockToActions[replayDock].menu->setTitle(tr("Replay Timeline")); dockToActions[replayDock].menu->setTitle(tr("Replay Timeline"));
} }
@ -1192,6 +1201,10 @@ void TabGame::createViewMenuItems()
registerDockWidget(viewMenu, messageLayoutDock, {250, 200}); registerDockWidget(viewMenu, messageLayoutDock, {250, 200});
registerDockWidget(viewMenu, playerListDock, {250, 50}); registerDockWidget(viewMenu, playerListDock, {250, 50});
if (historyDock) {
registerDockWidget(viewMenu, historyDock, {280, 400});
}
if (replayDock) { if (replayDock) {
registerDockWidget(viewMenu, replayDock, {900, 100}); registerDockWidget(viewMenu, replayDock, {900, 100});
} }
@ -1261,6 +1274,12 @@ void TabGame::actResetLayout()
addDockWidget(Qt::RightDockWidgetArea, playerListDock); addDockWidget(Qt::RightDockWidgetArea, playerListDock);
addDockWidget(Qt::RightDockWidgetArea, messageLayoutDock); addDockWidget(Qt::RightDockWidgetArea, messageLayoutDock);
if (historyDock) {
historyDock->setVisible(true);
historyDock->setFloating(false);
addDockWidget(Qt::LeftDockWidgetArea, historyDock);
}
if (replayDock) { if (replayDock) {
replayDock->setVisible(true); replayDock->setVisible(true);
replayDock->setFloating(false); replayDock->setFloating(false);
@ -1269,6 +1288,9 @@ void TabGame::actResetLayout()
cardInfoDock->resize(250, 360); cardInfoDock->resize(250, 360);
messageLayoutDock->resize(250, 200); messageLayoutDock->resize(250, 200);
playerListDock->resize(250, 50); playerListDock->resize(250, 50);
if (historyDock) {
historyDock->resize(280, 400);
}
replayDock->resize(900, 100); replayDock->resize(900, 100);
} else { } else {
cardInfoDock->resize(250, 360); cardInfoDock->resize(250, 360);
@ -1322,6 +1344,22 @@ void TabGame::createReplayDock(GameReplay *replay)
[this](const auto &event, auto options) { [this](const auto &event, auto options) {
game->getGameEventHandler()->processGameEventContainer(event, nullptr, options); game->getGameEventHandler()->processGameEventContainer(event, nullptr, options);
}); });
auto *historySidebar = new ReplayEventSidebar(nullptr, replay, replayWidget->getReplayTimeline());
historyDock = new QDockWidget(this);
historyDock->setObjectName("historyDock");
historyDock->setFeatures(QDockWidget::DockWidgetClosable | QDockWidget::DockWidgetFloatable |
QDockWidget::DockWidgetMovable);
historyDock->setWidget(historySidebar);
historyDock->setFloating(false);
connect(historySidebar, &ReplayEventSidebar::eventSelected, replayWidget, &ReplayWidget::seekToEvent);
// The sidebar highlights events rather than times, so ask the manager which event the game state has reached.
connect(replayWidget, &ReplayWidget::timeChanged, historySidebar,
[this, historySidebar] { historySidebar->setCurrentEvent(replayWidget->getLastProcessedEventIndex()); });
// No event has been replayed yet when the sidebar is connected, so apply the initial state explicitly.
historySidebar->setCurrentEvent(replayWidget->getLastProcessedEventIndex());
} }
void TabGame::createDeckViewContainerWidget(bool bReplay) void TabGame::createDeckViewContainerWidget(bool bReplay)

View file

@ -50,6 +50,7 @@ class AbstractClient;
class GameView; class GameView;
class GameScene; class GameScene;
class ReplayWidget; class ReplayWidget;
class ReplayEventSidebar;
class CardInfoFrameWidget; class CardInfoFrameWidget;
class QLabel; class QLabel;
class QPushButton; class QPushButton;
@ -89,7 +90,7 @@ private:
QMap<int, TabbedDeckViewContainer *> deckViewContainers; QMap<int, TabbedDeckViewContainer *> deckViewContainers;
QVBoxLayout *deckViewContainerLayout; QVBoxLayout *deckViewContainerLayout;
QWidget *gamePlayAreaWidget, *deckViewContainerWidget; QWidget *gamePlayAreaWidget, *deckViewContainerWidget;
QDockWidget *cardInfoDock, *messageLayoutDock, *playerListDock, *replayDock; QDockWidget *cardInfoDock, *messageLayoutDock, *playerListDock, *replayDock, *historyDock;
QAction *playersSeparator; QAction *playersSeparator;
QMenu *gameMenu, *viewMenu; QMenu *gameMenu, *viewMenu;
TearOffMenu *phasesMenu; TearOffMenu *phasesMenu;