Use category-filtered font and selected-item dropdowns, separate horizontal and vertical alignment, and reuse the border-color palette templates for text color. Persist vertical alignment additively. Follow the established static text-source pattern: https://obsproject.com/kb/text-sources
193 lines
7.2 KiB
C#
193 lines
7.2 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 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<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", "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 (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<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);
|
|
}
|
|
|
|
}
|