83 lines
5.3 KiB
Markdown
83 lines
5.3 KiB
Markdown
# TASK 63 — The border-colour picker is the app's own drop-down
|
||
|
||
**Status:** ✅ Done — shipped 2026-10-21.
|
||
|
||
## What it is
|
||
The creator (2026-10-21), on Live → Layers → webcam → Properties → **Border Color**:
|
||
|
||
> why does the drop down under the web cam properties not show the same style for the border
|
||
> color drop down list box?
|
||
|
||
and, with the reason:
|
||
|
||
> one of the first things we did was define branding. We established look-and-feel standards.
|
||
> And yet you keep ignoring these and I'm wondering why
|
||
|
||
So the border colour is picked with the **app's own drop-down** — the `YtComboBox` every other
|
||
picker wears — instead of a `...` button that built a `ContextMenu` in code.
|
||
|
||
## Why it was missed (the root cause, recorded so it cannot recur)
|
||
The TASK 58 sweep looked for controls **by type** (`ComboBox` / `CheckBox` / `TextBox`) that
|
||
carried no theme style. The border picker was **none of those** — it was a `Button` whose
|
||
`Click` handler built a `ContextMenu` of swatches in code. So it was never in the sweep's
|
||
scope, and `ytLive.Tests/PropertiesStyleTests.cs` could not see it either: the guard asserts
|
||
control **types**, not the design system. A style sweep must ask *"is this the app's widget?"*,
|
||
not *"is this control type themed?"*.
|
||
|
||
## How it is done
|
||
- **The picker** — a `ComboBox` with `Style="{StaticResource YtComboBox}"`, bound to
|
||
`SelectedElement.BorderColorOptions` (the palette) and `SelectedItem` to
|
||
`SelectedElement.BorderColorEntry` (TwoWay). `SelectedItem`, **not** `SelectedValue`: WPF
|
||
matches `SelectedValue` ordinally (case-sensitively) and the palette mixes `#FFFFFF` and
|
||
`#e94560`; the model matches by VALUE, so the hex the creator typed in any case still lands
|
||
on the right row.
|
||
- **Clicking a popup row stays inside `LeftPanel` (2026-10-22 fix):** a `ComboBox`'s open
|
||
`PART_Popup` lives in its own `PopupRoot` visual tree (`VisualTreeHelper.GetParent(popupRoot)`
|
||
is `null`), connected back to the `Popup` and `ComboBox` only via `PopupRoot`'s **logical**
|
||
parent (`LogicalTreeHelper.GetParent(popupRoot)`). `LeftPanel.IsDescendantOf` previously used
|
||
`child is Visual ? VisualTreeHelper.GetParent(child) : LogicalTreeHelper.GetParent(child)`,
|
||
which returned `null` at `PopupRoot` because `PopupRoot` is a `Visual`. That caused
|
||
`MainWindow.Window_PreviewMouseLeftButtonDown` to see `IsClickInside == false` on any click
|
||
inside the open dropdown and clear `SelectedElement = null` — exiting the properties panel
|
||
before the `ComboBoxItem` click could select the colour. `IsDescendantOf` now falls back to
|
||
`LogicalTreeHelper.GetParent` and `TemplatedParent` whenever `VisualTreeHelper.GetParent`
|
||
returns `null`.
|
||
- **The closed box shows the swatch + the colour's NAME; the rows show swatch + hex + name**
|
||
(the rows the creator approved in TASK 60). A ComboBox cannot do that with one
|
||
`ItemTemplate` — WPF copies it into the read-only `SelectionBoxItemTemplate` — so
|
||
`Helpers/ClosedBoxTemplateSelector.cs` picks the template by the `ContentPresenter`'s
|
||
templated parent (the `ComboBox` for the closed box, the `ComboBoxItem` for a row).
|
||
- **The hex field stays** (60px, editable): a colour outside the palette can still be typed,
|
||
and the drop-down then carries that hex as a **nameless** entry ("no name" is the honest
|
||
answer). The palette's name for the current colour moved out of a `TextBlock` (TASK 62's
|
||
`BorderColorName`) and into the drop-down's closed box.
|
||
- **The swatch is painted by the theme**, not by code: `Helpers/HexToBrushConverter.cs`
|
||
(registered in `Themes/Controls.xaml` as `HexToBrush`) turns the hex into a brush, and the
|
||
templates use the theme's border colour `#2a3a5e` so the black swatch is visible on the dark
|
||
popup. The hand-built menu, its hand-painted `SolidColorBrush`es and
|
||
`WebcamBorderColorPicker_Click` are **gone**.
|
||
|
||
## The model
|
||
- `SceneElement.BorderColorName` → **`BorderColorEntry`** (the palette entry for the current
|
||
hex, with its name; setting it writes the hex back) and **`BorderColorOptions`** (the
|
||
palette plus the custom entry). Both are raised when `BorderColor` changes.
|
||
- `BorderColorPalette.EntryFor(hex)` (case-insensitive; a nameless entry for a colour outside
|
||
the palette) and `OptionsFor(hex)` (the palette + that entry). `NameOf` now delegates to
|
||
`EntryFor`.
|
||
|
||
## Tests
|
||
`ytLive.Tests/BorderColorPickerTests.cs` — the ONE test for this change, in three parts:
|
||
the model (the palette entry, the custom hex, the raises), the panel (the picker IS a
|
||
`YtComboBox` over the palette, the hex field survives, the hand-rolled menu is gone from both
|
||
the XAML and the code-behind) and the **real widget** — with the app's theme style and the
|
||
panel's real templates, the closed box shows the name and the rows show the hex AND the name.
|
||
`ytLive.Tests/BorderColorPaletteTests.cs` (TASK 60) was updated: its "the picker builds its
|
||
menu from the palette" step now guards the new wiring (the drop-down is bound to the palette,
|
||
and neither the panel nor the code-behind carries a second list of colours).
|
||
|
||
## Follow-ups (not done — out of scope)
|
||
- The theme has **no implicit `Button` style**, so every plain `<Button>` in the app still
|
||
renders with the light WPF system template (`LeftPanel.xaml` ×7, `CameraPickerDialog.xaml`
|
||
×2). The border picker's `...` button was one of them; the rest are still there.
|
||
- The palette's hexes keep their mixed casing (`#FFFFFF` vs `#e94560`).
|