using System.Windows.Media; using ytLive.Helpers; namespace ytLive.Models; public enum SourceType { DisplayCapture, WindowCapture, Background, Image, TextOverlay, Countdown, WebSource, ChatBox, MediaSource, /// The designated alert zone: the six monetization events play their /// unique celebration animations here (OBS alert-box equivalent, native). AlertBox } /// Distinguishes how a media source presents its content (TASK 21). public enum MediaSourceType { Video, Audio } /// Playback state of a media source (TASK 21). public enum MediaPlaybackState { Stopped, Playing, Paused } public enum ClipShape { Traditional, Round } /// How an alert box presents its announcement strip (TASK 47). The strip is /// the full-width pill naming the event ("Viewer — Super Chat · $12.34"). public enum AlertDisplayMethod { /// Marquee: the pill scrolls across the top edge, paced so the text is /// read times per alert. TickerScroll, /// Pulse: the static pill blinks on and off for the whole alert. Flash, /// Static: the pill sits centred and still for the whole alert. Solid } /// /// A multi-instance scene object: image/background/text (screen/window later). /// The webcam is NOT a Source — it's a singleton resource whose per-scene usage /// is a . See the singleton-vs-multi-instance /// discriminator in ai.md. /// public class Source : SceneElement { private SourceType _type; public SourceType Type { get => _type; set { if (Set(ref _type, value)) { IsBackground = value == SourceType.Background; Raise(nameof(IsLiveCapture)); Raise(nameof(DisplaySource)); Raise(nameof(IsWebSource)); } } } public override bool IsDraggable => Type is SourceType.Image or SourceType.WebSource or SourceType.ChatBox or SourceType.AlertBox; /// Live captures, chat, web sources and media are dynamic; static art is fixed. public override ElementKind Kind => Type is SourceType.DisplayCapture or SourceType.WindowCapture or SourceType.ChatBox or SourceType.WebSource or SourceType.MediaSource or SourceType.AlertBox ? ElementKind.Dynamic : ElementKind.Static; public override bool IsWebSource => Type == SourceType.WebSource; /// The permanent scene background (live desktop/game capture). Never removable/reorderable. private bool _isBackground; public override bool IsBackground { get => _isBackground; set => Set(ref _isBackground, value); } private bool _showDesktop; public bool ShowDesktop { get => _showDesktop; set => Set(ref _showDesktop, value); } private bool _isEnabled = true; public bool IsEnabled { get => _isEnabled; set => Set(ref _isEnabled, value); } // Display/Window capture private int? _monitorIndex; public int? MonitorIndex { get => _monitorIndex; set => Set(ref _monitorIndex, value); } private IntPtr? _windowHandle; public IntPtr? WindowHandle { get => _windowHandle; set => Set(ref _windowHandle, value); } /// /// Identifies what this live-capture source points at: "monitor:<index>" or /// "window:<hwnd>". Persisted so a re-designated background survives a reload. /// private string? _captureKey; public string? CaptureKey { get => _captureKey; set => Set(ref _captureKey, value); } public bool IsLiveCapture => Type is SourceType.DisplayCapture or SourceType.WindowCapture; // Image (asset stored in the layout database) private string? _assetId; private ImageSource? _imageSource; public string? AssetId { get => _assetId; set { if (Set(ref _assetId, value)) { _imageSource = ImageCache.Get(value ?? string.Empty); Raise(nameof(ImageSource)); Raise(nameof(DisplaySource)); } } } public ImageSource? ImageSource => _imageSource; public override ImageSource? DisplaySource => IsLiveCapture ? (VideoImageSource ?? _imageSource) : (Type is SourceType.ChatBox or SourceType.WebSource or SourceType.MediaSource or SourceType.AlertBox ? VideoImageSource : _imageSource); public override bool IsImageSource => Type == SourceType.Image; // Countdown config private int _timerMinutes = 15; public int TimerMinutes { get => _timerMinutes; set => Set(ref _timerMinutes, Math.Clamp(value, 1, 60)); } // Web source config private string? _webUri; public string? WebUri { get => _webUri; set => Set(ref _webUri, value); } // Media source config (TASK 21: video/audio file playback) private MediaSourceType _mediaType = MediaSourceType.Video; public MediaSourceType MediaType { get => _mediaType; set => Set(ref _mediaType, value); } private string? _mediaPath; public string? MediaPath { get => _mediaPath; set { if (Set(ref _mediaPath, value)) Raise(nameof(IsMediaSource)); } } private bool _mediaIsLooping; public bool MediaIsLooping { get => _mediaIsLooping; set => Set(ref _mediaIsLooping, value); } private double _mediaVolume = 1.0; public double MediaVolume { get => _mediaVolume; set => Set(ref _mediaVolume, Math.Clamp(value, 0.0, 1.0)); } private MediaPlaybackState _mediaPlaybackState = MediaPlaybackState.Stopped; public MediaPlaybackState MediaPlaybackState { get => _mediaPlaybackState; set => Set(ref _mediaPlaybackState, value); } public bool IsMediaSource => Type == SourceType.MediaSource; // Alert video config (TASK 47): the shipped default clip lives in the Asset // table (seeded like image assets); the creator's replacement is a FILE PATH // reference only — never into the DB (creator ruling, 2026-09-26). Runtime // order: replacement path exists → DB default → six celebration animations. private string? _alertVideoAssetId; public string? AlertVideoAssetId { get => _alertVideoAssetId; set => Set(ref _alertVideoAssetId, value); } private string? _alertVideoPath; public string? AlertVideoPath { get => _alertVideoPath; set => Set(ref _alertVideoPath, value); } private bool _alertUseDefaultVideo = true; public bool AlertUseDefaultVideo { get => _alertUseDefaultVideo; set => Set(ref _alertUseDefaultVideo, value); } private bool _alertShowTicker = true; public bool AlertShowTicker { get => _alertShowTicker; set => Set(ref _alertShowTicker, value); } private double _alertVideoVolume = 1.0; public double AlertVideoVolume { get => _alertVideoVolume; set => Set(ref _alertVideoVolume, Math.Clamp(value, 0.0, 1.0)); } private AlertDisplayMethod _alertDisplayMethod = AlertDisplayMethod.TickerScroll; public AlertDisplayMethod AlertDisplayMethod { get => _alertDisplayMethod; set => Set(ref _alertDisplayMethod, value); } public bool IsAlertBox => Type == SourceType.AlertBox; // Chat box config (TASK 3.18) private double _chatFontSize = 16; public double ChatFontSize { get => _chatFontSize; set => Set(ref _chatFontSize, Math.Clamp(value, 8, 72)); } private string _chatFontColor = "#FFFFFF"; public string ChatFontColor { get => _chatFontColor; set => Set(ref _chatFontColor, value); } private string _chatBackgroundColor = "#80000000"; public string ChatBackgroundColor { get => _chatBackgroundColor; set { if (Set(ref _chatBackgroundColor, value)) Raise(nameof(ChatBackgroundOpacity)); } } /// Alpha channel of ChatBackgroundColor as 0.0–1.0. Slider writes back the full hex string. public double ChatBackgroundOpacity { get { var hex = _chatBackgroundColor.Trim().TrimStart('#'); return hex.Length == 8 ? byte.Parse(hex.AsSpan(0, 2), System.Globalization.NumberStyles.HexNumber) / 255.0 : 1.0; } set { var alpha = (byte)Math.Clamp(value * 255, 0, 255); var hex = _chatBackgroundColor.Trim().TrimStart('#'); if (hex.Length == 8) hex = hex[2..]; if (hex.Length == 6) ChatBackgroundColor = $"#{alpha:X2}{hex}"; } } private int _chatMaxMessages = 5; public int ChatMaxMessages { get => _chatMaxMessages; set => Set(ref _chatMaxMessages, Math.Clamp(value, 1, 20)); } private bool _chatShowTimestamps = true; public bool ChatShowTimestamps { get => _chatShowTimestamps; set => Set(ref _chatShowTimestamps, value); } private bool _chatShowBadges = true; public bool ChatShowBadges { get => _chatShowBadges; set => Set(ref _chatShowBadges, value); } private string _chatMemberColor = "#e94560"; public string ChatMemberColor { get => _chatMemberColor; set => Set(ref _chatMemberColor, value); } private string _chatSuperChatColor = "#eab308"; public string ChatSuperChatColor { get => _chatSuperChatColor; set => Set(ref _chatSuperChatColor, value); } private string _chatFontFamily = "Segoe UI"; public string ChatFontFamily { get => _chatFontFamily; set => Set(ref _chatFontFamily, value); } private int _chatFadeSeconds; public int ChatFadeSeconds { get => _chatFadeSeconds; set => Set(ref _chatFadeSeconds, Math.Clamp(value, 0, 60)); } public override bool IsChatBox => Type == SourceType.ChatBox; public override bool IsCountdown => Type == SourceType.Countdown; private bool _chatPreviewEnabled = true; public bool ChatPreviewEnabled { get => _chatPreviewEnabled; set => Set(ref _chatPreviewEnabled, value); } }