using System.Windows; using System.Windows.Media; namespace ytLive.Models; /// /// A scene's usage of the app-wide webcam — conceptually `webcam.{scene}.config`. /// The identity lives once on the entity; this object is that /// scene's placement, clip/mirror, border, and visibility for it. Exists only in /// scenes where the creator added the webcam. /// public class WebcamSceneConfig : SceneElement { public string WebcamId { get; init; } = string.Empty; public override bool IsWebcam => true; public override bool IsDraggable => true; public override ElementKind Kind => ElementKind.Dynamic; public override ImageSource? DisplaySource => VideoImageSource; private BackgroundEffect _backgroundEffect = BackgroundEffect.None; /// TASK 51: this scene's background effect for the webcam (persisted). public BackgroundEffect BackgroundEffect { get => _backgroundEffect; set { if (!Set(ref _backgroundEffect, value)) return; Raise(nameof(RemoveBackground)); Raise(nameof(BlurBackground)); Raise(nameof(HasBackgroundEffect)); } } /// True while Remove or Blur is on — shows the strength slider. public bool HasBackgroundEffect => BackgroundEffect != BackgroundEffect.None; private double _backgroundStrength = 0.5; /// TASK 51: 0..1 — Blur amount, or Remove's edge softness (persisted). public double BackgroundStrength { get => _backgroundStrength; set => Set(ref _backgroundStrength, Math.Clamp(value, 0, 1)); } /// Pill-switch surface: Remove and Blur behave like radio buttons — turning one /// on turns the other off; turning the active one off means . public bool RemoveBackground { get => BackgroundEffect == BackgroundEffect.Remove; set => BackgroundEffect = value ? BackgroundEffect.Remove : BackgroundEffect == BackgroundEffect.Remove ? BackgroundEffect.None : BackgroundEffect; } /// public bool BlurBackground { get => BackgroundEffect == BackgroundEffect.Blur; set => BackgroundEffect = value ? BackgroundEffect.Blur : BackgroundEffect == BackgroundEffect.Blur ? BackgroundEffect.None : BackgroundEffect; } // ─── Vertical (9:16) tier placement (TASK 57, the "vv" feature) ──────── // The vertical tier is a centred band crop of the same master frame, so the // 16:9 placement lands outside the cropped window and the camera vanishes // from the vertical output. The vertical tier therefore keeps its OWN // position, size and shape per scene, stored here and resolved when the // tier changes; switching back to 16:9 restores the desktop layout exactly. /// The vertical placement's X (null until the scene has one). public double? VerticalX { get; set; } /// The vertical placement's Y (null until the scene has one). public double? VerticalY { get; set; } /// The vertical placement's width (null until the scene has one). public double? VerticalWidth { get; set; } /// The vertical placement's height (null until the scene has one). public double? VerticalHeight { get; set; } /// The vertical placement's clip shape (circle or rect) — the /// creator's own choice per scene, independent of the 16:9 shape. public ClipShape? VerticalClipShape { get; set; } /// The vertical placement's pre-Round rect (so a Round tile /// restores its aspect on toggle-back, like the 16:9 one does). public double? VerticalRectWidth { get; set; } /// public double? VerticalRectHeight { get; set; } /// True once this scene has a vertical placement of its own. public bool HasVerticalLayout => VerticalX is not null; /// True while the vertical placement is the LIVE geometry (the /// element's own X/Y/W/H). The 16:9 layout is parked in /// and friends until the tier switches back. public bool VerticalLayoutLive { get; private set; } /// The vertical tier's band over the master frame (transient — the /// VM sets it when the tier changes). While the vertical placement is live /// the camera's geometry is clamped to it: outside the band is invisible. public Rect VerticalBand { get; set; } = Rect.Empty; // The parked 16:9 layout — meaningful only while the vertical placement is // live (it is the 16:9 geometry the tier switch restores). public double LandscapeX { get; private set; } public double LandscapeY { get; private set; } public double LandscapeWidth { get; private set; } public double LandscapeHeight { get; private set; } public ClipShape LandscapeClipShape { get; private set; } public double? LandscapeRectWidth { get; private set; } public double? LandscapeRectHeight { get; private set; } /// Parks the live (16:9) layout and brings a vertical placement /// in. The vertical placement becomes the live geometry from here on. public void BeginVerticalLayout(double x, double y, double width, double height, ClipShape clipShape) { LandscapeX = X; LandscapeY = Y; LandscapeWidth = Width; LandscapeHeight = Height; LandscapeClipShape = ClipShape; LandscapeRectWidth = RectWidth; LandscapeRectHeight = RectHeight; VerticalLayoutLive = true; Width = width; Height = height; X = x; Y = y; ClipShape = clipShape; RectWidth = VerticalRectWidth; RectHeight = VerticalRectHeight; } /// Restores the parked 16:9 layout — the tier switched back. public void EndVerticalLayout() { if (!VerticalLayoutLive) return; // The vertical placement is the creator's own — keep it, so switching // back and forth does not lose where they put it. CommitLiveLayoutToVertical(); VerticalLayoutLive = false; Width = LandscapeWidth; Height = LandscapeHeight; X = LandscapeX; Y = LandscapeY; ClipShape = LandscapeClipShape; RectWidth = LandscapeRectWidth; RectHeight = LandscapeRectHeight; } /// Copies the live geometry into the stored vertical placement, so /// the stored copy never lags the live one (the save path calls this). public void CommitLiveLayoutToVertical() { VerticalX = X; VerticalY = Y; VerticalWidth = Width; VerticalHeight = Height; VerticalClipShape = ClipShape; VerticalRectWidth = RectWidth; VerticalRectHeight = RectHeight; } // The geometry is clamped into the band while the vertical placement is // live — a camera outside the cropped window is a camera the viewers never // see, and every writer (drag, resize handle, the properties fields) goes // through these setters. public override double X { get => base.X; set => base.X = ClampToBand(value, base.Width, vertical: true); } public override double Y { get => base.Y; set => base.Y = ClampToBand(value, base.Height, vertical: false); } public override double Width { get => base.Width; set => base.Width = VerticalLayoutLive && !VerticalBand.IsEmpty ? Math.Min(value, VerticalBand.Width) : value; } public override double Height { get => base.Height; set => base.Height = VerticalLayoutLive && !VerticalBand.IsEmpty ? Math.Min(value, VerticalBand.Height / 2) : value; } private double ClampToBand(double value, double size, bool vertical) { if (!VerticalLayoutLive || VerticalBand.IsEmpty) return value; var band = VerticalBand; var min = vertical ? band.Left : band.Top; var max = vertical ? band.Right - size : band.Bottom - size; if (max < min) return min; return Math.Clamp(value, min, max); } }