Every dialog was an OS window with a dim widget behind it in the game window. That is why a click beside one could not reach it: Qt drops mouse events for a window a modal blocks, so the dim -- a child of the blocked window -- never saw the press. ModalLayer is that dim grown up: still a child of the main window covering its rect, but it now hosts the open modal, places it, and runs its event loop, so it is the widget the clicks beside a modal land on. It keeps a stack, paints one dim however many modals are open, and a hold keeps it up while one modal hands over to the next. ModalDimOverlay and ModalDimScope are gone; ModalLayerHold replaces the scope at the sites that still open a system message box. ModalDialog is what a dialog inherits in place of the window it lost: the panel background, the drawn header with its optional close button, and the dismissal gestures. isDismissible() governs Q and, next, the click outside -- one predicate because both gestures reach the same dialogs. Its default refuses, so a dialog opts in. requestDismiss() is what a gesture asks for, and ShipLayoutDialog overrides it with the one-step ladder its Q handler used to spell out, which is now reached by both. DialogDismiss.h folded into the base. The four dialogs the player meets keep their contents unchanged and lose their title bars: the recipe/schematic selection, blueprint selection, ship layout, and schematic choice dialogs. Placement moved with them -- placeOnSelectionPanel became getSelectionPanelAnchor, and the layer centers on that rectangle in window coordinates, with no global mapping and no frame height to guess at. Two things followed from dropping OS modality: the focus guard that hands focus back to the game world now also asks the layer whether a modal is open, and closeEvent refuses to close the window while one is, since its nested loop runs over widgets the window owns. The escape menu, the game-over and win screens, and the two name dialogs are still system dialogs; they are dimmed by a layer hold until they are converted next. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01Ne3mejABZoLWKLh8fgpM3x
110 lines
3.2 KiB
C++
110 lines
3.2 KiB
C++
#pragma once
|
|
|
|
#include <map>
|
|
#include <optional>
|
|
#include <set>
|
|
#include <string>
|
|
#include <vector>
|
|
|
|
#include <QPoint>
|
|
|
|
#include "GameConfig.h"
|
|
#include "ModalDialog.h"
|
|
#include "Rotation.h"
|
|
#include "ShipLayout.h"
|
|
#include "ShipLayoutBlueprint.h"
|
|
|
|
class ItemIconCache;
|
|
class QPushButton;
|
|
class RecipeLineRow;
|
|
class SectionBox;
|
|
class ShipStatsPanel;
|
|
|
|
class ShipLayoutDialog : public ModalDialog
|
|
{
|
|
Q_OBJECT
|
|
|
|
public:
|
|
// itemIcons draws what each module costs and what the layout costs in total
|
|
// (REQ-MOD-UI-DIALOG, REQ-MOD-UI-STATS-PANEL). Not owned.
|
|
ShipLayoutDialog(const GameConfig* config,
|
|
const std::string& shipId,
|
|
const ShipLayoutConfig& currentLayout,
|
|
std::vector<ShipLayoutBlueprint>& allBlueprints,
|
|
std::set<std::string> unlockedModuleIds,
|
|
bool debugDraw,
|
|
ItemIconCache* itemIcons,
|
|
QWidget* parent = nullptr);
|
|
|
|
std::optional<ShipLayoutConfig> getResult() const;
|
|
|
|
bool isDismissible() const override;
|
|
|
|
public slots:
|
|
// Backs out one level per dismissal, as Q does in the game world: the module being
|
|
// placed, then remove mode, then the dialog itself, which discards the session
|
|
// (REQ-UI-DIALOG-DISMISS). So a single press or click never both leaves a mode and
|
|
// throws the session away.
|
|
void requestDismiss() override;
|
|
|
|
protected:
|
|
void keyPressEvent(QKeyEvent* event) override;
|
|
|
|
signals:
|
|
void gridCellClicked(QPoint cell);
|
|
|
|
private slots:
|
|
void onModuleButtonClicked(int index);
|
|
// Enters remove mode, or leaves it when it is already active (REQ-MOD-REMOVE).
|
|
// Reached by the Remove button and by a dismissal, which leaves the mode on its way
|
|
// out of the dialog (REQ-UI-DIALOG-DISMISS).
|
|
void onRemoveButtonClicked();
|
|
void onConfirm();
|
|
void onCancel();
|
|
|
|
public:
|
|
struct CellInfo
|
|
{
|
|
bool buildable;
|
|
std::optional<int> moduleIndex; // nullopt if empty
|
|
};
|
|
|
|
private:
|
|
|
|
void rebuildOccupancy();
|
|
void updateGridWidget();
|
|
void updateStats();
|
|
bool canPlaceModule(const ModuleDef& def, QPoint position, Rotation rotation) const;
|
|
std::vector<std::string> rotatedMask(const ModuleDef& def, Rotation rotation) const;
|
|
void loadLayoutBlueprint(const std::vector<PlacedModule>& modules);
|
|
|
|
const GameConfig* m_config;
|
|
ItemIconCache* m_itemIcons;
|
|
std::string m_shipId;
|
|
std::set<std::string> m_unlockedModuleIds;
|
|
std::vector<std::string> m_shipLayout;
|
|
int m_rows;
|
|
int m_cols;
|
|
|
|
std::vector<PlacedModule> m_placedModules;
|
|
std::vector<std::vector<CellInfo>> m_grid;
|
|
|
|
// The module to place, as an index into config modules; nullopt when no
|
|
// module is selected for placement. m_removeMode is a separate mode in which
|
|
// clicking a cell removes the module there (mutually exclusive with a
|
|
// selected module).
|
|
std::optional<int> m_activeModuleIndex;
|
|
bool m_removeMode = false;
|
|
Rotation m_currentRotation;
|
|
|
|
std::vector<QPushButton*> m_moduleButtons;
|
|
QPushButton* m_removeButton;
|
|
QWidget* m_gridWidget;
|
|
ShipStatsPanel* m_statsPanel;
|
|
SectionBox* m_buildCostSection;
|
|
RecipeLineRow* m_buildCostLine;
|
|
bool m_debugDraw;
|
|
|
|
std::optional<ShipLayoutConfig> m_result;
|
|
};
|