TASK 61 + 62: the geometry fields share one width, and the border field names its colour

The creator (2026-10-21), on Live -> Layers -> webcam -> Properties:

  "the Y and H boxes under webcam properties should be the same width as the X and W
   boxes and those widths should be fixed to accomodate the max number of possible
   digits providing the geometry values"

  "also the border color drop drown box content should also include the name of the
   color next to the hex value as they in the drop-down list box"

TASK 61 - one fixed width for the four geometry fields.
The row is a DockPanel, and WPF sizes an element to its CONTENT unless it is told
otherwise, while the LAST child of a DockPanel fills the remaining space. So X/W (not
last) were as wide as their text and Y/H (last) stretched across the row - two widths
for the same kind of field. Now all four carry Width="44" and the rows set
LastChildFill="False" (without it the last field ignores its own Width and fills anyway).
44px is the measured width of "-1920" in the app's font at 11px (the largest geometry
value - the master frame is 1920x1080 - plus a sign) plus the field's padding and
border. Docs: https://learn.microsoft.com/en-us/dotnet/api/system.windows.controls.dockpanel.lastchildfill
The guard MEASURES the widest value with WPF's FormattedText instead of asserting a
magic number, so a font change cannot silently make the field too narrow:
https://learn.microsoft.com/en-us/dotnet/api/system.windows.media.formattedtext

TASK 62 - the border field names its colour.
SceneElement.BorderColorName carries the palette's name for the current colour
("Crimson") and is raised when the colour changes; it is EMPTY for a custom hex the
creator typed, because "no name" is the honest answer. BorderColorPalette.NameOf
changed to match (it used to fall back to the hex itself; the TASK 60 test assertion
was updated with it). The hex field keeps a fixed width (60) so the name sits right
next to the hex instead of drifting to the row's far edge - the same trap as TASK 61.

Guards: ytLive.Tests/GeometryFieldWidthTests.cs, ytLive.Tests/BorderColorNameTests.cs
Full suite: 435/435 passed, 0 warnings.
This commit is contained in:
2026-10-21 22:19:13 -07:00
parent 60b0218fa3
commit ef52508b1e
14 changed files with 322 additions and 30 deletions
+44 -2
View File
@@ -2155,8 +2155,11 @@ without reading the UI code.
`static IReadOnlyList<NamedColor> All` in `Models/`. Models = "plain data types, no logic
beyond what a property can carry" (see `Models/index.md`).
2. **Give it a lookup** (`NameOf(hex)`, case-insensitive) that answers for a value the user
typed, not only for the palette's own entries — the fallback returns the value itself, so
the UI never shows an empty name.
typed, not only for the palette's own entries. **What it answers for a colour OUTSIDE the
palette is a judgement call, and it changed once (TASK 62):** it used to fall back to the
value itself, so the UI never showed an empty name — but "no name" is the honest answer, and
the caller decides what to show (the border field shows the hex alone for a custom hex). The
fallback that lies is worse than the empty string.
3. **The UI becomes a consumer**: the handler iterates the catalogue and builds the rows. The
handler must not know any colour by heart.
4. **Guard it in the test**: every entry valid + named, no hex or name repeated, the colours
@@ -2171,3 +2174,42 @@ without reading the UI code.
**Why this matters beyond names:** the next request ("add a dark mode", "let me type a
custom colour", "reorder them") is now a change in ONE file instead of a change inside a
click handler.
## 2026-10-21 — A ROW OF SAME-KIND FIELDS NEEDS EXPLICIT WIDTHS (RECIPE — TASK 61)
The creator: *"the Y and H boxes should be the same width as the X and W boxes and those
widths should be fixed to accomodate the max number of possible digits."* The row was a
`DockPanel`, and WPF gave the four fields **two different widths** for no reason anyone chose.
**The trap (worth remembering on its own):**
- A WPF element **sizes itself to its content** unless it is told otherwise. So the X/W fields
(not the last child) were as wide as their text ("1920" ≈ 34px).
- In a `DockPanel` the **LAST child fills the remaining space** — so the Y/H fields stretched to
the full width of the row. Two widths for the same kind of field, purely by position.
**The recipe:**
1. Give **every** field in the row an explicit `Width` (one value, repeated — the point is that
they are EQUAL).
2. Set `LastChildFill="False"` on the row, or the last field ignores its own `Width` and fills
anyway.
3. **Measure the width, don't guess it.** The needed width = the widest value the field can
hold, measured in the app's own font at the field's size, plus the field's padding and
border. WPF measures it for you, headlessly, with no window:
```csharp
var typeface = new Typeface(SystemFonts.MessageFontFamily, FontStyles.Normal,
FontWeights.Normal, FontStretches.Normal);
var text = new FormattedText("-1920", CultureInfo.CurrentCulture, FlowDirection.LeftToRight,
typeface, 11, Brushes.Black, pixelsPerDip: 1.0).Width;
var needed = text + 8 /* padding */ + 2 /* border */;
```
`SystemFonts.MessageFontFamily` is exactly what a control uses when no font is set, so the
measurement matches what is drawn. (The geometry fields came out at 44px — 28 of text, 10 of
chrome, 6 of slack.)
4. **Assert the measurement in the test**, not a magic number: parse the XAML, take the widths,
and fail if the row's width cannot hold the widest value. A hard-coded "44" in the test
would pass even if the app's font changed and 44 stopped fitting.
5. **A label beside a field needs the field to be FIXED too.** The same trap in a different
costume: a field that fills its row pushes whatever sits after it to the far edge, so the
label drifts away from the value it labels (TASK 62 — the colour name beside the hex field).