Files
LlamaCasty/Models/BorderColorPalette.cs
gramps 987fddac24 TASK 63: the border-colour picker IS the app's own drop-down
The creator (2026-10-21), on Live -> Layers -> webcam -> Properties:

  "why does the drop down under the web cam properties not show the same style for
   the border color drop down list box?"

  "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"

WHY IT WAS MISSED - the answer the creator is owed.
The TASK 58 sweep had already walked the whole app for un-themed controls and the
border picker STILL was not themed, because the sweep looked for control TYPES
(ComboBox / CheckBox / TextBox) carrying no theme style - and the border picker was
NONE of those. It was a "..." Button whose Click handler built a ContextMenu of
swatches in code. It was never in the sweep's scope, and PropertiesStyleTests.cs
(a type-based guard) could not see it either. A picker that LOOKS like a drop-down
is invisible to a type-based audit; the guard has to ask "is this the app's widget?".

WHAT CHANGED
- The picker is now the app's YtComboBox - the widget the Display and Font pickers
  wear - bound to the palette (SelectedElement.BorderColorOptions) with its selection
  bound by VALUE (SelectedItem -> BorderColorEntry), because WPF's SelectedValue
  matching is ordinal and the palette mixes #FFFFFF and #e94560.
- 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 by the ContentPresenter's templated
  parent: the ComboBox for the closed box, the ComboBoxItem for a row. Verified
  against WPF with a throwaway probe, not assumed.
- The swatch is painted by the THEME (Helpers/HexToBrushConverter.cs, registered as
  HexToBrush) - the hand-painted SolidColorBrush(Color.FromRgb(...)) in the old
  click handler was the exact anti-pattern the theme forbids. The swatch also carries
  a 1px #2a3a5e border now, so black and dark swatches are visible on the dark menu
  (the follow-up TASK 60 left open).
- 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 moved out of TASK 62's TextBlock into the
  drop-down's closed box.
- The hand-built menu, its ContextMenu/MenuItem/SolidColorBrush code and
  WebcamBorderColorPicker_Click are gone.

MODEL: SceneElement.BorderColorName -> BorderColorEntry (the palette entry for the
current hex, with its name; setting it writes the hex back) + BorderColorOptions (the
palette plus the custom entry), both raised when BorderColor changes.
BorderColorPalette gains EntryFor(hex) and OptionsFor(hex); NameOf delegates.

Guards: ytLive.Tests/BorderColorPickerTests.cs (the ONE test for this change - the
model, the panel, and the REAL widget: with the app's theme style and the panel's own
templates the closed box names the colour and the rows show the hex AND the name).
The TASK 60 guard (BorderColorPaletteTests) was updated because the mechanism it
asserted - a code-built menu - no longer exists; it 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).

Full suite: 435/435 passed, 0 warnings.
2026-10-21 23:09:11 -07:00

73 lines
3.1 KiB
C#

namespace ytLive.Models;
/// <summary>A colour the border picker offers: its hex value and the plain-English
/// name shown next to it (TASK 60).</summary>
/// <param name="Hex">The hex value, e.g. <c>#e94560</c>.</param>
/// <param name="Name">The name the creator reads, e.g. <c>Crimson</c>.</param>
public readonly record struct NamedColor(string Hex, string Name)
{
/// <summary>"Crimson (#e94560)" — the tooltip the border drop-down shows.</summary>
public override string ToString()
=> string.IsNullOrEmpty(Name) ? Hex : $"{Name} ({Hex})";
}
/// <summary>The colours the webcam's border-colour drop-down offers — the SINGLE
/// source of truth for the list and for the names.
///
/// <para>TASK 60 (the creator, 2026-10-21): *"in the drop down box, the #hex values are
/// good, but add a corresponding color name/label next to the value."* The list used to
/// be a hard-coded array in the click handler, which is why a name could not be added
/// to it without touching the UI code.</para>
/// </summary>
public static class BorderColorPalette
{
/// <summary>The offered colours, in menu order.</summary>
public static IReadOnlyList<NamedColor> All { get; } = new NamedColor[]
{
new("#FFFFFF", "White"),
new("#000000", "Black"),
new("#FF0000", "Red"),
new("#00FF00", "Lime"),
new("#0000FF", "Blue"),
new("#FFFF00", "Yellow"),
new("#FF00FF", "Magenta"),
new("#00FFFF", "Cyan"),
new("#FF8800", "Orange"),
new("#8800FF", "Violet"),
new("#e94560", "Crimson"),
new("#22c55e", "Emerald"),
new("#3b82f6", "Azure"),
new("#f59e0b", "Amber"),
};
/// <summary>The colour's name, or an **empty string** when the palette has no name
/// for that hex (a custom hex the creator typed) — the caller decides what to show
/// then, and the border field shows the hex alone.</summary>
public static string NameOf(string hex) => EntryFor(hex).Name;
/// <summary>The palette entry for a hex value — the palette's OWN entry (so a
/// drop-down bound to it matches by value), or a nameless entry for a custom hex
/// the creator typed, so the drop-down always has a selection (TASK 63).</summary>
public static NamedColor EntryFor(string hex)
{
if (string.IsNullOrWhiteSpace(hex)) return new NamedColor(string.Empty, string.Empty);
var trimmed = hex.Trim();
foreach (var colour in All)
if (string.Equals(colour.Hex, trimmed, StringComparison.OrdinalIgnoreCase))
return colour;
return new NamedColor(trimmed, string.Empty);
}
/// <summary>The colours a border drop-down offers for a given hex — the palette, plus
/// the custom colour the creator typed when it is not one of the palette's, so the
/// drop-down always has a selection (TASK 63).</summary>
public static IReadOnlyList<NamedColor> OptionsFor(string hex)
{
var entry = EntryFor(hex);
var options = new List<NamedColor>(All);
if (!string.IsNullOrEmpty(entry.Hex) && !All.Contains(entry))
options.Add(entry);
return options;
}
}