The two selection categories used to arbitrate ownership of the panel by poking each other's widgets: buildFieldSelection() called clearContent() and buildEmpty(), buildEmpty() hid the four entity widgets, and hideAllWidgets() hid the scrap label. Splitting the halves apart without naming an arbiter would only have spread that across a class boundary. SelectedBuildingPanel is now the sole arbiter. It still receives all three selection events, forwards the two field ones to the embedded FieldSelectionPanel, and drops its own selection and content as soon as the field panel reports a selection (yieldToFieldSelection), mirroring what onSelectionChanged() already did in the other direction. The field panel decides only what to render and whether it is visible at all. Dropping the field branch of refreshSelectionDisplay() is behaviour preserving: whenever the field category owns the panel, m_singleBuildingId is null, so the building refresh returns immediately anyway. clearContent() and buildEmpty() became identical once the cross-half hiding was gone, so only buildEmpty() remains. Co-Authored-By: Claude <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG
123 lines
4.6 KiB
C++
123 lines
4.6 KiB
C++
#pragma once
|
|
|
|
#include <optional>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <QPoint>
|
|
#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 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.
|
|
class SelectedBuildingPanel : public QWidget,
|
|
public CombinedEventHandler<TickAdvancedEvent,
|
|
PlayerCommandsAppliedEvent,
|
|
EntitySelectionChangedEvent,
|
|
SelectionChangedEvent,
|
|
DebrisSelectionChangedEvent>
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
SelectedBuildingPanel(Simulation* sim, const GameConfig* config,
|
|
QWidget* parent = nullptr);
|
|
~SelectedBuildingPanel() override;
|
|
|
|
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();
|
|
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;
|
|
|
|
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;
|
|
};
|