Commit Graph
11 Commits
Author SHA1 Message Date
gramps fdb27f0758 feat(ui): gear to the rail, EDIT on the panel edge, preview undo/redo
Three creator directives (2026-11-01), done ahead of the surge.

**Gear moved to the preview rail** — first tab, orange (Brush.Attention), gear icon
only. Left-click opens the same app menu the top bar gear owned (Settings / Bug /
Feature / About), right-click goes straight to App Settings; both behaviours moved
with it, so nothing lost an entry point. TopBar keeps the wordmark and EDIT.

**EDIT right edge aligned to the layers panel.** The work area is margin 8 + a 360px
column, the top bar is padding 16, so the shared edge is 352 inside the bar. TopBar
column 0 is now a fixed 352 grid with EDIT right-aligned in it — alignment by
arithmetic instead of by eye.

**Preview undo/redo: Ctrl+Z / Ctrl+Shift+Z.** Models/PreviewHistory snapshots element
REFERENCES plus geometry, visibility, order and membership — a deep clone would mirror
every property of a dozen element subclasses and quietly miss the next one added.
One snapshot per gesture (pushed at drag/resize start, never in the move loop), bounded
at 50, redo survives multiple undos, cleared on layout load. Gated on EditingActive:
undo while the preview shows program output would rewrite a scene nobody is looking at.

Honest scope, in the type's doc comment: layout edits only. Colour/opacity/border/font
edits are not captured yet, so Ctrl+Z after one of those restores the last layout change.

6 tests in PreviewHistoryTests; 19 pass across it and the neighbouring UI classes; Debug
and Release both build at 0 warnings.
2026-11-01 07:46:10 -08:00
gramps deb74529a6 feat(scoreboard): unit 6 — it is a LAYER (source type, strip geometry, resolver, session hook)
Adds SourceType.Scoreboard with the same per-scene eye toggle, drag and presence as every other
element — 'prominent on BRB, dialled back on Live' is geometry, not a second mechanism. Default
geometry is the strip: 1920 wide, 56 tall, bottom edge.

The output resolver returns the presenter's cached frame exactly like the chat box and alert box do,
and every session start clears the tallies. Preview publication writes one shared WriteableBitmap on
the UI thread (the pump thread must never touch one), refreshed only when a displayed value moves.

