feat(ui): Show Desktop toggle, avatar login, chat preview, backdrop seeding, border animations
- Source.ShowDesktop bool persisted in DB (schema migration + LayoutStore) - ClearBackdropCaptureAsync clears CaptureKey + VideoImageSource for static fallback - Top bar: red/green/pulsing status light, avatar via BitmapImage, Log In button - ShowStartStream now requires IsConnected - RunMockChatPreviewAsync: simple async loop, ChatPreviewEnabled property on Source - EnsureDefaultBackdrop called on every scene switch, seeds all 5 scenes - SeedEndingBackdrop replaces existing, SeedChatBackdrop added - SceneCatalog: Chat scene display name corrected (layer = 'YouTube Chat') - Left panel spacing consistency, context menu reordered - Border animation rendering via code-behind Storyboard approach - 3 new backdrop assets (ending-backdrop.jpg, chat-backdrop.jpg, live-backdrop.jpg) - Build 0 warnings, 178 tests passing
This commit is contained in:
@@ -1,5 +1,7 @@
|
||||
using System;
|
||||
using System.Globalization;
|
||||
using System.Linq;
|
||||
using System.Windows.Data;
|
||||
|
||||
namespace ytLive.Models;
|
||||
|
||||
@@ -19,6 +21,24 @@ public static class SceneCatalog
|
||||
|
||||
public static readonly string[] All = { Starting, Live, Brb, Chat, Ending };
|
||||
|
||||
private static readonly Dictionary<string, string> DisplayNames = new(StringComparer.OrdinalIgnoreCase)
|
||||
{
|
||||
};
|
||||
|
||||
public static string DisplayName(string? name)
|
||||
=> name != null && DisplayNames.TryGetValue(name.Trim(), out var display) ? display : name ?? string.Empty;
|
||||
|
||||
public static readonly IValueConverter DisplayNameConverter = new SceneDisplayNameConverter();
|
||||
|
||||
private sealed class SceneDisplayNameConverter : IValueConverter
|
||||
{
|
||||
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
=> DisplayName(value as string);
|
||||
|
||||
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
|
||||
=> value ?? string.Empty;
|
||||
}
|
||||
|
||||
public static bool IsCanonical(string? name)
|
||||
=> name != null && All.Contains(name.Trim(), StringComparer.OrdinalIgnoreCase);
|
||||
|
||||
|
||||
+51
-2
@@ -52,6 +52,9 @@ public class Source : SceneElement
|
||||
private bool _isBackdrop;
|
||||
public bool IsBackdrop { get => _isBackdrop; set => Set(ref _isBackdrop, 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); }
|
||||
|
||||
@@ -91,7 +94,9 @@ public class Source : SceneElement
|
||||
|
||||
public ImageSource? ImageSource => _imageSource;
|
||||
|
||||
public override ImageSource? DisplaySource => IsLiveCapture ? VideoImageSource : _imageSource;
|
||||
public override ImageSource? DisplaySource => IsLiveCapture
|
||||
? (VideoImageSource ?? _imageSource)
|
||||
: (Type == SourceType.ChatBox ? VideoImageSource : _imageSource);
|
||||
|
||||
public override bool IsImageSource => Type == SourceType.Image;
|
||||
|
||||
@@ -119,7 +124,34 @@ public class Source : SceneElement
|
||||
public string ChatFontColor { get => _chatFontColor; set => Set(ref _chatFontColor, value); }
|
||||
|
||||
private string _chatBackgroundColor = "#80000000";
|
||||
public string ChatBackgroundColor { get => _chatBackgroundColor; set => Set(ref _chatBackgroundColor, value); }
|
||||
public string ChatBackgroundColor
|
||||
{
|
||||
get => _chatBackgroundColor;
|
||||
set
|
||||
{
|
||||
if (Set(ref _chatBackgroundColor, value))
|
||||
Raise(nameof(ChatBackgroundOpacity));
|
||||
}
|
||||
}
|
||||
|
||||
/// <summary>Alpha channel of ChatBackgroundColor as 0.0–1.0. Slider writes back the full hex string.</summary>
|
||||
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
|
||||
@@ -140,5 +172,22 @@ public class Source : SceneElement
|
||||
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 bool IsChatBox => Type == SourceType.ChatBox;
|
||||
|
||||
private bool _chatPreviewEnabled = true;
|
||||
public bool ChatPreviewEnabled
|
||||
{
|
||||
get => _chatPreviewEnabled;
|
||||
set => Set(ref _chatPreviewEnabled, value);
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user