Files
LlamaCasty/Controls/index.md
T
gramps ef52508b1e 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.
2026-10-21 22:19:13 -07:00

62 lines
6.6 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# Controls — index
The phase-2 carved pieces of what used to be one monolithic `MainWindow.xaml`
(1,681 lines → a 123-line shell). Each is a `UserControl` in the `ytLive.Controls`
namespace, extracted one roll-back commit at a time (refactor-commit-12..18).
See [`schema.md`](../schema.md) for the memory-map conventions.
| File | Purpose |
|------|---------|
| `BottomBar.xaml(.cs)` | Footer (Row 3): stream stats (bitrate/fps/dropped/duration/health), resolution dropdown (gear menu moved up to TopBar 2026-09-22) |
| `HealthBanner.xaml(.cs)` | Row 1 status banner + the 7 value→visibility converters live in the app theme, not here |
| `TopBar.xaml(.cs)` | Row 0 session surface (2026-09-22 redesign): brand + gear menu (left), one segmented REC|ON-AIR mode switch + world actions (Record: Start Recording · Stream: Go Live + Test) + reality line while running (dot/word/elapsed) + End (center), account zone — Sign In or avatar w/ Change Account/Logout context menu (right). `RefreshAvatar()`, `GearButton_Click` |
| `PreviewPane.xaml(.cs)` | Row 1 Col 1 center: the 1920×1080 `OverlayCanvas`/`SelectionOverlay`/`BrandFlashLayer`/`SocialBarElement`, text pull-out drawer, and the live-controls row (TRAX / game / mic meters + sliders + speakers + socials). **TASK 57:** dragging a webcam under the vertical tier routes through `MainViewModel.MoveWebcamInVerticalTier` (the six-spot snap inside the cropped band) instead of free-floating over the master, and a resize re-snaps the tile onto its spot (TASK 57 Unit 2: the resize routes through `MainViewModel.ResizeWebcamInVerticalTier` — the Chat camera's two sizes, then the snap) |
| `SceneThumbnailStrip.xaml(.cs)` | Row 0 scene-picker strip (`ItemsControl` over `Scenes`), staged-red / live-green border highlight |
| `LeftPanel.xaml(.cs)` | Row 1 Col 0 left panel: edit mode → layers+properties; live → YouTube chat. Source add/edit/remove, list drag-to-reorder, snapshot revert-accept. **TASK 51:** the webcam properties carry the Background section — `RemoveBackgroundPill` / `BlurBackgroundPill` (`PillToggle`, radio-exclusive via `WebcamSceneConfig.RemoveBackground`/`BlurBackground`) + `BackgroundStrengthSlider` (0..1, 5% snaps, shown only while an effect is on). **TASK 57:** the webcam properties carry a Geometry section (X/Y/W/H) — manual placement of the camera, bound to the **live** placement (the one the current tier shows), gated on `IsWebcamSelected` (not `SelectedElement.IsWebcam` — the TASK 51 test finds that panel by binding text and would match twice); the Shape button's `Visibility` is bound to `CanToggleClipShape`, so the circle option is NOT OFFERED in the vertical Chat layout. **TASK 58:** every drop-down, check box and field in the properties panel carries the theme's style (`YtComboBox`/`YtCheckBox`/`YtTextBoxCompact`) and paints no colour of its own — the guard is `ytLive.Tests/PropertiesStyleTests.cs`. **TASK 59:** the webcam's properties are a HIERARCHY — a `Shape` title owns the mirror switch and the shape choice (`Rect` | `Circle` as two `SegmentToggle` options, Rect selected by default, the circle option hidden where `CanToggleClipShape` is false), and every titled block (Geometry, Border Color, Border Thickness, Background) owns its content one indent deeper (Strength sits under Blur). **TASK 60:** the border-colour drop-down builds its menu from `Models/BorderColorPalette.cs` — each row is the hex plus the colour's NAME (`#e94560 Crimson`, tooltip "Crimson (#e94560)"); the swatch icon is unchanged. **TASK 61:** the four geometry fields (X/Y/W/H) share one fixed width (44px — the largest value, 1920, plus a sign) and their rows set `LastChildFill="False"`, so Y/H no longer stretch to fill the row. **TASK 62:** the border-colour field shows the palette's name beside the hex (`SelectedElement.BorderColorName`, empty for a custom hex) and keeps a fixed width so the name stays next to the hex |
| `OverlayHost.xaml(.cs)` | Root-grid overlay dialogs: Settings / Report Bug / Feature Request / About (+ licenses + license-key entry). `BackToAbout_Click` lives here |
| `SceneThumbnailStrip.xaml.cs` | Code-behind for the strip above |
| `LeftPanel.xaml.cs` | Code-behind for the panel above |
## Window ↔ control contract
The shell (`MainWindow.xaml(.cs)`) wires up each control and routes the few
cross-cutting calls through it:
- Ctor finds each control by `x:Name` (`TopBar`, `PreviewPane`, `LeftPanel`)
AFTER `InitializeComponent`, **before** any method touches it (null-ref guard).
- `_previewPane.IsClickInsidePreview/Drawer` + `_leftPanel.IsClickInside` →
deselection guard in `Window_PreviewMouseLeftButtonDown`.
- `_previewPane.UpdateSelectionOverlay(bool)` + `_leftPanel.OnSelectionChanged(vm)`
→ selection/edit-mode routing from the VM's `SelectedElement` change.
- `_topBar.RefreshAvatar()` on `IsConnected`/`AccountAvatarUrl` change.
- Handlers in moved controls cast `(DataContext as MainViewModel)` — controls
inherit the window's DataContext; never pass the VM through a property.
## Gotchas learned the hard way
- **A UserControl cannot see `Window.Resources`** (StaticResource resolves by
element lookup, so it stops at the control boundary). The 7 converters
(`BoolToVis`, `InverseBoolToVis`, `NotNullToVis`, `NullToVis`, `HiddenBoolToVis`,
`EnumToBool`, `AllTrueToVis`) were promoted from `MainWindow.Resources` into
`Themes/Controls.xaml` (see [`Themes/index.md`](../Themes/index.md)).
- **Moved XAML can lose its namespace**: a block cut from MainWindow carries no
root xmlns, so any `models:`/`Helpers:` ref in it breaks the build with
`MC2000: Value cannot be null (key)`. Declare `xmlns:models` / `xmlns:Helpers`
on each control's root (LeftPanel hit this).
- **Name-looked-up host stubs must stay in the window**, not a control:
`WebViewHostPanel` (ctor `InitWebView2(WebViewHostPanel)`) and `ToastArea`
(`NotificationService.InArea("ToastArea")`) are resolved by name from the
window root — so `OverlayHost` deliberately leaves them as thin window-root
children (Commit 18). Since the 2026-09-14 composition-capture redesign only
`ToastArea` remains — `WebViewHostPanel` is deleted (web sources no longer
host a multiline XAML control; `InitWebView2()` runs from `MainWindow_Loaded`
with no panel argument).
- When cutting visual wrappers, don't drop the outer `Border`'s
background/glow/margin — Commit 15 initially lost the preview's outer border
and had to restore it same-commit.
Related: [`ViewModels/index.md`](../ViewModels/index.md) (MainViewModel the
controls bind to), [`Themes/index.md`](../Themes/index.md) (styles/converters),
[`Helpers/index.md`](../Helpers/index.md) (ViewModelBase, converters), the
`MainWindow.xaml` shell at repo root that hosts these.