PreviewPane.xaml(.cs) takes the center preview (Row 1 Col 1): the 1920x1080
canvas (OverlayCanvas/SelectionOverlay/BrandFlashLayer/SocialBarElement),
the text pull-out drawer, and the live-controls row (TRAX / game / mic meters,
volume sliders, speaker toggles, socials button) inside the preserved
glow/background Border.
Code-behind moved into the control (drive by DataContext==MainViewModel):
preview drag/select/resize/hit-test (Preview_Mouse{Down,Move,Up},
EndPreviewDrag, HitHandle, HitElement), webcam context-menu handlers
(WebcamMenu_ChangeWebcam/SetBorderAnimation/StopBorderAnimation/
FindBorderShape/CreateBorderAnimation/AddDoubleAnimation/SetBorderColor/
HideInScene/Remove), social-bar glow (SocialBar_MouseLeftButtonDown,
SocialsButton_Mouse{Enter,Leave}, SocialGlowTick), audio controls
(VolumeSlider_*, GameVolumeSlider_*, MicSpeaker_*, GameSpeaker_*,
SetSliderValueFromClick, FindParent), and TraxButton_*.
Window keeps the left source/property/chat panel + thumbnail strip + overlay
host, and routes the selection overlay and preview hit-testing through the
control's public surface (_previewPane.UpdateSelectionOverlay(bool),
IsClickInsidePreview/Drawer, PreviewPane.IsDraggableElement). BackgroundMenu_*
stayed in the window because the left-panel source context menu uses them
(the preview's own desktop/refresh menu binds RefreshCaptureCommand instead).
Preserved the outer preview Border (background/glow/margin) that was initially
dropped in the cut — caught and restored before commit. Pruned now-unused usings
(System.Net.Http, Media.Effects, Media.Animation, Media.Imaging, Shapes,
Threading) from MainWindow.xaml.cs.
Zero behavior change; build 0 warnings; 246 pass, only the 2 known failures.
Docs: ViewModels/index.md Phase 2 tracker updated in the same commit.
16 KiB
ViewModels — index
MVVM layer. See schema.md for the memory-map conventions.
Each landed MainViewModel partial owns a row here describing its area; the
MainViewModel.cs row describes only what still lives in core. Docking most
areas' docs in their own rows keeps this index small to read.
| File | Purpose |
|---|---|
MainViewModel.cs |
Core. public partial class MainViewModel : ViewModelBase. Source commands + element rows: scenes are pure selection rows; sources carry edit/visibility/trash (EditElementCommand sets element.IsEditing, ToggleElementVisibilityCommand flips IsVisible, RemoveSourceCommand; Backdrop trash slot reserved via HiddenBoolToVisibilityConverter — TASK 13); shared NextSourceName (Image, Image2, Image3… no-space increment from actual names — used by AddSource + AddReusedImage), AddImageCommand. Lifecycle/ctor wiring: fields + ctor wiring (all command assignments, mixer/camera/chat/hotkey/subscription hookups), Shutdown/SaveLayoutNow/ScheduleSave/LoadLayout, CreateBrush + shared BarOnBrush/BarOffBrush (referenced by Audio/Trax/Socials partials), window-geometry pass-through, SelectedElement/OnSelectedElementPropertyChanged, ShowPreviewPlaceholder, BrandFlash*, WebView2 hosting (InitWebView2/RegisterLoadedWebSources/OnWebView2PreviewBitmapChanged), frame-pump wiring (construction + _rtmpUrlProvider seam + Dispose, plus ResolveOutputFrame → RenderChatBox for chat layers) — the pump's stream-state consumers live in the Streaming partial. |
MainViewModel.Account.cs |
Session + connected account: LoadSavedSessionAsync (DPAPI restore at startup), SignInAsync/ChangeAccountAsync/SignOutYouTubeAsync, IsConnected/AccountAvatarUrl/AccountDisplayName/AccountStatusLightToolTip via SyncConnectedAccount (cleared on End). |
MainViewModel.License.cs |
Polar license (freemium): LicenseKey/LicenseMessage/IsLicenseValidating/IsLicenseEntryOpen/IsHubVisible/IsPremium, OfflineGracePeriod, ValidateLicenseAtStartupAsync/ActivateLicenseAsync. |
MainViewModel.Scenes.cs |
Five-scene catalog: Scenes + StagedScene/LiveScene/IsEditMode, AddScene (canonical names only), MissingScenes/ShowAddScene/AddSceneCommand; scene wiring OnScenesChanged/WireScene/UnwireScene; transitions SelectedTransitionType/TransitionDurationMs/TransitionToScene/ApplyTransitionToLive; global hotkeys HandleHotkey/OpenHotkeyConfig/RefreshHotkeyLabels; scene-gating hints (CanChangeBackground). |
MainViewModel.Background.cs |
Screen backdrop (Live-only by policy): ScreenCaptureManager + ScreenCaptureSourceFactory (WinRT GraphicsCapture); background model EnsureBackground/CreateBackground/NormalizeBackgrounds/HealBackgrounds/StampDefaultBackgroundAsset; screen capture ReacquireScreenCaptures/ResolveAutoCaptureKey/RedesignateBackgroundAsync/ClearBackgroundCaptureAsync/SetBackgroundCapture/OnScreenPreviewBitmapChanged/OnFullscreenMonitorChanged/RefreshBackgroundAutoCapture/BackgroundShowDesktop (keys on full-screen-game monitor or primary display via Win32FullScreenDetector); display picker Displays/SetBackgroundDisplayCommand; preview fns ActiveBackgroundImage/BackgroundImage/UpdateActiveBackground/UpdateBackgroundImage/RefreshSnapshotsAsync/LoadBackgroundImage/CreatePlaceholderSnapshot; ChangeCaptureCommand/RefreshCaptureCommand/ToggleShowDesktopCommand/BrowseBackgroundCommand. ShowPreviewPlaceholder stays in core. |
MainViewModel.Webcam.cs |
Webcam identity: _webcam/ReacquireWebcam/SwapWebcamIdentityAsync; per-scene configs AddWebcamToStagedSceneAsync/ShowWebcamInStagedScene/ChangeWebcamAsync (Windows picker always opens); capture OnCameraPreviewBitmapChanged/OnCameraFailed; gating CanAddWebcam/CanChangeWebcam/CanShowWebcamInStagedScene/IsWebcamSelected/WebcamError; size safeguards WebcamMax*/ClampWebcamToBounds/HealLegacySquareRect; AddWebcamCommand/ChangeWebcamCommand/ShowWebcamCommand. |
MainViewModel.Audio.cs |
Mic bar: read-only realtime meter AudioLevel/MeterFillWidth/MeterBrush (fill = Math.Min(1, AudioLevelMeter.ToDisplay((float)AudioLevel) * MicVolume), green→yellow→red at 60%/80% zones), MicVolume (0.8 default; drives MicMuted ⇔ volume 0; unmute flash BeginVolumeFlash/EndVolumeFlash/CancelVolumeFlash), MicSourceName (left-justified inside the bar, persists via LayoutStore.Settings), ToggleMicMute/PickMicrophone (MicPickerDialog; picked device swaps the live source via _audioMixer.RestartMic(), loopback keeps running), status dot MicStatus/MicStatusBrush/MicStatusToolTip (Models.MicStatus: green Connected / yellow Problem / red no-device), capture starts once at startup (StartMicCaptureAsync) — NOT go-live. Game audio bar: overlaid bottom-center, always visible (show/hide gating removed 2026-08-15); GameAudioLevel (loopback via OnLoopbackLevelChanged), GameAudioVolume/GameMuted/GameMuteText/ToggleGameMute/PushSystemVolume, mirror volume flash SetGameVolumeAdjusting/BeginGameVolumeFlash/EndGameVolumeFlash/CancelGameVolumeFlash. Mixer handlers OnMicLevelChanged/OnLoopbackLevelChanged/OnMicConnected/OnMicFailed. |
MainViewModel.Trax.cs |
Background music (TASK 9, rides the loopback into the stream): _musicPlayer (MusicPlayer), IsMusicPlaying/TraxError/TraxDotBrush/TraxToolTip, interaction TraxLeftClick/TraxRightClick/PickTraxTrack, playback LoadTrax/PlayTrax/PauseTrax/OnTraxPlaybackEnded/RefreshTraxUi, UpdateTraxLocalGain (mirrors the game bar's mute onto MusicPlayer.LocalGain so the track ducks/restores with desktop audio). |
MainViewModel.Socials.cs |
Social bar (global resource, overlaid on every scene): _socials/_socialBarFrame/_socialValidator (ISocialValidator/HttpSocialValidator), HasSocials/SocialBarVisible/SocialBarDotBrush/SocialBarGlowBrush/Socials/SocialBarTop, OpenSocialDialogCommand/OpenSocialDialog (spawns SocialsDialogViewModel + window; saved copy replaces _socials), NotifySocialsChanged/RenderSocialBarFrame (SocialBarRenderer → immutable _socialBarFrame the frame pump reads), fediverse heal HealFediverseSoftwareAsync (static, test-facing) + HealFediverseSoftwareInBackground, bar edge toggle SetSocialBarPosition/ToggleSocialBarPosition. BarOnBrush/BarOffBrush/CreateBrush stay in core (shared tokens). |
MainViewModel.Streaming.cs |
Go-live/record + health + quality tiers + session UI. Stream state: StreamStatus/StreamVisibility setters + _streamStatus field, IsOffline/IsLive/IsLivePrivate. Go-live flow: StartSession (routes by pill state) → BeginRecordOnly (signed-out local recording) or BeginGoLive (GoLive dialog → captures title/description/visibility + BroadcastForm.CaptureGoLive → PrepareAndStartLiveAsync: reusable stream → CreateBroadcast (stores _currentBroadcastId) → empty-metadata warn → chat poll start (liveChatId) → _framePump.StartAsync() + health poll; failures → StreamStatus.Error, never a crash); StartRecordFile (auto-named ty-…-0000.mp4) + FinalizeRecordingAsync (rename modal, UniquePath); StopStream (posts Offline, stops chat/audio pipe/pump, clears _currentBroadcastId; session/token deliberately NOT cleared — sign-out stays explicit). Health: CurrentHealth/ResetHealth/ApplyHealth/OnFramePumpHealthUpdated (marshal FramePump.HealthUpdated off the stderr thread), PollHealthAsync/OnHealthPollTick/ApplyHealthIssue (report-by-exception banner HealthIssueBanner/HealthIssueBackground via StreamHealthReporter). Session timer: _liveTimer (ctor-wired) UpdateSessionTimer/OnLiveTimerTick/LiveElapsedText/_recDotPulse. Start/End UI: REC/ON-AIR pills (RecordPillOn/OnAirPillOn/CanToggleOnAir), ShowPrimaryStartButton/CanStartSession/ShowEndStreamButton/EndButtonText/ShowLogInButton, REC sign RecDotBrush/RecTextBrush/RecDotOpacity/OnAirBrush, live window tokens WindowTitle/TopBarBackground/PreviewGlow* via UpdateLiveVisuals; ChooseRecordFolder/ResetRecordFolder/_recordFolder/RecordFolderDisplay/DefaultRecordFolder. Resolution dropdown: QualityOptions/SelectedQuality/ResolutionHelp/ApplyStreamQuality + OutputRect*/DimRects/IsOutputCropped/ResolutionBadgeText over the 1920×1080 master. Pipeline bits: _framePump/_liveTimer/_healthPollTimer fields, BuildEncoderOptions. Commands StartStreamCommand/EndStreamCommand/ChooseRecordFolderCommand/ResetRecordFolderCommand. Broadcast pull-out: CurrentBroadcastId/BroadcastForm. |
MainViewModel.Overlays.cs |
Settings/bug/feature/about panels + creator-hub links: panel state IsSettingsOpen/IsBugOpen/IsFeatureOpen/IsAboutOpen (via SetPanel → IsAnyOverlayOpen), IsLicensesOpen (flips IsHubVisible), OverlayTitle, ShowOverlay (one-panel exclusivity) + ShowLicenses (loads shipped THIRD-PARTY-NOTICES.txt into LicensesText on first open, never the OS viewer); bug/feature report BugReportText/BugReportEmail/FeatureRequestText/FeatureRequestEmail + SubmitBug/SubmitFeature/ComposeEmail/OpenUrl; About hub consts ChannelUrl/MastodonUrl/CoffeeUrl (public, integration-test live) + PremiumUrl (tabled billing seam — TASK 12 → greyed "Unlock Premium" lights up when filled) + IsPremiumAvailable + SupportEmail + AppVersionLabel; commands OpenSettingsCommand/OpenBugCommand/OpenFeatureCommand/OpenAboutCommand/CloseOverlayCommand/SubmitBugCommand/SubmitFeatureCommand/OpenChannelCommand/OpenMastodonCommand/OpenCoffeeCommand/OpenPremiumCommand/OpenLicensesCommand. Ctor command assignments stay in core. |
MainViewModel.Chat.cs |
Chat overlay + preview pipeline: ChatMessages (500-message cap; clear-on-fade), OnChatMessageReceived (marshal _youtubeChat.MessageReceived onto the dispatcher, switch previews mock→real), fade _chatFadeTimer/ResetChatFadeTimer (shortest ChatFadeSeconds across chat layers); live render RenderChatBox (called from ResolveOutputFrame, stays in core) + preview render UpdateChatBoxPreview (bitmap onto chatBox.VideoImageSource); mock preview MockChatMessages/StartMockChatPreview/StopMockChatPreview/RunMockChatPreviewAsync/_mockChatCts; UpdateActiveChatBoxPreviews (wires Chat* property-change subscriptions + first render); CanAddYouTubeChat (one-per-layout gate), ShowChatInactiveMessage (!IsLive). _chatBoxRenderer field. Ctor wiring (MessageReceived hookup, new ChatBoxRenderer(), LoadLayout preview start, _youtubeChat.Dispose) stays in core. |
GoLiveViewModel.cs |
Start Stream dialog: account row (saved channel shown with Change Account, or Sign in to YouTube; Start gated on IsSignedIn/IsBusy) + title/description + locked Visibility (always "Private" — ship step 7, no dropdown), start/cancel requests |
About hub (TASK 11, in MainViewModel.Overlays.cs) |
the About overlay is the in-app creator hub: ChannelUrl/MastodonUrl/CoffeeUrl (public consts, live) + PremiumUrl (tabled billing seam — TASK 12 → the greyed "Unlock Premium" button lights up when filled); OpenLicensesCommand → ShowLicenses() loads the shipped THIRD-PARTY-NOTICES.txt into LicensesText and sets IsLicensesOpen (in-app scrolling panel, never the OS viewer); BackToAbout_Click in MainWindow.xaml.cs flips it off. |
ReuseImageViewModel.cs |
Add Image dialog: candidate list (ReuseImageCandidate), reuse/new/cancel |
CameraPickerViewModel.cs |
Add Webcam dialog: async camera list (loading / has / none states), UseRequested(CameraDeviceInfo)/CancelRequested |
MicPickerViewModel.cs |
Select Microphone dialog (voice source): async mic list (loading / has / none states), UseRequested(MicrophoneDeviceInfo)/CancelRequested |
SocialsDialogViewModel.cs |
Social Media Site Promotion dialog (six fixed slots, SocialSlotViewModel): row 0 = YouTube sign-in gate (sign-in provider fills the channel, delete = sign-out); rows 1-5 validated entries / empty / freemium locks 2-5 (isPremium seam); DetectService → ISocialValidator on confirm (input snaps to text + real logo on success, red do-not + stays editable on failure); CanSave blocked while any slot edits/validates/errors; BarEnabled switch; commit via CommittedEntries + BuildSocialsConfig() (YouTube first) + SaveRequested/CancelRequested. WPF-free — the window owns the dialogs (delete confirm, sign-in gate) and calls StartEdit/ConfirmEdit/DeleteSlotAsync. |
Related: Models/index.md for the data; Services/index.md
for what the ViewModels drive; base class in Helpers/ViewModelBase.cs.
MainViewModel partial split (in progress)
MainViewModel.cs is being split into one file per functional area, zero behavior
change, one commit per partial (each commit a roll-back point). Commit naming:
titles carry the series id (Commit N/12), tags are refactor-commit-N — e.g.
git show refactor-commit-1. Every partial lands with a ViewModels/index.md
update in the SAME commit. Each partial's docs live in its table row above; this
tracker is just the code↔commit map.
Progress (Commit N/12 · tag):
- Commit 1/12 ·
refactor-commit-1— Account →MainViewModel.Account.cs - Commit 2/12 ·
refactor-commit-2— License →MainViewModel.License.cs - Commit 3/12 ·
refactor-commit-3— Scenes →MainViewModel.Scenes.cs - Commit 4/12 ·
refactor-commit-4— Background →MainViewModel.Background.cs - Commit 5/12 ·
refactor-commit-5— Webcam →MainViewModel.Webcam.cs - Commit 6/12 ·
refactor-commit-6— Audio →MainViewModel.Audio.cs - Commit 7/12 ·
refactor-commit-7— Trax →MainViewModel.Trax.cs - Commit 8/12 ·
refactor-commit-8— Socials →MainViewModel.Socials.cs - Commit 9/12 ·
refactor-commit-9— Streaming →MainViewModel.Streaming.cs - Commit 10/12 ·
refactor-commit-10— Chat →MainViewModel.Chat.cs - Commit 11/12 ·
refactor-commit-11— Overlays →MainViewModel.Overlays.cs - Phase 1 complete: core
MainViewModel.cssplit into 10 functional partials (1,890 → 758 lines).
Phase 2 — MainWindow.xaml split into UserControls (new Controls/ folder, namespace ytLive.Controls):
- Commit 12 ·
refactor-commit-12— BottomBar →Controls/BottomBar.xaml(.cs)(gear menu + stream stats + quality dropdown;GearButton_Clickmoved to the control's code-behind). - Commit 13 ·
refactor-commit-13— HealthBanner →Controls/HealthBanner.xaml(Row 1). Also promoted the 7 value↔visibility converters out ofMainWindow.Window.ResourcesintoThemes/Controls.xamlso extracted controls can resolve them (app-level, not window-level). - Commit 14 ·
refactor-commit-14— TopBar →Controls/TopBar.xaml(.cs)(Row 0: logo, REC/ON-AIR pills + signs, avatar, Sign In/Start/End buttons). Avatar fetch moved (async-preserved) into the control; window exposes_topBar.RefreshAvatar()invoked onIsConnected/AccountAvatarUrlchanges. - Commit 15 ·
refactor-commit-15— PreviewPane →Controls/PreviewPane.xaml(.cs)(Row 1 Col 1 center preview pane). Moved the 1920×1080 canvas (OverlayCanvas/SelectionOverlay/BrandFlashLayer/SocialBarElement), text pull-out, and the live-controls row (TRAX/game/mic meters + sliders + speakers + socials button). Code-behind moved: preview drag/select/resize/hit-test (Preview_Mouse*, HitHandle, HitElement), webcam-context-menu handlers (WebcamMenu_), social-bar glow (SocialBar_, SocialsButton_, SocialGlowTick), audio sliders/speakers (VolumeSlider_, GameVolumeSlider_, MicSpeaker_, GameSpeaker_, SetSliderValueFromClick, FindParent), TraxButton_. Window keeps the source-list left panel + thumbnail strip + edit UI, and routes selection-overlay via_previewPane.UpdateSelectionOverlay(bool)plus_previewPane.IsClickInsidePreview/Drawer.BackgroundMenu_*stayed in the window (used by the left-panel source context menu). Pending: thumbnail strip, left scene/chat panel, overlay host.
The partial .cs files land next to MainViewModel.cs in this folder as the split
proceeds; each partial carries its own usings and re-declares nothing from core.