Guard: ScoreboardLayerTests pins the enum member, the geometry constants, the resolver case and the
session hook, so the layer cannot quietly become a special widget.
2026-10-30 14:40:31 -07:00
gramps 841c905c06 Brand logo: one locked image layer per scene above the background, image from App Settings
- Source.IsBrandLogo persisted (additive migration); layer healed on load, removal blocked
- Image stored as an asset; settings keep the asset id and file name
- App Settings: Brand Logo file field with Browse
- Default placement top-left; per-scene eye uses the existing element visibility
Test: BrandLogoTests; layout/background tests pass (34). Not pushed.
2026-10-27 09:25:49 -07:00
gramps 3239808a73 Layers panel and scene strip: scene layers header with icons, per-type layer icons, PROGRAM badge and selected/live orange borders, scene name and hotkey overlays, Starting as default scene
- LeftPanel: SCENE LAYERS header with layer count; per-type blue icons; properties header gear; layers list min/max height; properties placeholder; list and properties indent
- SceneThumbnailStrip: names/hotkeys over the tile; PROGRAM badge with blinking red dot on the selected tile; orange border on selected and live tiles; fixed the element border override that hid the style border; reflag tiles on load
- LiveScene is now set on go-live, record start, and live transitions (it was never assigned, so IsLive never fired)
- Startup stages the scene named Starting
- DESIGN.md: color roles (red = YouTube/on-air/record, orange #ff8c3a = attention, amber = game audio/Super Chat)
- Staged live tile uses background art instead of the green LIVE placeholder

Not pushed.
2026-10-26 14:00:23 -07:00
gramps de22983b36 TASK 51 slice 4: camera-path hookup — CameraManager renders per-scene effect variants (one segmentation per raw frame on a latest-frame-drop worker, raw path untouched), VM declares/adopts variants per config; separable upsampling cuts Remove 46->3 ms, Blur 80->18 ms (Debug, 720p) (Unit: bg-effects hookup) 2026-10-09 10:27:38 -07:00
gramps d4aa588da0 feat(alerts): TASK 43 — native events & alerts (six unique animations) + chat parity
Replace the external StreamElements feed with NATIVE YouTube events per the creator's
2026-09-23 question.

Chat parity half:
- YouTubeChatService decodes ALL SIX liveChat/messages event types into
  ChatMessage.Kind (superChat/superSticker/newSponsor/membershipGifting/
  giftMembershipReceived/memberMilestoneChat) — the four formerly-empty overlay
  rows are real now — and re-arms its one-shot poll on the server's
  pollingIntervalMillis (streamList connection semantics; clamp 1000-6000ms,
  maxResults=2000). ParsePage internal static seam + ChatPage record for
  deterministic tests; optional HttpClient ctor seam kept; InjectSimulatedMessage
  (TASK 41) preserved.

Alerts half (creator rulings: one celebration zone, six UNIQUE animations, no
menus/polls, sub mention = chat row only, NO viewer count):
- New SourceType.AlertBox 'Stream Alerts' (one per layout, CanAddAlerts gate
  mirroring chat; idle = transparent).
- AlertRenderer: six distinct branded animations (SuperChat slide-up/shine/
  count-up, SuperSticker scale-pop, NewMember drop-in/flash, MemberGift slide-left/
  chip-fan, GiftReceived confetti, MemberMilestone rise/growth-bar), every card
  drawing the 'made with LlamaCasty!' brand line.
- AlertOverlayLayer: true component (Commit-G pattern) — queue (cap 10, drop tail
  never stall), 33ms UI ticker, cache-first RenderFrame + UpdatePreview,
  Advance(double) as the deterministic test clock; ChatEventKind.None rows never
  enqueue.
- Wired: resolver RenderAlertBox, _alertLayer ctor + dispose, LoadLayout previews,
  Add menu item (Controls/LeftPanel.xaml), TestSessionViewModel sims tagged
  (member->NewMember, superchat->SuperChat).

Good Dog ONE integration test: AlertLayerTests (RealApp STA, real WPF raster) —
ParsePage classifies all six kinds + cadence fields; None rows enqueue nothing;
six events play pairwise-distinct moving frames then drain to null.

Gate: clean build 0 warnings; full suite 316/317, the one failure
(LayerReorderPersistenceTests.RealMouseDrag) repros on the clean tree — the
known environmental class (real-mouse-drag no-ops with a game/fullscreen window
focused).

References (OBS/overlay ecosystem):
- streamList semantics: https://developers.google.com/youtube/v3/live/docs/liveChatMessages/streamList
- OBS alert-box pattern (designated celebration zone, idle transparent): creator-chosen model
2026-09-24 08:27:00 -07:00
gramps f91bf8715d fix(reorder): save layer list AND preview layout immediately at drop; recording save dialog +10% (276→304)
Reorder: OnSceneElementsReordered now calls SaveLayoutNow() (was debounced
ScheduleSave) so a drag-drop persists instantly — one write stores the new
layer list (Source.SortOrder) and each layer's preview geometry (X/Y/W/H) in
the same rows. Reference: WPF ListBox drag-reorder requires an explicit persist
at drop; a debounce window lets a crash lose the drop.

Recording save dialog (RenameRecordingDialog.xaml): height 276→304 so the
Cancel/Save button row is no longer obscured; Save button named
SaveRecordingButton so the sizing test can assert it sits inside the client area.

Tests: deterministic seam test now asserts the dragged layer's geometry (preview
layout) is persisted with the new order; sizing test asserts 304 + textbox and
button-row bottoms within client area. 308/308 green.
2026-09-18 10:20:25 -07:00
gramps c11788574e fix(reorder): trap layer-list drag drop as a data change and persist it
List_PreviewMouseMove reorders StagedScene.Elements via RemoveAt/Insert,
bypassing SceneGraph's mutation surface, but never scheduled a save — the
new z-order was lost on restart. The drop now sets _dragReordered and
EndListDrag funnels it through MainViewModel.OnSceneElementsReordered()
(InvalidateBake + ScheduleSave, same background-save path as every other
mutation). Integration test (RealApp + temp DB) reproduces the exact
code-behind mutation and asserts the debounced save lands the new Source
SortOrder. Derivation reference: standard WPF ItemsControl drag-reorder
pattern (OBS layering semantics: bottom-most layer = index 0).
2026-09-18 08:18:51 -07:00
gramps 9d00955004 feat(webcam): app-default gate slice — per-scene offer, Add places default directly, identity survives removal
Chat's camera no longer greys Web Cam in Live (per-scene max, not app-wide);
TASK 26 superseded by creator directive (app-level resource model). Add Webcam
now places the existing app default without the picker; the picker runs only
for the initial selection. Removing the last placement keeps the identity
(_webcam never nulled) so Add stays offered. Startup pass adopts a solo camera
as the app default, so a clean layout offers the layer in Live/Chat at once.
Dynamic WebcamAddToolTip names the why (incl. the 'graduated to OBS' line).

