The panel was anchored to the right edge of the view, which is nowhere near
whatever the player just clicked. It now stands beside the selection: right of
it where it fits, otherwise left, otherwise the roomier side pushed inside the
view -- the one case where it covers part of what it describes.
Nothing told the panel where the selection was. The selection events carry ids
only, and the mode that separates a fresh selection from an expanded one is
consumed inside SelectionController before they are built, so the view now
publishes the selection's screen bounds itself, immediately before selecting and
only when the selection is starting. Freezing that rectangle is what holds the
panel still: it does not chase a scrolling view, a ship flying off, or a
selection being added to. Only the panel's own size still moves it, and even
then it keeps its side and the edge facing the selection.
The rectangles come from what the renderer was already computing for the
selection outlines, now shared rather than duplicated.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
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
The controls panel needs to say what each key does right now, and a panel that
keeps its own list of that is a list that goes stale. So the list moves into
lib/core/ControlAction.h: which actions exist, what each is bound to, and when
each does something. InputMapper stops deciding that and switches on the
resolved action instead, so the panel and the key handling cannot disagree
about what Q means -- there is only one place that says.
The table declares; it never performs. It holds no simulation access, fires no
events, and names nothing: display strings live in the ui target, which formats
the bindings this hands it, so a badge is rendered from the real binding rather
than typed beside it. What an action *does* stays exactly where it was.
ControlContext is the snapshot the rules read, which is what keeps this
testable without a world. Two of its facts come from BlueprintLibrary, which is
built after the world view and so arrives by setter; one comes from the new
hovered-transfer flag on BuildModeController, resolved once on mouse-move
through the same classifier the click and the ghost colour already use.
Behaviour is unchanged, deliberately. Ctrl still separates the chords and every
other modifier is still ignored, so Shift+A pans as before; matching modifiers
exactly would have silently swallowed those presses. Build hotkeys and F3/F4
stay outside the table -- the first are advertised on the build buttons and
already derive their badges from the handler's own table, the second are
development controls the panel must never offer.
The tests are the point of putting this in lib: every row's bindings must
resolve back to that row's action in that same context, which fails the moment
a shown row and its handler part ways.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01YHcUerKAZKWNvSKJxYKbnG