#pragma once #include #include #include #include #include #include "DebrisSelectionChangedEvent.h" #include "DebugDrawToggledEvent.h" #include "EntitySelectionChangedEvent.h" #include "EventHandler.h" #include "FloatingPanel.h" #include "FloatingPanelPlacement.h" #include "PlayerCommandsAppliedEvent.h" #include "SelectionAnchorChangedEvent.h" #include "SelectionChangedEvent.h" #include "TickAdvancedEvent.h" #include "selection/SelectionContentFactory.h" #include "selection/SelectionContext.h" struct GameConfig; struct VisualsConfig; class BuildingIconCache; class ItemIconCache; class SelectionContent; class Simulation; class QMouseEvent; class QScrollArea; class QVBoxLayout; // Shows the current selection. The panel itself renders nothing: it arbitrates between // the two selection categories (REQ-UI-SELECTION-CATEGORIES), picks the card that fits // what is selected (REQ-UI-SELECTION-CONTENT), and hosts exactly one of them at a time. // What each card looks like lives in src/ui/selection/. // // The panel floats over the game world view rather than occupying a column of its own // (REQ-UI-SELECTION-PANEL): it sizes itself to its card, places itself in what the build // button bar and the controls panel have left free, and hides itself entirely while // nothing is selected (REQ-UI-EMPTY-SELECTION). class SelectionPanel : public QWidget, public FloatingPanel, public CombinedEventHandler { Q_OBJECT public: // visuals, itemIcons and buildingIcons are window-wide rendering resources the cards // draw from; none is owned and all must outlive this widget. SelectionPanel(Simulation* sim, const GameConfig* config, const VisualsConfig* visuals, ItemIconCache* itemIcons, BuildingIconCache* buildingIcons, QWidget* parent = nullptr); ~SelectionPanel() override; // Sizes the panel to its card and places it beside the selection it describes, in // what the widgets placed before it have left free. Keeping clear of them is entirely // this panel's job; neither of them ever moves for it (REQ-UI-SELECTION-PANEL, // REQ-UI-BUILD-BAR, REQ-UI-CONTROLS-PANEL). void placeIn(const QRect& viewRect, const std::vector& occupiedRects) override; protected: // The panel is moved by dragging its card header (REQ-UI-SELECTION-PANEL-DRAG). Every // other mouse event over the panel is swallowed here so that none of them reaches the // game world beneath (REQ-UI-SELECTION-PANEL). void mousePressEvent(QMouseEvent* event) override; void mouseMoveEvent(QMouseEvent* event) override; void mouseReleaseEvent(QMouseEvent* event) override; private: void handleEvent(std::shared_ptr event) override; void handleEvent(std::shared_ptr event) override; void handleEvent(std::shared_ptr event) override; void handleEvent(std::shared_ptr event) override; void handleEvent(std::shared_ptr event) override; void handleEvent(std::shared_ptr event) override; void handleEvent(std::shared_ptr event) override; // Re-reads the live values of the card on screen. When the selection now calls for a // different card -- a construction site finishing is the case that matters -- it // rebuilds instead, because a card never changes its own shape. void refreshContent(); // Replaces the card with the one the current selection calls for. void rebuildContent(); // Asks for the placement pass to be re-run, the card having changed size or the // panel having gained or lost its reason to be shown at all. void invalidateLayout(); SelectionContext m_context; // Read through m_context by the cards that need it, so a toggle reaches the card on // screen without it having to subscribe to the event itself. bool m_debugDrawEnabled = false; SelectionRequest m_request; ContentKey m_contentKey; SelectionContent* m_content = nullptr; // Where the current selection was on the screen when it started, in the game world // view's coordinates, and which side of it the panel took. Both are frozen for as // long as the selection lasts: the anchor because the panel does not chase a // scrolling view or a moving ship, the side because a card that grows must not flip // the panel across the object (REQ-UI-SELECTION-PANEL). The side is resolved on the // first placement after a new anchor, being the first point at which the panel's // width is known. Dragging the panel supersedes the pair for the rest of the // selection (REQ-UI-SELECTION-PANEL-DRAG). QRect m_anchorRect; std::optional m_side; // Where the player dragged the panel, in the game world view's coordinates, and the // cursor's offset within the panel while a drag is running // (REQ-UI-SELECTION-PANEL-DRAG). The desired position is kept exactly as dropped: // resolving it against the view's edges and the widgets the panel steps around never // writes back to it, so the panel returns to it once the room is there again. It // lasts as long as the selection it was set in. std::optional m_desiredTopLeftPx; std::optional m_dragGrabOffsetPx; // Origin of the view the panel was last placed in, which is what the two coordinate // systems differ by -- the panel is a sibling of the view, not a child of it. QPoint m_viewOriginPx; // Scrolls the card once it outgrows the space the panel has (REQ-UI-SELECTION-PANEL). // The card is a child of m_body, not of the panel itself. QScrollArea* m_scrollArea; QWidget* m_body; QVBoxLayout* m_bodyLayout; };