Files
LlamaCasty/Controls/index.md
T
gramps c01206fb8a feat: web sources frame-captured via composition (CoreWebView2CompositionController → Windows.Graphics.Capture); PNG poll + CaptureScheduler deleted
The ~30Hz CapturePreviewAsync PNG poll capped real cadence at ~20Hz
(35–165ms full-HD encode+decode), so a 60fps widget still juddered at ~1/6
speed. Replaced polling with frame-driven capture of the composition
controller's root visual — the mechanism WebView2CompositionControl and
Flutter's webview_windows use (graphics_context.cc captures the root
surface_ visual via CreateGraphicsCaptureItemFromVisual; reference:
github.com/microsoft/Windows.UI.Composition.WinUI / flutter-internal
webview_windows). Frames now arrive at the renderer's own pace; capture
memory is epoch'd ring reuse + one crop-sized shared WriteableBitmap.

New Services/WebCaptureFrameSource.cs owns GraphicsCaptureItem + free-
threaded Direct3D11CaptureFramePool + session (Straight alpha readback,
per-frame FindContentBounds → CropBounds). WebView2Manager reworked around
per-session composition controllers + one UI-thread Compositor created via
the CoreMessaging CreateDispatcherQueueController P/Invoke (the 19041
projection lacks CreateOnCurrentThread); internal seam ctor
(Dispatcher, Func<string,IScreenCaptureSource>?) for hermetic tests.
CaptureScheduler.cs deleted; the three SetCaptureInterval cadence hooks
removed; InitWebView2() moved from MainWindow ctor to Loaded (a parent
HWND must exist for the composition controller); the hidden WebViewHostPanel
overlay deleted. TransparentBackgroundScript unchanged.

Tests: WebView2ManagerTests reworked — 4 control-size + scheduler tests
dropped, FindContentBounds tests moved to WebCaptureFrameSource, ONE
integration test (Frames_PublishCroppedPreview_And_CoalesceToLatest_CarryingCropBounds)
drives the seam with a FakeWebSource + background-STA DispatcherPump.
Suite 293/293, 0 warnings.

NOTE: composition path NOT yet verified on a device — the take is the
next step. Web work committed locally only (no push per standing rule).

Docs same-commit: ai.md Slice 14 + supersede marker on Slice 11, HANDOFF,
MyMistakes (CoreMessaging DQ + namespace-landmine recipe), TASK 17,
Controls/ViewModels/Services indexes.
2026-09-14 16:14:00 -07:00

4.1 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): gear menu (stop/mic/game-mute/quality dropdown), stream stats (bitrate/fps/dropped/duration/health), resolution dropdown
HealthBanner.xaml(.cs) Row 1 status banner + the 7 value→visibility converters live in the app theme, not here
TopBar.xaml(.cs) Row 0 stream controls: REC/ON-AIR pills+signs, elapsed timer, start/stop, account avatar/name (RefreshAvatar())
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)
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
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.