166 lines
8.0 KiB
Markdown
166 lines
8.0 KiB
Markdown
# FB-08 — HUD widgets reflow to fill the gap when one is hidden
|
|
|
|
**Depends on** — · **Size** M · **Status** Done
|
|
|
|
## Goal
|
|
Hiding a HUD metric must resize the remaining widgets in its row to fill the empty
|
|
space. Today hiding a metric leaves an empty gap in the grid.
|
|
|
|
## Context
|
|
Direct user feedback (`docs/FEEDBACK.md`):
|
|
|
|
> When disabling some statistic during the HUD sessions, they should resize
|
|
> themselves, instead of leaving a gap by default.
|
|
|
|
`lib/src/hud/hud_layout_controller.dart`, `setVisible` (full method):
|
|
|
|
```dart
|
|
void setVisible(HudMetric metric, bool visible) {
|
|
var current = state[metric] ?? HudWidgetLayout.defaultFor(metric);
|
|
if (visible && _overlapsAnyOther(metric, current.copyWith(visible: true))) {
|
|
current = HudWidgetLayout.nextFreeSlot(
|
|
metric,
|
|
occupied: state,
|
|
colSpan: current.colSpan,
|
|
rowSpan: current.rowSpan,
|
|
);
|
|
}
|
|
state = {...state, metric: current.copyWith(visible: visible)};
|
|
}
|
|
```
|
|
|
|
This method only ever changes the toggled metric's own entry. No other metric's `col`,
|
|
`row`, or `colSpan` ever changes as a side effect. When a metric in a row of four is
|
|
hidden, the other three keep their exact original `col`/`colSpan` — the grid cell the
|
|
hidden metric used to occupy simply renders nothing, showing as blank space in the HUD.
|
|
|
|
`HudWidgetLayout` (`lib/src/hud/hud_widget_layout.dart`) already has everything this
|
|
ticket needs to build a row-reflow function: `col`, `row`, `colSpan`, `rowSpan` (all
|
|
`int`), `copyWith`, and `hudRectsOverlap`. `hudGridColumns` is `4`.
|
|
|
|
## Design
|
|
- Add a private helper to `HudLayoutController`:
|
|
```dart
|
|
/// Spreads every visible widget in [row] evenly across the full grid width,
|
|
/// left to right in their existing column order. A widget whose row has no other
|
|
/// visible member is left alone -- there is nothing to redistribute.
|
|
Map<HudMetric, HudWidgetLayout> _reflowRow(
|
|
Map<HudMetric, HudWidgetLayout> layouts,
|
|
int row,
|
|
) {
|
|
final members = layouts.values.where((l) => l.visible && l.row == row).toList()
|
|
..sort((a, b) => a.col.compareTo(b.col));
|
|
if (members.length < 2) return layouts;
|
|
final baseSpan = hudGridColumns ~/ members.length;
|
|
final extra = hudGridColumns % members.length;
|
|
final result = Map<HudMetric, HudWidgetLayout>.from(layouts);
|
|
var col = 0;
|
|
for (var i = 0; i < members.length; i++) {
|
|
final span = baseSpan + (i < extra ? 1 : 0);
|
|
result[members[i].metric] = members[i].copyWith(col: col, colSpan: span);
|
|
col += span;
|
|
}
|
|
return result;
|
|
}
|
|
```
|
|
A row of 4 becomes 4 equal columns (unchanged from today). A row of 3 becomes
|
|
columns of width `[2, 1, 1]` (`4 ~/ 3 = 1`, remainder `1` goes to the first member).
|
|
A row of 2 becomes `[2, 2]`. A row of 1 becomes `[4]` — a single remaining widget
|
|
fills the whole row.
|
|
- Call `_reflowRow` from `setVisible`, after computing the toggled metric's own new
|
|
entry, using that metric's **row before the toggle** when hiding, and its **row
|
|
after placement** when showing (the row a newly-shown metric lands in via
|
|
`defaultFor`/`nextFreeSlot`):
|
|
```dart
|
|
void setVisible(HudMetric metric, bool visible) {
|
|
var current = state[metric] ?? HudWidgetLayout.defaultFor(metric);
|
|
if (visible && _overlapsAnyOther(metric, current.copyWith(visible: true))) {
|
|
current = HudWidgetLayout.nextFreeSlot(
|
|
metric,
|
|
occupied: state,
|
|
colSpan: current.colSpan,
|
|
rowSpan: current.rowSpan,
|
|
);
|
|
}
|
|
final updated = {...state, metric: current.copyWith(visible: visible)};
|
|
state = _reflowRow(updated, current.row);
|
|
}
|
|
```
|
|
- Reflow only touches the toggled metric's own row. A different row's widgets never
|
|
move when a metric in another row is hidden or shown.
|
|
- Reflow applies to every visible widget currently in that row, whether it is at its
|
|
default position or one the rider dragged there manually. This ticket does not track
|
|
"was this widget moved by the rider" — the row is always kept evenly filled, by
|
|
design, since the feedback asks for the row to "resize themselves" whenever a metric
|
|
in it is hidden, not only in the untouched-default case.
|
|
- A drag or a resize (as opposed to a visibility toggle) does **not** trigger reflow —
|
|
`updatePosition`/`updateSize` are unchanged. Reflow is scoped to `setVisible` only.
|
|
|
|
## Implementation
|
|
1. Add `_reflowRow` to `HudLayoutController`.
|
|
2. Call `_reflowRow` at the end of `setVisible`, passing the toggled metric's row.
|
|
3. Run `flutter analyze` and `flutter test`.
|
|
|
|
## Acceptance criteria
|
|
- [ ] Hiding one metric out of four in the same row resizes the remaining three to
|
|
fill the row evenly — no empty gap.
|
|
- [ ] Hiding a metric that is alone in its row leaves every other row untouched.
|
|
- [ ] Showing a hidden metric back reflows its landing row to make room for it,
|
|
shrinking the row's other members evenly.
|
|
- [ ] A metric in a different row from the one just toggled never moves.
|
|
- [ ] `flutter analyze` clean, `flutter test` green, test count only goes up.
|
|
|
|
## Tests
|
|
In `test/hud_layout_controller_test.dart` (alongside the existing `setVisible` tests):
|
|
- Hiding one of four same-row, equally-sized metrics leaves the remaining three each
|
|
spanning `hudGridColumns ~/ 3` or one more (matching the `baseSpan`/`extra` split),
|
|
covering the full row width with no gap (assert the sum of `colSpan` across the
|
|
remaining visible row members equals `hudGridColumns`).
|
|
- Hiding a metric whose row has no other visible member leaves every other metric's
|
|
`col`/`row`/`colSpan` exactly unchanged (assert full equality against the pre-toggle
|
|
state for every other metric).
|
|
- Showing a previously-hidden metric back into a row with existing members reflows
|
|
that row to fit it — assert the sum of `colSpan` across the row (including the
|
|
newly-shown metric) still equals `hudGridColumns`.
|
|
- Toggling a metric in one row leaves a different row's own members' `col`/`colSpan`
|
|
unchanged.
|
|
|
|
## Risks
|
|
None significant — this only changes `col`/`colSpan` values within a single row at the
|
|
moment of a visibility toggle; it does not touch persistence format, the drag/resize
|
|
collision logic, or any other file.
|
|
|
|
## Out of scope
|
|
FB-09's title-driven default sizing — this ticket does not change what colSpan a
|
|
metric starts at, only how a row's members redistribute space among themselves when
|
|
one of them is hidden or shown. Sequence FB-09 after this ticket merges, since both
|
|
touch `HudLayoutController`/`HudWidgetLayout` and a large simultaneous diff in both is
|
|
harder to review and merge than one after the other.
|
|
|
|
## Outcome
|
|
Implemented `_reflowRow` in `lib/src/hud/hud_layout_controller.dart` exactly as
|
|
specified in the Design section, and wired it into `setVisible` using the toggled
|
|
metric's `current.row` (its row before the toggle when hiding, its row after
|
|
`defaultFor`/`nextFreeSlot` placement when showing) — no deviation from the design.
|
|
|
|
Added four tests to `test/hud_layout_controller_test.dart` covering the four
|
|
acceptance-criteria scenarios: hiding one of four same-row metrics reflows the
|
|
remaining three to fill the row with no gap; hiding a metric alone in its row leaves
|
|
every other metric's `col`/`row`/`colSpan` untouched; showing a previously-hidden
|
|
metric into a row with an existing member reflows that row so their `colSpan`s sum to
|
|
`hudGridColumns`; and toggling a metric in one row leaves a different row's members'
|
|
`col`/`colSpan` unchanged.
|
|
|
|
`flutter analyze` stayed clean (only pre-existing, unrelated info-level lint notices in
|
|
`lib/src/crash/crash_reporter.dart` and `lib/src/tiles/map_connectivity.dart`, neither
|
|
touched by this ticket). `flutter test` is green with a final count of **416 tests**
|
|
(412 baseline + 4 new), no regressions.
|
|
|
|
### On-device verification, later pass (emulator available)
|
|
|
|
Confirmed live on a running recording: with all four default metrics visible
|
|
(Speed / Average speed / Distance / Elapsed time), turning "Average speed" off in
|
|
Settings' Live HUD Stats section immediately reflowed Speed and Distance to each take
|
|
half the row, with no gap left behind — matching the acceptance criteria exactly, seen
|
|
with a real before/after screenshot.
|