The creator's locked table (2026-10-17) gives the Chat scene its own rules in the 9:16 tier: RECT ONLY - the circle option is not offered - and a size that is either the normal small tile or ENLARGED to fill one whole 607x540 area (top or bottom). Unit 1 shipped the per-scene placement; this unit is the Chat scene's own variant of it. The rule lives on the MODEL, not on the button: - Models/SceneElement.cs: ClipShape and ToggleClipShape() are now virtual. - Models/WebcamSceneConfig.cs: VerticalShapeLocked (set per scene by the VM) OVERRIDES the ClipShape property, so no writer can slip a circle in - the toggle, the Shape button and the placement swap all go through it. The lock is cleared BEFORE the 16:9 layout comes back, so the desktop shape returns. - ViewModels/MainViewModel.Webcam.cs: CanToggleClipShape (the circle is NOT offered in the vertical Chat layout - the Shape button disappears, it is not merely greyed out), IsChatSceneCamera, ResizeWebcamInVerticalTier. - Models/VerticalCameraLayout.cs: FillsArea (the "enlarged" state, derived - no new column) and SizeFor - the Chat camera's TWO sizes, the tile or the whole area, whichever is nearer the requested width, so the resize handle has two detents instead of any size in between. - An enlarged camera FILLS the area its centre is in (top or bottom), so dragging it up/down swaps the areas. - The Chat camera is auto-tiled like the Live one when the vertical tier is picked - otherwise the vertical chat stream has no camera at all. - Controls/PreviewPane.xaml.cs: the resize routes through the VM; the drag assigns, lets the model clamp into the band, then snaps (so the enlarged camera can be dragged from the top area to the bottom one). Test: ytLive.Tests/VerticalChatCameraLayoutTests.cs - the ONE Good Dog test for the unit: the vertical tier is picked -> the Chat camera comes into the band as a small RECT tile -> the circle option is not offered and cannot be set even on the model -> dragged up and enlarged it fills the TOP area exactly -> back to 16:9 the desktop layout is restored exactly and the circle option is offered again. 430/430 tests pass, 0 warnings. Research (the creator's table is the established practice, not our invention): - Streamlabs' vertical guide: "splitting the screen in half horizontally, placing the camera feed in the top half and the main content on the bottom (or vice versa) may be the bet", and a just-chatting vertical wants the camera "to take up most of the screen" (streamlabs.com/content-hub/post/overlays-vertical-and-horizontal-streaming) - Bloopbot's vertical overlay docs describe the same per-layer model we use at scene granularity: "move or resize a layer on Vertical and it stops being Auto-placed. It keeps the place you gave it, and the horizontal layout is not touched" (bloopbot.com/docs/vertical-overlays-twitch-dual-format) - our swap-at-tier-change, stored per scene. - Landmine recorded: "about the bottom 560 px of the vertical video can be covered by Twitch's chat and player controls on phones" (Bloopbot) - the Chat camera's enlarged BOTTOM area may be half-hidden by the platform chrome; TASK 53's safe-zone work decides that. Memory in the same commit: ai.md (the Chat rules, the shape lock, the tests), TASKS.md + TASKS/task-57-vertical-camera-layout.md (TASK 57 complete), HANDOFF.md, MyMistakes.md (3 recipes: lock the model property not the UI button; two detents for a discrete size set; the edit tool cannot match an oldString with an em dash), Models/ViewModels/Controls index rows.
5.4 KiB
5.4 KiB
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 (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 |
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) AFTERInitializeComponent, before any method touches it (null-ref guard). _previewPane.IsClickInsidePreview/Drawer+_leftPanel.IsClickInside→ deselection guard inWindow_PreviewMouseLeftButtonDown._previewPane.UpdateSelectionOverlay(bool)+_leftPanel.OnSelectionChanged(vm)→ selection/edit-mode routing from the VM'sSelectedElementchange._topBar.RefreshAvatar()onIsConnected/AccountAvatarUrlchange.- 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 fromMainWindow.ResourcesintoThemes/Controls.xaml(seeThemes/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 withMC2000: Value cannot be null (key). Declarexmlns:models/xmlns:Helperson each control's root (LeftPanel hit this). - Name-looked-up host stubs must stay in the window, not a control:
WebViewHostPanel(ctorInitWebView2(WebViewHostPanel)) andToastArea(NotificationService.InArea("ToastArea")) are resolved by name from the window root — soOverlayHostdeliberately leaves them as thin window-root children (Commit 18). Since the 2026-09-14 composition-capture redesign onlyToastArearemains —WebViewHostPanelis deleted (web sources no longer host a multiline XAML control;InitWebView2()runs fromMainWindow_Loadedwith 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.