Put size and color on one row, place emphasis labels before their toggles, and split the remaining font families into handwriting, symbols, and international groups while omitting legacy dingbat fonts. Text-layer properties follow the existing hierarchical layout and palette conventions.
244 lines
9.7 KiB
C#
244 lines
9.7 KiB
C#
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 HashSet<string> ExcludedLegacyFontFamilies = new(StringComparer.OrdinalIgnoreCase)
|
|
{
|
|
"Bookshelf Symbol 7", "Marlett", "Symbol", "Webdings", "Wingdings", "Wingdings 2", "Wingdings 3",
|
|
};
|
|
private static readonly string[] SystemTextFontFamilies = Fonts.SystemFontFamilies
|
|
.Select(font => font.Source)
|
|
.Where(font => !ExcludedLegacyFontFamilies.Contains(font))
|
|
.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<TextLayerAlignment>();
|
|
private static readonly TextLayerVerticalAlignment[] VerticalAlignmentOptions =
|
|
Enum.GetValues<TextLayerVerticalAlignment>();
|
|
|
|
private readonly TextOverlayRenderer _textOverlayRenderer = new();
|
|
private readonly HashSet<Source> _wiredTextOverlays = new();
|
|
|
|
private string _selectedTextFontType = "Sans Serif";
|
|
|
|
public IReadOnlyList<string> TextFontTypeOptions { get; } =
|
|
new[] { "Sans Serif", "Serif", "Monospace", "Display", "Handwriting", "Symbols", "International", "Other" };
|
|
|
|
public string SelectedTextFontType
|
|
{
|
|
get => _selectedTextFontType;
|
|
set
|
|
{
|
|
if (SetProperty(ref _selectedTextFontType, value))
|
|
{
|
|
OnPropertyChanged(nameof(TextFontFamilies));
|
|
OnPropertyChanged(nameof(SelectedTextFontFamily));
|
|
}
|
|
}
|
|
}
|
|
|
|
public IReadOnlyList<string> 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<double> 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<TextLayerAlignment> TextHorizontalAlignmentOptions =>
|
|
HorizontalAlignmentOptions;
|
|
|
|
public IReadOnlyList<TextLayerVerticalAlignment> 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 (SymbolFonts.Contains(font, StringComparer.OrdinalIgnoreCase)
|
|
|| SymbolFontTokens.Any(token => font.Contains(token, StringComparison.OrdinalIgnoreCase)))
|
|
return "Symbols";
|
|
|
|
if (font.Contains("Script", StringComparison.OrdinalIgnoreCase)
|
|
|| font.Contains("Hand", StringComparison.OrdinalIgnoreCase)
|
|
|| font.Contains("Print", StringComparison.OrdinalIgnoreCase)
|
|
|| HandwritingFonts.Contains(font, StringComparer.OrdinalIgnoreCase))
|
|
return "Handwriting";
|
|
|
|
if (InternationalFonts.Contains(font, StringComparer.OrdinalIgnoreCase)
|
|
|| InternationalFontTokens.Any(token => font.Contains(token, StringComparison.OrdinalIgnoreCase)))
|
|
return "International";
|
|
|
|
if (SerifFonts.Contains(font, StringComparer.OrdinalIgnoreCase)
|
|
|| (font.Contains("Serif", StringComparison.OrdinalIgnoreCase)
|
|
&& !font.Contains("Sans", StringComparison.OrdinalIgnoreCase)))
|
|
return "Serif";
|
|
|
|
if (font.Contains("Display", StringComparison.OrdinalIgnoreCase)
|
|
|| DisplayFonts.Contains(font, StringComparer.OrdinalIgnoreCase))
|
|
return "Display";
|
|
|
|
if (font.Contains("Sans", StringComparison.OrdinalIgnoreCase)
|
|
|| SansSerifFonts.Contains(font, StringComparer.OrdinalIgnoreCase)
|
|
|| font.StartsWith("Segoe UI", StringComparison.OrdinalIgnoreCase)
|
|
|| font.StartsWith("Arial", StringComparison.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", "Showcard Gothic", "Stencil",
|
|
};
|
|
|
|
private static readonly string[] HandwritingFonts =
|
|
{
|
|
"Bradley Hand ITC", "Brush Script MT", "Edwardian Script ITC", "French Script MT",
|
|
"Freestyle Script", "Kunstler Script", "Lucida Handwriting", "Mistral", "Monotype Corsiva",
|
|
"Papyrus", "Pristina", "Segoe Print", "Segoe Script", "Viner Hand ITC", "Vladimir Script",
|
|
};
|
|
|
|
private static readonly string[] SymbolFonts =
|
|
{
|
|
"Segoe MDL2 Assets", "Segoe Fluent Icons", "Segoe UI Symbol", "Segoe UI Emoji",
|
|
};
|
|
|
|
private static readonly string[] SymbolFontTokens =
|
|
{
|
|
"Emoji", "Icon", "Symbol", "Wingding", "Webding", "Dingbat",
|
|
};
|
|
|
|
private static readonly string[] InternationalFonts =
|
|
{
|
|
"Batang", "DengXian", "Dotum", "Ebrima", "FangSong", "Gulim", "Hiragino", "KaiTi",
|
|
"Leelawadee", "Malgun Gothic", "Meiryo", "MingLiU", "Mongolian", "MS Gothic", "Nirmala UI",
|
|
"NSimSun", "PMingLiU", "SimSun", "Yu Gothic", "Yu Mincho",
|
|
};
|
|
|
|
private static readonly string[] InternationalFontTokens =
|
|
{
|
|
"Arabic", "Armenian", "Bengali", "Canadian Aboriginal", "Cherokee", "Devanagari", "Ethiopic",
|
|
"Georgian", "Gujarati", "Gurmukhi", "Hebrew", "Javanese", "Kannada", "Khmer", "Lao",
|
|
"Myanmar", "Oriya", "Sinhala", "Syriac", "Tamil", "Telugu", "Thai",
|
|
};
|
|
|
|
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<Source>()
|
|
.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<Source>()
|
|
.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);
|
|
}
|
|
|
|
}
|