feat(text): add static text editor and preview
TextOverlay layers now have a multiline editor, Windows font and size controls, color/emphasis/alignment settings, and a visible draggable preview. Text sources follow OBS's static text-source conventions: https://obsproject.com/kb/text-sources
This commit is contained in:
@@ -0,0 +1,98 @@
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Windows;
|
||||
using System.Windows.Controls;
|
||||
using System.Windows.Media;
|
||||
using Microsoft.Data.Sqlite;
|
||||
using Xunit;
|
||||
using ytLive.Controls;
|
||||
using ytLive.Models;
|
||||
using ytLive.ViewModels;
|
||||
|
||||
namespace ytLive.Tests;
|
||||
|
||||
[Collection("RealApp")]
|
||||
public sealed class TextLayerEditorPreviewTests
|
||||
{
|
||||
private readonly RealAppHost _app;
|
||||
|
||||
public TextLayerEditorPreviewTests(RealAppHost app) => _app = app;
|
||||
|
||||
[Fact]
|
||||
public void Editing_One_Text_Layer_Updates_Only_That_Preview()
|
||||
{
|
||||
_app.Run(Run);
|
||||
}
|
||||
|
||||
private static void Run()
|
||||
{
|
||||
var dbPath = Path.Combine(Path.GetTempPath(), $"ytLlive-text-editor-{Guid.NewGuid():N}.db");
|
||||
MainViewModel.LayoutPathOverride = dbPath;
|
||||
MainWindow? window = null;
|
||||
try
|
||||
{
|
||||
window = new MainWindow();
|
||||
var vm = (MainViewModel)window.DataContext;
|
||||
vm.EditingActive = true;
|
||||
vm.StagedScene = vm.Scenes.Single(scene => SceneCatalog.Is(scene.Name, SceneCatalog.Live));
|
||||
vm.AddSourceCommand.Execute(SourceType.TextOverlay);
|
||||
vm.AddSourceCommand.Execute(SourceType.TextOverlay);
|
||||
|
||||
var textLayers = vm.StagedScene!.Elements.OfType<Source>()
|
||||
.Where(source => source.Type == SourceType.TextOverlay).ToArray();
|
||||
Assert.Equal(2, textLayers.Length);
|
||||
var first = textLayers[0];
|
||||
var second = textLayers[1];
|
||||
second.TextContent = "Leave this caption alone";
|
||||
vm.SelectedElement = first;
|
||||
|
||||
window.Show();
|
||||
window.UpdateLayout();
|
||||
|
||||
var leftPanel = (LeftPanel)window.FindName("LeftPanel")!;
|
||||
var editor = (TextBox)leftPanel.FindName("TextLayerEditor")!;
|
||||
Assert.Equal("Text", editor.Text);
|
||||
Assert.True(first.IsDraggable);
|
||||
Assert.Equal(900, first.Width);
|
||||
Assert.Equal(140, first.Height);
|
||||
|
||||
editor.Text = "Updated headline\nSecond line";
|
||||
editor.GetBindingExpression(TextBox.TextProperty)?.UpdateSource();
|
||||
window.UpdateLayout();
|
||||
|
||||
Assert.Equal("Updated headline\nSecond line", first.TextContent);
|
||||
Assert.Equal("Leave this caption alone", second.TextContent);
|
||||
|
||||
var preview = (PreviewPane)window.FindName("PreviewPane")!;
|
||||
var firstPreview = FindTextPreview(preview, first);
|
||||
var secondPreview = FindTextPreview(preview, second);
|
||||
Assert.NotNull(firstPreview);
|
||||
Assert.NotNull(secondPreview);
|
||||
Assert.Equal("Updated headline\nSecond line", firstPreview!.Text);
|
||||
Assert.Equal("Leave this caption alone", secondPreview!.Text);
|
||||
Assert.True(firstPreview.IsVisible);
|
||||
Assert.True(secondPreview.IsVisible);
|
||||
}
|
||||
finally
|
||||
{
|
||||
window?.Close();
|
||||
MainViewModel.LayoutPathOverride = null;
|
||||
SqliteConnection.ClearAllPools();
|
||||
try { File.Delete(dbPath); } catch { /* best-effort cleanup */ }
|
||||
}
|
||||
}
|
||||
|
||||
private static TextBlock? FindTextPreview(DependencyObject parent, Source source)
|
||||
{
|
||||
if (parent is TextBlock text && Equals(text.Tag, "TextLayerPreview")
|
||||
&& ReferenceEquals(text.DataContext, source))
|
||||
return text;
|
||||
|
||||
for (var i = 0; i < VisualTreeHelper.GetChildrenCount(parent); i++)
|
||||
{
|
||||
var result = FindTextPreview(VisualTreeHelper.GetChild(parent, i), source);
|
||||
if (result != null) return result;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user