#pragma once #include #include #include #include #include "ControlAction.h" class GameWorldView; class QLabel; class QTimer; class QVBoxLayout; // Shows the controls available in the player's current situation // (REQ-UI-CONTROLS-PANEL). The panel decides nothing: which rows apply is // ControlAction.h's answer, the same one the key handling and the world view's mouse // dispatch act on, so what is shown and what happens cannot part company // (REQ-UI-CONTROLS-ACCURACY). // // It floats over the game world at the left edge, bottom-aligned within the band its // owner hands it, and collapses to its heading when the heading is clicked. // // Refreshed on a timer rather than by subscribing to events: two of the things that // change a row -- a belt drag starting, the ghost moving over a transfer target -- // happen on mouse movement and publish nothing, and they still have to be reflected // while the game is paused, so there is no tick to hang it on either. The rebuild is // skipped unless the resolved content actually differs, which is a vector of enums to // compare. class ControlsPanel : public QWidget { Q_OBJECT public: // Neither pointer is owned; both must outlive this widget. The view is the source // of the control context, being the widget that owns the build mode and the // selection. ControlsPanel(const GameWorldView* view, QWidget* parent = nullptr); // Confines the panel to the given band of the game world view: it left-aligns // within it and sits on its bottom edge. The band is the world view less the strip // the build button bar occupies, so the two never overlap and the bar never has to // move (REQ-UI-CONTROLS-PANEL, REQ-UI-BUILD-BAR). void anchorTo(const QRect& bandRect); protected: // Clicking the heading collapses and expands the panel (REQ-UI-CONTROLS-PANEL). void mousePressEvent(QMouseEvent* event) override; private: // Re-resolves the context and rebuilds only if the rows or the heading changed. void refresh(); // Replaces the rows with those of the current context. void rebuild(const ControlContext& context); // The heading's " * " text for the context, detail omitted when the // context has none. QString getHeadingText(const ControlContext& context) const; // Re-fits the panel to its content within the anchored band. void refit(); const GameWorldView* m_view; QLabel* m_heading; QWidget* m_rows; QVBoxLayout* m_rowsLayout; QTimer* m_refreshTimer; // What is currently drawn, so a refresh that resolves to the same thing does // nothing. The always-available block is kept separately because the divider // between the two is part of what is drawn. QString m_shownHeading; std::vector m_shownContextActions; std::vector m_shownAlwaysActions; // Survives context changes and simulation restarts; presentation only, never a // command (REQ-UI-CONTROLS-PANEL). bool m_collapsed = false; // The band the panel confines itself to, in the coordinates of its parent; null // until the owner has anchored it for the first time. QRect m_bandRect; };