#pragma once #include #include #include #include #include "ControlAction.h" #include "FloatingPanel.h" class GameWorldView; class QLabel; class QScrollArea; 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, public FloatingPanel { 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); // Places the panel in the bottom-left corner of the game world view. It shares the // bottom edge with the build button bar rather than clearing its strip, the bar // being centered and sized to its buttons and so usually leaving the left free; it // rises above the bar only when the two would otherwise overlap // (REQ-UI-CONTROLS-PANEL, REQ-UI-BUILD-BAR). The bar is the only thing placed before // it, so it is the only rect it ever has to rise above. void placeIn(const QRect& viewRect, const std::vector& occupiedRects) override; 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; // Asks for the placement pass to be re-run, the panel's own size having changed. void invalidateLayout(); const GameWorldView* m_view; QLabel* m_heading; // Scrolls the rows once they outgrow the space the panel has (REQ-UI-CONTROLS-PANEL). // The heading is deliberately outside it, so it stays put and stays clickable. QScrollArea* m_scrollArea; 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; };