Files
dota_factory/src/ui/SelectionPanel.h

134 lines
6.3 KiB
C++

#pragma once
#include <QPoint>
#include <QRect>
#include <QWidget>
#include <optional>
#include <vector>
#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<TickAdvancedEvent,
PlayerCommandsAppliedEvent,
EntitySelectionChangedEvent,
SelectionChangedEvent,
SelectionAnchorChangedEvent,
DebrisSelectionChangedEvent,
DebugDrawToggledEvent>
{
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<QRect>& 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<const SelectionChangedEvent> event) override;
void handleEvent(std::shared_ptr<const SelectionAnchorChangedEvent> event) override;
void handleEvent(std::shared_ptr<const EntitySelectionChangedEvent> event) override;
void handleEvent(std::shared_ptr<const DebrisSelectionChangedEvent> event) override;
void handleEvent(std::shared_ptr<const TickAdvancedEvent> event) override;
void handleEvent(std::shared_ptr<const PlayerCommandsAppliedEvent> event) override;
void handleEvent(std::shared_ptr<const DebugDrawToggledEvent> 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<PanelSide> 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<QPoint> m_desiredTopLeftPx;
std::optional<QPoint> 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;
};