Files
LlamaCasty/TASKS/task-59-properties-hierarchy.md
T
gramps 89f6aa3a67 TASK 59: the properties panel is a hierarchy, and the shape is a choice
The creator's ruling on the webcam's properties: a "Shape" title above the
Mirror/Rect row; the drag hint indented under that row "to show parentage";
the same for Geometry, Border Color, Border Thickness; Remove and Blur indented
under Background; Strength indented under Blur.

One rule covers it: a title owns its content and the content is indented one
level (10px) under it - two levels where a title is itself nested (Strength
under Blur under Background). The titles are SemiBold 11px #a0a0b0 like the
ones already there.

The shape also stops being a toggle button that shows the NEXT action ("Round"
when the camera was a rectangle) and becomes a CHOICE: two segment options,
Rect | Circle, with Rect selected by default - which is what the creator asked
for ("Rect should be selected as a default if none exists"). The old
ShapeButtonText is gone.

The mutual exclusion lives in the MODEL: IsRectShape/IsRoundShape on
SceneElement, each setter flipping the shape through ToggleClipShape() (so the
pre-round rect is still stored/restored) and always re-raising, so a refused
write - the vertical Chat layout is rect-only (TASK 57) - makes the UI snap
back instead of sticking. Deliberately NOT a WPF RadioButton group: a group
unchecks its siblings by setting IsChecked in code, which makes WPF drop the
binding (stackoverflow 7376373; the same trap measured in 50980687/57275046).

The Circle option stays hidden where CanToggleClipShape is false, so the circle
is still not offered in the vertical Chat layout.

Guard: ytLive.Tests/PropertiesHierarchyTests.cs - the model half (Rect by
default, pick circle, pick rect, the locked Chat layout refuses the circle) and
the panel half (the Shape title owns both options, the circle is the gated one,
the hint is indented and follows the row, every titled block's content is at
10px, Strength at 20px). 432/432 green, 0 warnings.
2026-10-21 19:58:28 -07:00

75 lines
3.6 KiB
Markdown

# TASK 59 — The properties panel is a hierarchy (titles own their content)
**Status:** ✅ Done — shipped 2026-10-21.
## What it is
The creator's ruling (2026-10-21), on the Live scene → Layers → webcam → Properties:
> Mirror Rect: needs a title "Shape" above it and **Rect should be selected as a default
> if none exists** … also, the "drag to move" help text needs to be **indented under
> "Mirror Rect" to show parentage** … same with **Geometry, Border Color, Border
> Thickness**. Background → Remove: Remove should be indented under Background as should
> Blur. Blur → Strength: Strength should be indented under Blur.
One rule covers all of it: **a title owns its content, and the content is indented one
level (10px) under it.** Two levels deep when a title is itself nested (Strength under
Blur under Background).
## The panel now reads
```
Shape ← NEW title
[Mirror] [Rect] [Circle] ← the shape is a CHOICE, Rect selected by default
Drag to move · drag the corner dot to resize
Geometry ← title (already there), fields indented
X [ ] Y [ ]
W [ ] H [ ]
Border Color ← promoted to a title, the field indented
[#2c3a5c] [..]
Border Thickness ← promoted to a title, the slider indented
[slider] 3
Background ← title, its rows indented
Remove [pill]
Blur [pill]
Strength [slider] 50% ← one level deeper, under Blur
```
## The shape: a choice, not a toggle
Before: one button whose content was `ShapeButtonText` — the **next** action ("Round"
when the camera was a rectangle). The creator could not tell what the camera *was*, only
what it would become.
Now: two `SegmentToggle` options — **Rect** and **Circle** — bound to two new settable
bools on `SceneElement`:
- `IsRectShape` — true when the clip is the plain rectangle, which is what a camera is
when no shape was ever picked (**the creator's "Rect is selected by default if none
exists"**).
- `IsRoundShape` — true when the clip is a circle.
Each setter flips the shape through `ToggleClipShape()` (so the pre-round rect is still
stored/restored) and **always re-raises** — a refused write (the vertical Chat layout is
rect-only, TASK 57) makes the UI snap back to the truth instead of sticking.
`ShapeButtonText` is gone (dead code after this change).
### Why the mutual exclusion lives in the model
Not in a WPF `RadioButton` group: a RadioButton group unchecks its siblings by setting
`IsChecked` **in code**, which makes WPF drop the binding ("Losing the binding for radio
button after I set the property manually in code", stackoverflow 7376373) — the panel
would silently stop reflecting the model. Two bool properties with model-side setters keep
the binding alive (stackoverflow 50980687 / 57275046 describe the same trap).
## The circle option is still not offered where it must not be
The Circle option's `Visibility` binds to `CanToggleClipShape` — false in the vertical
tier's Chat layout (TASK 57) — so the option is not even shown there, and the model
refuses the write anyway.
## Tests
`ytLive.Tests/PropertiesHierarchyTests.cs` — the ONE test for this change:
- the model: Rect is selected by default → picking Circle makes the clip Round → picking
Rect again restores it → the vertical Chat lock still refuses the circle;
- the panel: the `Shape` title owns both options, the Circle option is the one gated by
`CanToggleClipShape`, the drag hint is indented and follows the shape row, every titled
block's content is indented 10px, and Strength sits at 20px (under Blur).
432/432 green at ship time.