place the floating widgets in one ordered pass

The three widgets over the game world view each cached a rect handed to them by
MainWindow's resize, then re-placed themselves from it. But the build button bar
re-centers on a building unlock and the controls panel re-fits on a 50 ms timer,
neither of which goes through MainWindow, so the rects the others held went
stale -- and each widget re-implemented its own avoidance against them.

They now implement FloatingPanel and are placed in one ordered pass: the bar
takes what it wants, the controls panel steps around the bar, and the selection
panel keeps clear of both. A widget that changed size or visibility publishes
FloatingLayoutInvalidatedEvent instead of moving itself, because what it may
take depends on the widgets placed before it.

The rule they step around each other by is one function in lib, where it can be
tested without a display -- the only way any of this geometry gets automated
cover, screen capture of the world view being blank here.

The selection panel keeps its right edge and its vertical centering, but the
space it centers in is now what its own column has left free rather than the
full-width strip the bar used to reserve. It therefore sits lower than before
where the centered bar does not reach it, and it now clears the controls panel,
which it previously ignored.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
This commit is contained in:
2026-08-08 19:10:00 +02:00
parent 37899ea964
commit e66eb7a81f
17 changed files with 329 additions and 143 deletions

View File

@@ -12,6 +12,7 @@
#include "BuildingId.h"
#include "EscapeMenuRequestedEvent.h"
#include "EventHandler.h"
#include "FloatingLayoutInvalidatedEvent.h"
#include "GameConfig.h"
#include "GameOverEvent.h"
#include "LayoutDialogRequestedEvent.h"
@@ -45,7 +46,8 @@ class MainWindow : public QWidget,
LayoutDialogRequestedEvent,
RecipeSelectionRequestedEvent,
BlueprintSaveRequestedEvent,
BlueprintSelectionRequestedEvent>
BlueprintSelectionRequestedEvent,
FloatingLayoutInvalidatedEvent>
{
Q_OBJECT
@@ -67,6 +69,7 @@ private:
void handleEvent(std::shared_ptr<const RecipeSelectionRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const BlueprintSaveRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const BlueprintSelectionRequestedEvent> event) override;
void handleEvent(std::shared_ptr<const FloatingLayoutInvalidatedEvent> event) override;
// Reloads the game config and visuals.toml from disk (REQ-CFG-RELOAD), shared
// by every restart path. On success the reloaded visuals are applied to this
@@ -85,6 +88,8 @@ private:
// both callers already hold theirs, which is what keeps the dim continuous when a
// confirmed save hands straight over to this dialog (REQ-UI-MODAL-DIM).
void showBlueprintSelectionDialog();
// Places the widgets floating over the game world view, in one ordered pass
// (FloatingPanel.h). Runs on a resize and on every FloatingLayoutInvalidatedEvent.
void layoutPanels();
private:
@@ -109,4 +114,8 @@ private:
std::vector<ShipLayoutBlueprint> m_layoutBlueprints;
std::shared_ptr<ParsedReplay> m_replay; // non-null => view-only playback
// Set while the placement pass runs, so a widget placed in it cannot start a second
// pass from inside the first.
bool m_layingOut = false;
};