Good Dog: WebcamMenuGateTests rewritten (real app + temp DB + camera seams) —
identity in Chat does not gray Live; Add in Live places same wc-1 no picker;
scene-with-placement stays gray; identity survives both removals (DB row 1).
WebcamStartupResourceTests single-lock fact asserts CanAddWebcam after adoption.
Docs same commit (ai.md supersession, TASKS.md, HANDOFF.md). 304/304, 0 warnings.
2026-09-17 08:56:04 -07:00
gramps 670fe3a10a TASK 31: SceneGraph component + baked-crust compositor optimization
- Add ElementKind (Static/Dynamic) to SceneElement base; Source/WebcamSceneConfig classify
- Services/SceneGraph.cs: owns the Scenes collection (ViewModel's Scenes delegates to it),
  the element mutation surface (Add/Insert/Remove/Move, each invalidating the bake), and the
  queries that were scattered LINQ (GetBackground/GetWebcam/GetChatBoxes/GetSplitPoint/IsStatic)
- SceneCompositor: split-aware BakeStaticBase + CompositeLayers + Render(.., staticBase, split);
  builds/caches the static base below the split point in source-rect space
- FramePump: optional SceneGraph -> optimized bake+composite path; falls back to full render
- MainViewModel: routes element mutations through the graph; invalidates the bake on static
  layout/opacity/visibility/useDefaultBackground changes and after background heal/Ensure
- Integration test SceneGraphTests.BakedStaticBase_WithDynamicLayer_CompositesCorrectly

Derivative survey (mandated): tried before writing — OBS does per-source opacity/visibility
caching and static-scene baking; this mirrors OBS's 'cached static source' optimization.
3 documented defensive deviations from the spec (ChatOverlayLayer stays decoupled;
background helpers stay VM-static for direct testability; full facade peel deferred post-1.0)
in TASKS.md + ai.md. Scope check passed; clean build 0 warnings.
2026-08-31 17:53:34 -07:00
gramps 6944db8ab4 refactor: split MainViewModel core under 500 lines — 500-limit Phase 3, Commit A
Core MainViewModel.cs 758 -> 495 lines. Moved the source add/remove/image
block (AddSource, NextSourceName, AddImage, BuildImageCandidates,
PickImageBytes, AddAsset, AddImageSource, AddReusedImage, RemoveElement) into a
new MainViewModel.Sources.cs partial, and WebView2 source hosting
(RegisterLoadedWebSources, InitWebView2, OnWebView2PreviewBitmapChanged) into a
new MainViewModel.Web.cs partial. Pruned now-dead usings from core.

Zero behavior change; build 0 warnings; 246 pass, only the 2 known failures.
Docs: ViewModels/index.md Phase 3 tracker updated in same commit.
2026-08-30 22:09:02 -07:00