host the modals in the window instead of in windows of their own
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
This commit is contained in:
82
src/ui/ModalLayer.h
Normal file
82
src/ui/ModalLayer.h
Normal file
@@ -0,0 +1,82 @@
|
||||
#pragma once
|
||||
|
||||
#include <vector>
|
||||
|
||||
#include <QColor>
|
||||
#include <QRect>
|
||||
#include <QWidget>
|
||||
|
||||
class ModalDialog;
|
||||
class QPaintEvent;
|
||||
class QScrollArea;
|
||||
|
||||
// The one surface every modal is shown on (REQ-UI-MODAL-CHROME, REQ-UI-MODAL-DIM): a
|
||||
// child of the main window covering its whole rect, which paints the dim, hosts the
|
||||
// open modal, and runs its modal loop. Because it is a widget of the window rather than
|
||||
// a window of its own, it receives the clicks that land beside the modal -- which a
|
||||
// blocked window would never see -- and that is what makes the click dismissal possible
|
||||
// (REQ-UI-DIALOG-DISMISS).
|
||||
//
|
||||
// Modals nest: the layer keeps a stack, shows one dim for all of them, and hides itself
|
||||
// when the last one closes. It is shown while the stack is non-empty or a hold is taken.
|
||||
class ModalLayer : public QWidget
|
||||
{
|
||||
Q_OBJECT
|
||||
|
||||
public:
|
||||
ModalLayer(const QColor& dimColor, QWidget* parent);
|
||||
|
||||
// Shows content on this layer and runs it until it accepts or rejects, returning its
|
||||
// QDialog result code. anchorRect (in this layer's coordinates, which are the main
|
||||
// window's) is what the content is centered on; a null rect centers it on the layer
|
||||
// itself (REQ-UI-PANEL-MODAL).
|
||||
int execute(ModalDialog& content, const QRect& anchorRect = QRect());
|
||||
|
||||
// Whether a modal is open. The main window asks before handing focus back to the
|
||||
// game world, which it must not do while a modal holds it.
|
||||
bool isActive() const;
|
||||
|
||||
// Keeps the layer shown with nothing on it, so that one modal handing straight over
|
||||
// to another does not blink the dim off in between (REQ-UI-MODAL-DIM).
|
||||
void addHold();
|
||||
void removeHold();
|
||||
|
||||
// Update the dim color (e.g. after a config reload on Restart, REQ-CFG-RELOAD).
|
||||
void setDimColor(const QColor& dimColor);
|
||||
|
||||
protected:
|
||||
void paintEvent(QPaintEvent* event) override;
|
||||
|
||||
private:
|
||||
// Sizes content to what it asks for, capped at the layer, and centers it on
|
||||
// anchorRect (REQ-UI-PANEL-MODAL).
|
||||
void place(QScrollArea& host, ModalDialog& content, const QRect& anchorRect) const;
|
||||
|
||||
void updateVisibility();
|
||||
|
||||
QColor m_dimColor;
|
||||
std::vector<ModalDialog*> m_stack; // bottom-most first; back() is the open one
|
||||
int m_holdCount = 0;
|
||||
};
|
||||
|
||||
// RAII guard for addHold()/removeHold().
|
||||
class ModalLayerHold
|
||||
{
|
||||
public:
|
||||
explicit ModalLayerHold(ModalLayer& layer)
|
||||
: m_layer(layer)
|
||||
{
|
||||
m_layer.addHold();
|
||||
}
|
||||
|
||||
~ModalLayerHold()
|
||||
{
|
||||
m_layer.removeHold();
|
||||
}
|
||||
|
||||
ModalLayerHold(const ModalLayerHold&) = delete;
|
||||
ModalLayerHold& operator=(const ModalLayerHold&) = delete;
|
||||
|
||||
private:
|
||||
ModalLayer& m_layer;
|
||||
};
|
||||
Reference in New Issue
Block a user