using System.Collections.Generic; using System.Linq; using System.Windows.Media; using ytLive.Models; using ytLive.Services; namespace ytLive.ViewModels; public partial class MainViewModel { private static readonly string[] SystemTextFontFamilies = Fonts.SystemFontFamilies .Select(font => font.Source) .Distinct(StringComparer.OrdinalIgnoreCase) .OrderBy(name => name, StringComparer.CurrentCultureIgnoreCase) .ToArray(); private static readonly double[] CommonTextFontSizes = { 12, 16, 20, 24, 32, 36, 48, 60, 72, 96, 120, 144, 180, 240, 300 }; private static readonly TextLayerAlignment[] HorizontalAlignmentOptions = Enum.GetValues(); private static readonly TextLayerVerticalAlignment[] VerticalAlignmentOptions = Enum.GetValues(); private readonly TextOverlayRenderer _textOverlayRenderer = new(); private readonly HashSet _wiredTextOverlays = new(); private string _selectedTextFontType = "Sans Serif"; public IReadOnlyList TextFontTypeOptions { get; } = new[] { "Sans Serif", "Serif", "Monospace", "Display", "Other" }; public string SelectedTextFontType { get => _selectedTextFontType; set { if (SetProperty(ref _selectedTextFontType, value)) { OnPropertyChanged(nameof(TextFontFamilies)); OnPropertyChanged(nameof(SelectedTextFontFamily)); } } } public IReadOnlyList TextFontFamilies { get { var selected = SelectedElement is Source { Type: SourceType.TextOverlay } source ? source.TextFontFamily : null; var options = SystemTextFontFamilies .Where(font => FontTypeFor(font) == SelectedTextFontType) .ToList(); if (selected != null && FontTypeFor(selected) == SelectedTextFontType && !options.Contains(selected, StringComparer.Ordinal)) options.Add(selected); return options; } } public string? SelectedTextFontFamily { get => (SelectedElement as Source)?.TextFontFamily; set { if (SelectedElement is Source { Type: SourceType.TextOverlay } source && !string.IsNullOrWhiteSpace(value)) source.TextFontFamily = value; } } public IReadOnlyList TextFontSizeOptions { get { var selected = (SelectedElement as Source)?.TextFontSize; return selected is { } size && !CommonTextFontSizes.Contains(size) ? CommonTextFontSizes.Append(size).OrderBy(value => value).ToArray() : CommonTextFontSizes; } } public IReadOnlyList TextHorizontalAlignmentOptions => HorizontalAlignmentOptions; public IReadOnlyList TextVerticalAlignmentOptions => VerticalAlignmentOptions; private void SyncTextFontCategory() { SelectedTextFontType = SelectedElement is Source { Type: SourceType.TextOverlay } source ? FontTypeFor(source.TextFontFamily) : "Sans Serif"; OnPropertyChanged(nameof(SelectedTextFontFamily)); OnPropertyChanged(nameof(TextFontSizeOptions)); } private static string FontTypeFor(string font) { if (font.Contains("Mono", StringComparison.OrdinalIgnoreCase) || font.Contains("Consolas", StringComparison.OrdinalIgnoreCase) || font.Contains("Courier", StringComparison.OrdinalIgnoreCase) || font.Contains("Fixedsys", StringComparison.OrdinalIgnoreCase) || font.Contains("Terminal", StringComparison.OrdinalIgnoreCase)) return "Monospace"; if (SerifFonts.Contains(font, StringComparer.OrdinalIgnoreCase) || (font.Contains("Serif", StringComparison.OrdinalIgnoreCase) && !font.Contains("Sans", StringComparison.OrdinalIgnoreCase))) return "Serif"; if (font.Contains("Display", StringComparison.OrdinalIgnoreCase) || font.Contains("Script", StringComparison.OrdinalIgnoreCase) || DisplayFonts.Contains(font, StringComparer.OrdinalIgnoreCase)) return "Display"; if (font.Contains("Sans", StringComparison.OrdinalIgnoreCase) || SansSerifFonts.Contains(font, StringComparer.OrdinalIgnoreCase)) return "Sans Serif"; return "Other"; } private static readonly string[] SerifFonts = { "Cambria", "Constantia", "Georgia", "Garamond", "Palatino Linotype", "Times New Roman", "Book Antiqua", "Century Schoolbook", "Sitka Text", "Sitka Heading", "Bodoni MT", "Bell MT", }; private static readonly string[] DisplayFonts = { "Algerian", "Broadway", "Chiller", "Comic Sans MS", "Cooper Black", "Impact", "Jokerman", "Magneto", "Papyrus", "Showcard Gothic", "Stencil", }; private static readonly string[] SansSerifFonts = { "Arial", "Arial Nova", "Aptos", "Bahnschrift", "Calibri", "Candara", "Century Gothic", "Corbel", "Franklin Gothic Medium", "Microsoft Sans Serif", "Segoe UI", "Tahoma", "Trebuchet MS", "Verdana", }; private void UpdateConfiguredTextOverlays() { var sources = Scenes.SelectMany(scene => scene.Elements).OfType() .Where(source => source.Type == SourceType.TextOverlay).ToArray(); RetainTextOverlaySubscriptions(); _textOverlayRenderer.Retain(sources); foreach (var source in sources) { if (source.Width <= 0) source.Width = 900; if (source.Height <= 0) source.Height = 140; WireTextOverlaySource(source); _textOverlayRenderer.UpdatePreview(source); } } private void WireTextOverlaySource(Source source) { if (_wiredTextOverlays.Add(source)) source.PropertyChanged += OnElementPropertyChanged; } private void UpdateTextOverlayPreview(Source source) => _textOverlayRenderer.UpdatePreview(source); private void RemoveTextOverlayPreview(Source source) { if (_wiredTextOverlays.Remove(source)) source.PropertyChanged -= OnElementPropertyChanged; _textOverlayRenderer.Remove(source); } private void RetainTextOverlaySubscriptions() { var active = Scenes.SelectMany(scene => scene.Elements).OfType() .Where(source => source.Type == SourceType.TextOverlay).ToHashSet(); foreach (var source in _wiredTextOverlays.ToArray()) if (!active.Contains(source)) RemoveTextOverlayPreview(source); } private void OnTextOverlayPropertyChanged(Source source, string? propertyName) { if (propertyName is nameof(Source.TextContent) or nameof(Source.TextFontFamily) or nameof(Source.TextFontSize) or nameof(Source.TextColor) or nameof(Source.TextBold) or nameof(Source.TextItalic) or nameof(Source.TextUnderline) or nameof(Source.TextAlignment) or nameof(Source.TextVerticalAlignment) or nameof(Source.Width) or nameof(Source.Height)) UpdateTextOverlayPreview(source); } }