float the selection panel over the game world instead of a side column
Implements REQ-UI-SELECTION-PANEL and the full-width REQ-UI-WORLD-SIZE / REQ-UI-HEADER: MainWindow drops the 25% side column and its 75/25 math, so the header bar and world view span the window, and the panel joins the build button bar as a widget floating over the world. The panel follows the bar's pattern -- an opaque sibling built after the world view, so it sits above the vignettes, below the dim overlay, and swallows the mouse events that would otherwise reach the world. It sizes itself to its content within a band the window hands it (the world view less the bar's strip, so the bar never has to move for it), right-aligned and centered in that band, and scrolls once the content outgrows it. Its content moved into a scroll area for that; the width is capped at 320 px because the wrapped labels and the splitter filter lists have no natural width of their own. With nothing selected the panel now hides entirely rather than showing an empty box (REQ-UI-EMPTY-SELECTION). Two defects that content sizing exposed: buildEmpty() left the selected ids behind when the building vanished under the panel, which would have held an empty panel on screen, and buildMulti() let a shipyard's layout preview survive from a previous single selection (REQ-UI-MULTI-SELECTION). Renames SelectedBuildingPanel to SelectionPanel throughout, matching the requirements: the panel has long shown ships, stations, and debris too. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
This commit is contained in:
151
src/ui/SelectionPanel.h
Normal file
151
src/ui/SelectionPanel.h
Normal file
@@ -0,0 +1,151 @@
|
||||
#pragma once
|
||||
|
||||
#include <optional>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
|
||||
#include <QPoint>
|
||||
#include <QRect>
|
||||
#include <QWidget>
|
||||
|
||||
#include "BeltSystem.h"
|
||||
#include "Building.h"
|
||||
#include "BuildingId.h"
|
||||
#include "EntitySelectionChangedEvent.h"
|
||||
#include "EventHandler.h"
|
||||
#include "GameConfig.h"
|
||||
#include "PlayerCommandsAppliedEvent.h"
|
||||
#include "RecipesConfig.h"
|
||||
#include "DebrisSelectionChangedEvent.h"
|
||||
#include "SelectionChangedEvent.h"
|
||||
#include "ShipLayout.h"
|
||||
#include "ShipsConfig.h"
|
||||
#include "Tick.h"
|
||||
#include "TickAdvancedEvent.h"
|
||||
|
||||
class Simulation;
|
||||
class FieldSelectionPanel;
|
||||
class ShipLayoutPreview;
|
||||
class QLabel;
|
||||
class QListWidget;
|
||||
class QPushButton;
|
||||
class QScrollArea;
|
||||
class QVBoxLayout;
|
||||
|
||||
// Shows the current selection. The building category (buildings and construction sites)
|
||||
// is rendered by this panel itself; the field category (ships, defence stations, debris)
|
||||
// is rendered by the embedded FieldSelectionPanel.
|
||||
//
|
||||
// The two categories are mutually exclusive (REQ-UI-SELECTION-CATEGORIES) and this panel
|
||||
// is the sole arbiter of which one owns the content: it listens to all three selection
|
||||
// events, forwards the field ones to the child panel, and drops the losing category's
|
||||
// content. Neither panel touches the other's widgets.
|
||||
//
|
||||
// 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 content, anchors to the right edge of
|
||||
// the band its owner hands it, and hides itself entirely while nothing is selected
|
||||
// (REQ-UI-EMPTY-SELECTION).
|
||||
class SelectionPanel : public QWidget,
|
||||
public CombinedEventHandler<TickAdvancedEvent,
|
||||
PlayerCommandsAppliedEvent,
|
||||
EntitySelectionChangedEvent,
|
||||
SelectionChangedEvent,
|
||||
DebrisSelectionChangedEvent>
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
SelectionPanel(Simulation* sim, const GameConfig* config,
|
||||
QWidget* parent = nullptr);
|
||||
~SelectionPanel() override;
|
||||
|
||||
// Confines the panel to the given band of the game world view: it right-aligns
|
||||
// within it and centers vertically in it. 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-SELECTION-PANEL, REQ-UI-BUILD-BAR).
|
||||
void anchorTo(const QRect& bandRect);
|
||||
|
||||
private:
|
||||
void handleEvent(std::shared_ptr<const TickAdvancedEvent> event) override;
|
||||
void handleEvent(std::shared_ptr<const PlayerCommandsAppliedEvent> event) override;
|
||||
void handleEvent(std::shared_ptr<const EntitySelectionChangedEvent> event) override;
|
||||
void handleEvent(std::shared_ptr<const SelectionChangedEvent> event) override;
|
||||
void handleEvent(std::shared_ptr<const DebrisSelectionChangedEvent> event) override;
|
||||
|
||||
private slots:
|
||||
void onSelectRecipeClicked();
|
||||
void onClearBelt();
|
||||
void onSplitterFilterChanged();
|
||||
|
||||
private:
|
||||
// Why the selection display is being refreshed. A periodic tick only needs a
|
||||
// lightweight content update (e.g. a construction site's progress label),
|
||||
// whereas an applied player command may have changed the configuration and
|
||||
// needs a full structural rebuild.
|
||||
enum class RefreshReason
|
||||
{
|
||||
PeriodicTick,
|
||||
CommandApplied
|
||||
};
|
||||
|
||||
void onSelectionChanged(const std::vector<BuildingId>& ids);
|
||||
// Gives the panel to the field category once it has anything selected.
|
||||
void yieldToFieldSelection();
|
||||
// Shows the panel while either category has a selection and hides it otherwise
|
||||
// (REQ-UI-EMPTY-SELECTION), re-fitting it to its current content while it is shown.
|
||||
// Every path that changes the content ends here, because the content is what sizes
|
||||
// the panel.
|
||||
void updateVisibility();
|
||||
// Re-fits the panel to its content within the anchored band.
|
||||
void refit();
|
||||
void refreshSelectionDisplay(RefreshReason reason);
|
||||
void rebuild();
|
||||
void hideAllWidgets();
|
||||
void buildEmpty();
|
||||
void buildSingle(BuildingId id);
|
||||
void buildMulti(const std::vector<BuildingId>& ids);
|
||||
void refreshBuffers(const Building* b);
|
||||
void refreshSiteProgress(const ConstructionSite* s);
|
||||
void updateShipyardLayoutWidgets(BuildingType type,
|
||||
const std::string& recipeId,
|
||||
const std::optional<ShipLayoutConfig>& shipLayout);
|
||||
void buildSplitterFilters(const std::optional<BeltSystem::SplitterInfo>& info);
|
||||
const RecipeDef* findRecipe(const Building* b) const;
|
||||
const ShipDef* findShipDef(const std::string& id) const;
|
||||
std::vector<std::string> getAllItemIds() const;
|
||||
|
||||
Simulation* m_sim;
|
||||
const GameConfig* m_config;
|
||||
std::vector<BuildingId> m_selectedBuildingIds;
|
||||
|
||||
// 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;
|
||||
|
||||
// Scrolls the content once it outgrows the band (REQ-UI-SELECTION-PANEL). All the
|
||||
// content widgets below are children of m_content, not of the panel itself.
|
||||
QScrollArea* m_scrollArea;
|
||||
QWidget* m_content;
|
||||
|
||||
QVBoxLayout* m_layout;
|
||||
QLabel* m_titleLabel;
|
||||
QPushButton* m_recipeSelectButton;
|
||||
QPushButton* m_clearBeltBtn;
|
||||
QLabel* m_filterALabel;
|
||||
QListWidget* m_filterAList;
|
||||
QLabel* m_filterBLabel;
|
||||
QListWidget* m_filterBList;
|
||||
QLabel* m_buffersLabel;
|
||||
|
||||
ShipLayoutPreview* m_layoutPreview;
|
||||
QPushButton* m_configureLayoutBtn;
|
||||
|
||||
std::optional<BuildingId> m_singleBuildingId;
|
||||
bool m_singleIsSite = false; // selected single entity is a construction site
|
||||
QPoint m_splitterTile;
|
||||
std::string m_currentRecipeId;
|
||||
|
||||
// Renders the field selection (actors + debris) below the building content
|
||||
// (REQ-UI-FIELD-MULTI-SELECTION). Hides itself while nothing field-side is selected.
|
||||
FieldSelectionPanel* m_fieldSelectionPanel;
|
||||
};
|
||||
Reference in New Issue
Block a user