vv webcam Unit 2: the Chat scene in the vertical tier - rect only, tile or one whole 607x540 area
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.
This commit is contained in:
@@ -486,6 +486,11 @@ public partial class MainViewModel
|
||||
|
||||
private bool ResolveVerticalCameraLayout(Scene scene, WebcamSceneConfig config, bool vertical)
|
||||
{
|
||||
// The Chat scene's vertical layout is RECT-only (the creator's table:
|
||||
// "vv · Chat = rect only — no circle option"). The lock is cleared
|
||||
// BEFORE the 16:9 layout comes back, so the desktop shape is restored.
|
||||
config.VerticalShapeLocked = vertical && SceneCatalog.IsChat(scene.Name);
|
||||
|
||||
if (!vertical)
|
||||
{
|
||||
config.VerticalBand = Rect.Empty;
|
||||
@@ -509,12 +514,14 @@ public partial class MainViewModel
|
||||
return true;
|
||||
}
|
||||
|
||||
// The Live scene's camera must be INSIDE the band or the vertical
|
||||
// output has no camera at all — it starts as a small tile in the
|
||||
// bottom-right spot (the corner the 16:9 default uses). The other
|
||||
// scenes keep their 16:9 placement until the creator drags them into
|
||||
// the band (creator ruling 2026-10-17: Starting/BRB/Ending unchanged).
|
||||
if (!SceneCatalog.Is(scene.Name, SceneCatalog.Live)) return false;
|
||||
// The Live and Chat scenes are the camera scenes in the vertical tier:
|
||||
// without a placement INSIDE the band the vertical output has no camera
|
||||
// at all. They start as a small tile in the bottom-right spot (the
|
||||
// corner the 16:9 default webcam uses). Starting/BRB/Ending keep their
|
||||
// 16:9 placement (creator ruling 2026-10-17: unchanged) until the
|
||||
// creator drags them into the band.
|
||||
if (!SceneCatalog.Is(scene.Name, SceneCatalog.Live) && !SceneCatalog.IsChat(scene.Name))
|
||||
return false;
|
||||
|
||||
var spot = VerticalCameraLayout.DefaultSpot(
|
||||
VerticalBand, VerticalCameraLayout.TileWidth, VerticalCameraLayout.TileHeight);
|
||||
@@ -534,11 +541,9 @@ public partial class MainViewModel
|
||||
{
|
||||
if (!IsVerticalTier) return;
|
||||
EnsureVerticalCameraLayout(config);
|
||||
var spot = VerticalCameraLayout.SpotFor(
|
||||
VerticalBand, config.Width, config.Height,
|
||||
x + config.Width / 2, y + config.Height / 2);
|
||||
config.X = spot.X;
|
||||
config.Y = spot.Y;
|
||||
config.X = x;
|
||||
config.Y = y;
|
||||
SnapWebcamInVerticalTier(config);
|
||||
}
|
||||
|
||||
/// <summary>Snaps a camera back onto its spot — the resize handle grew or
|
||||
@@ -547,13 +552,63 @@ public partial class MainViewModel
|
||||
{
|
||||
if (!IsVerticalTier) return;
|
||||
EnsureVerticalCameraLayout(config);
|
||||
|
||||
var centreX = config.X + config.Width / 2;
|
||||
var centreY = config.Y + config.Height / 2;
|
||||
|
||||
// The enlarged Chat camera fills one whole area — the top one or the
|
||||
// bottom one, by where its centre is.
|
||||
if (VerticalCameraLayout.FillsArea(VerticalBand, config.Width))
|
||||
{
|
||||
var area = VerticalCameraLayout.AreaFor(
|
||||
VerticalBand, centreY >= VerticalBand.Top + VerticalBand.Height / 2);
|
||||
config.Width = area.Width;
|
||||
config.Height = area.Height;
|
||||
config.X = area.Left;
|
||||
config.Y = area.Top;
|
||||
return;
|
||||
}
|
||||
|
||||
var spot = VerticalCameraLayout.SpotFor(
|
||||
VerticalBand, config.Width, config.Height,
|
||||
config.X + config.Width / 2, config.Y + config.Height / 2);
|
||||
VerticalBand, config.Width, config.Height, centreX, centreY);
|
||||
config.X = spot.X;
|
||||
config.Y = spot.Y;
|
||||
}
|
||||
|
||||
/// <summary>True when this camera belongs to the Chat scene — the vertical
|
||||
/// tier's Chat layout has its own rules (rect only, two sizes).</summary>
|
||||
public bool IsChatSceneCamera(WebcamSceneConfig config)
|
||||
=> Scenes.Any(s => SceneCatalog.IsChat(s.Name) && s.Elements.Contains(config));
|
||||
|
||||
/// <summary>False when the circle option is not offered — the Chat scene's
|
||||
/// vertical layout is rect-only (the creator's table, 2026-10-17).</summary>
|
||||
public bool CanToggleClipShape => !(IsVerticalTier
|
||||
&& SelectedElement is WebcamSceneConfig selected
|
||||
&& IsChatSceneCamera(selected));
|
||||
|
||||
/// <summary>Applies a resize inside the vertical tier: the Chat camera's
|
||||
/// size is discrete — the small tile (normal) or one whole 607×540 area
|
||||
/// (enlarged) — and every camera ends up snapped to its spot.</summary>
|
||||
public void ResizeWebcamInVerticalTier(WebcamSceneConfig config, double requestedWidth, double requestedHeight)
|
||||
{
|
||||
if (!IsVerticalTier) return;
|
||||
EnsureVerticalCameraLayout(config);
|
||||
|
||||
if (IsChatSceneCamera(config))
|
||||
{
|
||||
var (w, h) = VerticalCameraLayout.SizeFor(VerticalBand, requestedWidth);
|
||||
config.Width = w;
|
||||
config.Height = h;
|
||||
}
|
||||
else
|
||||
{
|
||||
config.Width = requestedWidth;
|
||||
config.Height = requestedHeight;
|
||||
}
|
||||
|
||||
SnapWebcamInVerticalTier(config);
|
||||
}
|
||||
|
||||
/// <summary>Makes the scene's vertical placement live — creating it (a small
|
||||
/// tile in the default spot) when the scene has none yet.</summary>
|
||||
private void EnsureVerticalCameraLayout(WebcamSceneConfig config)
|
||||
|
||||
Reference in New Issue
Block a user