Files
LlamaCasty/Controls/index.md
T
gramps 6be062c45f vv webcam Unit 1: the vertical (9:16) tier keeps its OWN camera placement per scene
The vertical tier is a centred band crop of the SAME 1920x1080 master (band x
656.25, w 607.5, h 1080), so the 16:9 camera placement (x~1408) falls outside the
cropped window and the creator is simply not in the vertical stream. Each scene
now keeps its own vertical position, size and shape; switching back to 16:9
restores the desktop layout exactly (the creator's locked table, 2026-10-17).

Geometry only - NO render-path change. The preview and the compositor both read
the element's own X/Y/W/H + ClipShape, so the tier switch swaps the LIVE geometry:
- Models/VerticalCameraLayout.cs (new): the pure math - the band, its two 607x540
  areas, the six spots (top/bottom x left/centre/right), the 32px inset and the
  270x152 default tile (480x270 in the 1080x1920 output) in the bottom-right spot,
  the corner the 16:9 default webcam uses.
- Models/WebcamSceneConfig.cs: the vertical placement lives in seven additive
  columns (VerticalX/Y/Width/Height/ClipShape/RectWidth/RectHeight; NULL = "this
  scene has no vertical placement yet"). The 16:9 layout stays in the old columns
  and is parked in the transient Landscape* fields while the vertical tier is
  live; Begin/EndVerticalLayout + CommitLiveLayoutToVertical do the swap. The
  X/Y/W/H overrides clamp every write into the band - outside it the camera is
  invisible in the vertical output.
- Models/SceneElement.cs: X/Y/Width/Height became virtual so the derived element
  constrains every writer (drag, resize handle, typed fields) in ONE choke point
  instead of three writers each re-implementing the rule.
- ViewModels/MainViewModel.Webcam.cs + .Streaming.cs: IsVerticalTier/VerticalBand,
  ResolveVerticalCameraLayout(s) (the tier swap), MoveWebcamInVerticalTier /
  SnapWebcamInVerticalTier (the six-spot snap, by the tile's centre),
  EnsureVerticalCameraLayout; ApplyStreamQuality is the tier-change hook. The
  Live scene gets its small tile automatically on the first vertical switch (the
  vertical stream would otherwise have no camera at all); the other scenes keep
  their 16:9 placement until the creator drags them into the band.
- Controls/PreviewPane.xaml.cs: the drag/resize route through the VM snap under
  the vertical tier instead of free-floating over the master.
  Controls/LeftPanel.xaml: the webcam properties gained a Geometry section
  (X/Y/W/H) for the manual entry the creator asked for, bound to the LIVE
  placement, gated on the VM's IsWebcamSelected (NOT SelectedElement.IsWebcam -
  the TASK 51 test finds that panel by binding text and would match twice).
- The store: seven guarded ADD COLUMNs, the INSERT/SELECT extended, and the
  X/Y/W/H/ClipShape columns stay the 16:9 layout - while the vertical tier is
  live the save writes the parked values there and the live ones into Vertical*.

Test: ytLive.Tests/VerticalWebcamLayoutTests.cs - the ONE Good Dog test. The
vertical tier brings the Live camera INTO the band as a small tile; dragging it to
the top-right lands it top-right inside the band; back to 16:9 the desktop layout
is restored exactly (1408/778/480x270); both placements survive a save + reload.
Full suite 429/429, 0 warnings. LayerReorderPersistenceTests.RealMouseDrag fails
ALONE on a clean tree in this environment - pre-existing flake, proven by stashing
this work, not a regression.

Derivative work: OBS has no built-in per-aspect-ratio layout storage - the
maintainers' answer is "create a second scene and add the scene you want to send
as source" (obsproject.com/forum/threads/161307) - and Streamyard's dual-aspect
guide says the same ("no built-in way to store separate layouts per aspect ratio",
so you rely on manual workarounds and plugins):
https://streamyard.com/blog/dual-aspect-ratio-streaming-tool
Our per-scene, per-tier stored placement, resolved on the tier change, is the
step past both.

Memory in the same commit: ai.md (new section), TASKS.md + TASKS/task-57-*.md
(Unit 1 shipped, Unit 2 = the Chat scene still open), HANDOFF.md, MyMistakes.md
(the per-tier geometry-swap recipe, the binding-text panel trap, the store's
webcam-identity rule) and the Models/Services/ViewModels/Controls index rows.
2026-10-21 08:58:24 -07:00

5.2 KiB
Raw Blame History

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 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
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
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)
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).
  • 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 (MainViewModel the controls bind to), Themes/index.md (styles/converters), Helpers/index.md (ViewModelBase, converters), the MainWindow.xaml shell at repo root that hosts these.