diff --git a/src/ui/MainWindow.cpp b/src/ui/MainWindow.cpp index fe99cd5..3f736f6 100644 --- a/src/ui/MainWindow.cpp +++ b/src/ui/MainWindow.cpp @@ -7,6 +7,7 @@ #include #include +#include #include #include #include @@ -317,6 +318,9 @@ void MainWindow::openShipLayoutDialog(BuildingId shipyardId, std::move(unlockedModuleIds), m_gameWorldView->isDebugDrawEnabled(), this); + // Opened from the panel's "Configure" button (REQ-MOD-UI-PREVIEW) or straight after + // a schematic change (REQ-MOD-UI-AUTO-DIALOG), so it opens on the panel either way. + placeOnSelectionPanel(dialog); if (dialog.exec() == QDialog::Accepted && dialog.getResult().has_value()) { std::shared_ptr command = @@ -328,6 +332,45 @@ void MainWindow::openShipLayoutDialog(BuildingId shipyardId, } } +void MainWindow::placeOnSelectionPanel(QDialog& dialog) const +{ + // The panel is up whenever one of these modals opens -- they are opened from its own + // controls, and it is shown whenever anything is selected (REQ-UI-EMPTY-SELECTION). + // Were it not, there would be no rectangle to center on and Qt's own centering on + // this window stands. + if (!m_selectionPanel->isVisible()) { return; } + + // The dialog has never been shown, so it is still at its default size until its + // layout has run; centering it before that would use the wrong extent. + dialog.adjustSize(); + const QSize dialogSize = dialog.size(); + + // The panel's live geometry, so a panel the player has dragged + // (REQ-UI-SELECTION-PANEL-DRAG) carries the modal with it. + const QRect panelRect(m_selectionPanel->mapToGlobal(QPoint(0, 0)), + m_selectionPanel->size()); + const QRect windowRect(mapToGlobal(QPoint(0, 0)), size()); + + QPoint topLeft(panelRect.center().x() - dialogSize.width() / 2, + panelRect.center().y() - dialogSize.height() / 2); + + // Pushed back inside the window, never resized to fit (REQ-UI-PANEL-MODAL). The far + // edge is clamped first and the near edge second, which is what aligns a dialog too + // large for the window with the window's top-left corner rather than pushing it off + // the opposite edge. + topLeft.setX(qMax(windowRect.left(), + qMin(topLeft.x(), windowRect.right() - dialogSize.width() + 1))); + topLeft.setY(qMax(windowRect.top(), + qMin(topLeft.y(), windowRect.bottom() - dialogSize.height() + 1))); + + // Positions the dialog's frame, whose size is not known until it is first shown, so + // the result sits low by the title bar height against a true center -- measuring it + // would mean showing the dialog at the wrong place first. The move also marks the + // dialog as positioned, which is what stops QDialog from centering it on this window + // when it is shown. + dialog.move(topLeft); +} + void MainWindow::handleEvent(std::shared_ptr event) { // A construction site has no Building yet; fall back to its site record so @@ -385,6 +428,7 @@ void MainWindow::handleEvent(std::shared_ptr command = std::make_shared(); diff --git a/src/ui/MainWindow.h b/src/ui/MainWindow.h index c34d225..de74473 100644 --- a/src/ui/MainWindow.h +++ b/src/ui/MainWindow.h @@ -36,6 +36,7 @@ class BlueprintLibrary; class BuildingIconCache; class ItemIconCache; class QCloseEvent; +class QDialog; class QResizeEvent; class MainWindow : public QWidget, @@ -83,6 +84,11 @@ private: const std::string& schematicId, const ShipLayoutConfig& currentLayout); + // Centers a modal opened from the selection panel on that panel, kept inside this + // window (REQ-UI-PANEL-MODAL). Called on the constructed dialog before exec(), and + // only for the two modals the panel opens. + void placeOnSelectionPanel(QDialog& dialog) const; + // Runs the blueprint selection dialog and enters placement mode for whatever the // player picked (REQ-UI-BLUEPRINT-DIALOG). Holds no pause or dim scope of its own: // both callers already hold theirs, which is what keeps the dim continuous when a