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
Selecting a building after a piece of debris left a scroll bar on a card that
had nothing to scroll.
Two faults, both found by driving the panel through the reported sequences and
printing what it measured itself to:
The bar was Qt's decision. Asked for ScrollBarAsNeeded, the scroll area shows a
bar the moment the card is larger than its viewport -- which is true while the
card is being measured, since measuring means giving it a width -- and does not
take it back when the range turns out to be empty. The panel already works out
whether the card fits its band and widens itself for the bar when it does not,
so it now sets the policy itself: AlwaysOn when it decided to scroll, AlwaysOff
when it did not. The previous commit's resize-to-cap made this visible;
removing that alone traded the phantom bar for a real one, because the card's
height depends on the width it is measured at.
The measurement was also a pass short. A card's width follows from the room it
is given and its height from that width, so refit() measures twice: once at the
cap to learn the width the card wants, once at that width for the height. The
whole thing then runs twice, because parts of a freshly built card report an
unstyled size until the style reaches them during the first round -- that gap
was leaving the panel a few pixels short of what the card turned out to need,
which is a scroll bar over a card that looks like it fits.
Measured before and after, HQ card, roomy band:
before panel=113x114 card wants 111x121 bar visible, range empty
after panel=113x123 card wants 111x121 no bar
And in a 90px band it still scrolls: panel=130x74, range 47, bar shown.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
Clicking something sometimes left the panel collapsed to its scroll bar, most
often for field entities.
Same cause as the controls panel's collapse in d7c6734: a widget created under
an already-visible parent starts hidden, and a layout counts a hidden item as
empty. rebuildContent() built the card, added it to the body layout and
measured immediately, so the body reported nothing but its own margins. A width
of nearly zero makes heightForWidth() on the wrapped labels enormous, that
overflows the band, and refit() then caps the height and widens by the scroll
bar -- leaving the panel exactly one scroll bar wide.
It could not recover on the next tick either, because a word-wrapped label's
size hint follows its current width: once narrow, it keeps reporting narrow and
tall. So refit() now measures the body at the width cap rather than at whatever
width the panel currently has, and invalidates the body layout before reading
it -- cards are built and discarded whole, so its cached hint otherwise
describes the card before this one.
Field entities hit it more often because a field selection publishes twice,
actors then debris, so one click rebuilds the card twice.
The same defect existed one level down, where parts are rebuilt while the card
is already visible: ItemChipRow::rebuildChips() and RecipeSummaryRow::rebuild()
now show what they create. Those were measuring the buffer section and the
recipe line as empty on every recipe change.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
The panel rendered every selection out of a single pool of member widgets,
hidden and shown per branch, so each build path had to remember to hide the
other branches' widgets. That coupling produced the two defects fixed in
668ce0f, and the per-type detail the requirements now ask for would only add
more of it.
The pool is gone. SelectionPanel keeps the category arbitration, the float and
the hide-when-empty behaviour, and hosts exactly one SelectionContent at a
time; SelectionContentFactory picks which one from the selection alone. Each
card is one row of the catalog in REQ-UI-SELECTION-CONTENT and owns only its
own widgets.
The card structure (REQ-UI-SELECTION-CARD) lives in the base class: a header
with an identity symbol, a name and one right slot, then a configuration group
and a runtime group. A construction site keeps its configuration and has its
whole runtime group replaced by the construction progress, decided once there
rather than in every card (REQ-BLD-SITE-CONFIG).
Also implemented here:
- REQ-UI-SELECTION-STATUS: the header status dot, taken from the simulation's
own getProductionStatus() so the panel and the world's status light cannot
disagree.
- REQ-UI-SELECTION-AGGREGATE: belt-subsystem tiles and debris-only selections
collapse into one card with a count instead of a count summary.
- REQ-UI-HQ-PANEL: the HQ shows the global block stock and its HP, neither of
which is a buffer.
- BuildingIconCache, extracted from BuildButtonBar's file-local chip loading so
the card headers and the build buttons rasterize the same SVGs once.
FieldSelectionPanel is deleted: ships, stations, debris and the field count
summary are four more cards in the same factory, so the two-panel arbitration
collapses into one decision.
The card parts are still today's labels and buttons; the item chips, bars,
recipe summary and stat rows follow.
Build clean, 541 tests pass, app runs with no Qt warnings. Visual check
pending.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K
Implements REQ-UI-SELECTION-PANEL and the full-width REQ-UI-WORLD-SIZE /
REQ-UI-HEADER: MainWindow drops the 25% side column and its 75/25 math, so
the header bar and world view span the window, and the panel joins the
build button bar as a widget floating over the world.
The panel follows the bar's pattern -- an opaque sibling built after the
world view, so it sits above the vignettes, below the dim overlay, and
swallows the mouse events that would otherwise reach the world. It sizes
itself to its content within a band the window hands it (the world view
less the bar's strip, so the bar never has to move for it), right-aligned
and centered in that band, and scrolls once the content outgrows it. Its
content moved into a scroll area for that; the width is capped at 320 px
because the wrapped labels and the splitter filter lists have no natural
width of their own. With nothing selected the panel now hides entirely
rather than showing an empty box (REQ-UI-EMPTY-SELECTION).
Two defects that content sizing exposed: buildEmpty() left the selected
ids behind when the building vanished under the panel, which would have
held an empty panel on screen, and buildMulti() let a shipyard's layout
preview survive from a previous single selection (REQ-UI-MULTI-SELECTION).
Renames SelectedBuildingPanel to SelectionPanel throughout, matching the
requirements: the panel has long shown ships, stations, and debris too.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01JcReq7hVk4KUPhTDKWAG7K