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.
6.9 KiB
6.9 KiB
Models — index
Plain data types. No logic beyond what a property can carry. See
schema.md for the memory-map conventions.
| File | Purpose |
|---|---|
Scene.cs |
A scene: Name, IsHidden, IsChatScene, IsEditing, HasBackdrop (Live-only policy flag, default off — only the canonical Live scene has one, see SceneCatalog), Elements collection (images + webcam config), WebcamConfig accessor |
SceneCatalog.cs |
The five canonical scenes (Starting/Live/BRB/Chat/Ending) — the product, by name: All, IsCanonical, Is(name, canonical) (case-insensitive trim), IsChat, HasBackdrop(name) (true only for Live). Drives the empty-DB seed, the "+" missing-scenes menu, and MainViewModel.EnforceBackdropPolicy |
Source.cs |
A source: SourceType enum (DisplayCapture/WindowCapture/Background/Image/TextOverlay/ChatBox/Countdown/WebSource/MediaSource/AlertBox (TASK 43 — the celebration zone, one per layout) — Webcam removed), X/Y/Width/Height, Opacity, IsEnabled, ImageSource (static) / VideoImageSource (live frames), DisplaySource (IsLiveCapture → video, else static), ClipShape enum (Traditional/Round), IsMirrored + MirrorScale, MirrorButtonText/ShapeButtonText, asset identity, IsImageSource (XAML binds this, never Type), backdrop surface: IsBackdrop (permanent bottom layer, never removable), CaptureKey (persisted monitor:<n>/window:<hwnd>/picker:<name>), IsLiveCapture (Type is Display/WindowCapture) |
SceneElement.cs |
Base for anything placeable in a scene: shared layout/clip/mirror/border surface, virtual IsWebcam/virtual IsImageSource/virtual IsZoomLayer (TASK 52); ToggleClipShape + persisted RectWidth/RectHeight (pre-Round rect so round→rect restores after reload); TASK 57: the geometry setters (X/Y/Width/Height) are virtual so a derived element can CONSTRAIN every write (the webcam clamps itself into the vertical tier's band) instead of every writer re-implementing the rule; ClipShape + ToggleClipShape() are virtual too, so a derived element can CONSTRAIN the shape (the Chat scene's vertical camera is rect-only) |
VerticalCameraLayout.cs |
TASK 57 (the "vv" feature): the vertical (9:16) tier's camera rules as pure math — TileWidth/TileHeight (270×152 master px = 480×270 in the 1080×1920 output), Inset (32 master px from the band's outer edge), IsVerticalTier(QualityOption) (portrait tier), AreaFor(band, bottom) (the band splits into two 607×540 areas), MaxSizeFor(band), SpotFor(band, w, h, centreX, centreY) (the six spots — the area and column the tile's centre falls in, inset, never outside the band) and DefaultSpot (bottom-right, the corner the 16:9 default webcam uses), FillsArea(band, width) (the camera is wide enough to fill a whole area = "enlarged") and SizeFor(band, requestedWidth) (the Chat camera's TWO sizes - the small tile or one whole 607×540 area — whichever is nearer the requested width, so the resize handle has two detents) |
ZoomSceneElement.cs |
Transient zoom box (TASK 52, creator-corrected 2026-10-08): carries the box rect AND the anchored CaptureX/CaptureY (the fixed box/2 capture window set on ActivateZoom; moving the box never touches it). IsZoomActive flips Kind Static→Dynamic (leaves the baked base while active); never persisted (LayoutStore save skips non-Source/non-WebcamSceneConfig rows), never shown in the layers list or the element template (IsZoomLayer gates both). The compositor SKIPS the active element in every layer loop and draws its 2x content LAST (layer n+1) via ZoomMagnifier. |
Webcam.cs |
Singleton webcam identity: Id, DeviceId, Name (one row app-wide) |
BackgroundEffect.cs |
TASK 51: per-webcam background effect enum None / Remove / Blur, carried by WebcamSceneConfig |
WebcamSceneConfig.cs |
Per-scene webcam placement (subclass of SceneElement): geometry + IsVisible + border (BorderColor/BorderOpacity/BorderWidth/BorderAnimation) + VideoImageSource; WebcamId links to Webcam; TASK 51: BackgroundEffect + BackgroundStrength (0..1, default 0.5) + the radio-pill surface RemoveBackground/BlurBackground (one on turns the other off; both off = None) + HasBackgroundEffect (shows the strength slider); TASK 57 (the "vv" feature): the vertical (9:16) tier's OWN placement per scene — VerticalX/Y/Width/Height/VerticalClipShape/VerticalRectWidth/VerticalRectHeight (persisted, NULL = no vertical placement yet) + the transient VerticalBand/VerticalLayoutLive/LandscapeX…LandscapeRectHeight (the parked 16:9 layout) + BeginVerticalLayout/EndVerticalLayout/CommitLiveLayoutToVertical (the tier swap) and the X/Y/Width/Height overrides that clamp every write into the band; VerticalShapeLocked (the Chat scene's vertical layout is rect-only) overrides ClipShape + ToggleClipShape() so no writer can slip a circle in |
QualityOption.cs |
Resolution tier record (Display, Fps, Bitrate, Width, Height); Label formats as 1080p60 (8Mbps) — drives the bottom-bar dropdown and the output-rect math |
StreamConfig.cs |
StreamConfig (note: TargetBitrate/Resolution defaults are stale — not yet wired to the dropdown), StreamHealth (bitrate/FPS/dropped/duration), StreamStatus enum |
MicStatus.cs |
Footer mic indicator: NotConnected / Connected / Problem — the state behind the MIC button's status dot (red = no device/capture off, green = capturing, yellow = a requested mic failed: in use/unplugged) |
YouTube.cs |
YouTubeChannel and ChatMessage (chat feed) — since TASK 43 every message carries Kind (ChatEventKind None/SuperChat/SuperSticker/NewMember/MemberGift/GiftReceived/MemberMilestone) + event payload fields (SuperChatDisplayString, SuperStickerDescription, GiftNewMembersCount, GifterName, MilestoneMonths) |
Socials.cs |
Social bar (global layer, never a Source): SocialService enum (YouTube/Twitch/X/Instagram/TikTok/Facebook/Discord/Kick/Threads/Bluesky/GitHub/LinkedIn/Pinterest/Snapchat/Reddit/WhatsApp/Telegram/Link/Website/Fediverse), SocialEntry (Service/Handle/ProfileUrl/FediverseSoftware), SocialsConfig (Entries + BarPosition Top/Bottom + BarEnabled on/off — BarJustify dropped, column back-compat), SocialServiceIcons (canonical URL builder + DetectService (URL domain / fediverse @user@domain → Fediverse / bare→Website) + bundled SVG logo path data per service (LogoDataFor) + LogoDataForFediverse(software) mapping nodeinfo software names (mastodon/peertube/pixelfed/misskey/lemmy/pleroma/firefish) to logos with a generic fediverse glyph fallback (FediverseIconData) + LockedIconData/DoNotIconData — initials/colors gone) |
Related: ViewModels/index.md consume these;
Services/LayoutStore.cs persists Scene/Source.