MainViewModel.Chat.cs (194 lines) takes the YouTube chat overlay: ChatMessages (+500 cap, clear-on-fade), OnChatMessageReceived (dispatcher marshal, mock→real preview switch), chat fade timer (ResetChatFadeTimer), the ChatBox render paths (RenderChatBox for the live compositor, UpdateChatBoxPreview for the editor preview), the mock preview loop (MockChatMessages/StartMockChatPreview/ StopMockChatPreview/RunMockChatPreviewAsync/_mockChatCts), UpdateActiveChatBoxPreviews, CanAddYouTubeChat (one-per-layout gate) and ShowChatInactiveMessage. Core drops 1119 -> 940 lines. Ctor wiring (MessageReceived hookup, ChatBoxRenderer ctor, LoadLayout preview start, dispose) stays in core; ResolveOutputFrame (core) still calls RenderChatBox cross-partial. Zero behavior change; build 0 warnings; 246 pass, only the 2 known failures. Docs: index.md Chat row added + core row trimmed in the same commit.
13 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. Overlays/lifecycle: fields + ctor wiring (all command assignments, mixer/camera/chat/hotkey/subscription hookups), Shutdown/SaveLayoutNow/ScheduleSave/LoadLayout, SetPanel/ShowOverlay/ShowLicenses, CreateBrush + shared BarOnBrush/BarOffBrush (referenced by Audio/Trax/Socials partials), OpenUrl/ComposeEmail/SubmitBug/SubmitFeature, 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.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) |
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 — Overlays (settings/bug/feature/about panels + license entry)
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.