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:
+63
-1
@@ -2,7 +2,8 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:Helpers="clr-namespace:ytLive.Helpers"
|
||||
xmlns:models="clr-namespace:ytLive.Models">
|
||||
xmlns:models="clr-namespace:ytLive.Models"
|
||||
xmlns:media="clr-namespace:System.Windows.Media;assembly=PresentationCore">
|
||||
<UserControl.Resources>
|
||||
<!-- TASK 63: the border-colour drop-down is the app's own YtComboBox, so the
|
||||
colour entries are drawn by TEMPLATES, in the theme's colours and with the
|
||||
@@ -317,6 +318,67 @@
|
||||
<ScrollViewer x:Name="ElementsScrollViewer" Grid.Row="6" VerticalScrollBarVisibility="Auto" Background="Transparent"
|
||||
Visibility="{Binding SelectedElement, Converter={StaticResource NotNullToVis}}">
|
||||
<StackPanel x:Name="ElementsPanel" Margin="8,4,0,0">
|
||||
<StackPanel Visibility="{Binding SelectedElement.IsTextOverlay, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
|
||||
<TextBlock Text="Text" Style="{StaticResource PropertyHeader}"/>
|
||||
<TextBlock Text="Static text — it will not animate automatically. Move and resize it in the preview."
|
||||
Style="{StaticResource PropertyHint}" Margin="0,0,0,6" TextWrapping="Wrap"/>
|
||||
<TextBox x:Name="TextLayerEditor" Text="{Binding SelectedElement.TextContent, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Style="{StaticResource YtTextBoxCompact}" AcceptsReturn="True" TextWrapping="Wrap"
|
||||
VerticalScrollBarVisibility="Auto" MinHeight="72" MaxHeight="120"
|
||||
AutomationProperties.Name="Text layer content"/>
|
||||
<TextBlock Text="Font" Style="{StaticResource PropertyHeader}"/>
|
||||
<ComboBox IsEditable="True" ItemsSource="{x:Static media:Fonts.SystemFontFamilies}"
|
||||
DisplayMemberPath="Source"
|
||||
Text="{Binding SelectedElement.TextFontFamily, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Style="{StaticResource YtComboBox}" Height="26" Margin="0,0,0,6"
|
||||
AutomationProperties.Name="Text layer font"/>
|
||||
<DockPanel Margin="0,0,0,6" LastChildFill="False">
|
||||
<TextBlock DockPanel.Dock="Left" Text="Size" Style="{StaticResource PropertyFieldLabel}"
|
||||
Width="42" VerticalAlignment="Center"/>
|
||||
<ComboBox IsEditable="True"
|
||||
Text="{Binding SelectedElement.TextFontSize, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Style="{StaticResource YtComboBox}" Height="26" Width="76"
|
||||
AutomationProperties.Name="Text layer size">
|
||||
<ComboBoxItem Content="12"/><ComboBoxItem Content="16"/>
|
||||
<ComboBoxItem Content="20"/><ComboBoxItem Content="24"/>
|
||||
<ComboBoxItem Content="32"/><ComboBoxItem Content="36"/>
|
||||
<ComboBoxItem Content="48"/><ComboBoxItem Content="60"/>
|
||||
<ComboBoxItem Content="72"/><ComboBoxItem Content="96"/>
|
||||
<ComboBoxItem Content="120"/><ComboBoxItem Content="144"/>
|
||||
<ComboBoxItem Content="180"/><ComboBoxItem Content="240"/>
|
||||
<ComboBoxItem Content="300"/>
|
||||
</ComboBox>
|
||||
</DockPanel>
|
||||
<DockPanel Margin="0,0,0,6" LastChildFill="False">
|
||||
<TextBlock DockPanel.Dock="Left" Text="Color" Style="{StaticResource PropertyFieldLabel}"
|
||||
Width="42" VerticalAlignment="Center"/>
|
||||
<TextBox x:Name="TextLayerColorEditor"
|
||||
Text="{Binding SelectedElement.TextColor, Mode=TwoWay, UpdateSourceTrigger=PropertyChanged}"
|
||||
Style="{StaticResource YtTextBoxCompact}" MaxLength="9" Width="82"
|
||||
ToolTip="#RRGGBB or #AARRGGBB" AutomationProperties.Name="Text layer color"/>
|
||||
<Border Width="18" Height="18" Margin="8,0,0,0" CornerRadius="2"
|
||||
Background="{Binding SelectedElement.TextColor, Converter={StaticResource HexToBrush}}"
|
||||
BorderBrush="{StaticResource Brush.Outline}" BorderThickness="1"
|
||||
VerticalAlignment="Center"/>
|
||||
</DockPanel>
|
||||
<WrapPanel Margin="0,0,0,4">
|
||||
<CheckBox Style="{StaticResource PropertyPillCheck}" Content="Bold" Margin="0,0,8,4"
|
||||
IsChecked="{Binding SelectedElement.TextBold, Mode=TwoWay}"/>
|
||||
<CheckBox Style="{StaticResource PropertyPillCheck}" Content="Italic" Margin="0,0,8,4"
|
||||
IsChecked="{Binding SelectedElement.TextItalic, Mode=TwoWay}"/>
|
||||
<CheckBox Style="{StaticResource PropertyPillCheck}" Content="Underline" Margin="0,0,8,4"
|
||||
IsChecked="{Binding SelectedElement.TextUnderline, Mode=TwoWay}"/>
|
||||
</WrapPanel>
|
||||
<TextBlock Text="Alignment" Style="{StaticResource PropertyHeader}"/>
|
||||
<ComboBox SelectedValuePath="Tag"
|
||||
SelectedValue="{Binding SelectedElement.TextAlignment, Mode=TwoWay}"
|
||||
Style="{StaticResource YtComboBox}" Height="26">
|
||||
<ComboBoxItem Content="Left" Tag="{x:Static models:TextLayerAlignment.Left}"/>
|
||||
<ComboBoxItem Content="Center" Tag="{x:Static models:TextLayerAlignment.Center}"/>
|
||||
<ComboBoxItem Content="Right" Tag="{x:Static models:TextLayerAlignment.Right}"/>
|
||||
</ComboBox>
|
||||
</StackPanel>
|
||||
|
||||
<!-- Opacity + mirror/shape (draggable elements: image, webcam) -->
|
||||
<StackPanel Visibility="{Binding SelectedElement.IsDraggable, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
|
||||
<StackPanel Visibility="{Binding SelectedElement.IsChatBox, Converter={StaticResource InverseBoolToVis}}">
|
||||
|
||||
@@ -2,7 +2,8 @@
|
||||
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:Helpers="clr-namespace:ytLive.Helpers"
|
||||
xmlns:local="clr-namespace:ytLive" xmlns:controls="clr-namespace:ytLive.Controls">
|
||||
xmlns:local="clr-namespace:ytLive" xmlns:controls="clr-namespace:ytLive.Controls"
|
||||
xmlns:models="clr-namespace:ytLive.Models">
|
||||
<Border Background="{StaticResource Brush.SurfaceContainerLowest}" CornerRadius="6" Margin="8,0"
|
||||
BorderBrush="{Binding PreviewGlowBrush}" BorderThickness="{Binding PreviewGlowThickness}">
|
||||
<Grid>
|
||||
@@ -175,6 +176,41 @@
|
||||
</Style>
|
||||
</Image.Style>
|
||||
</Image>
|
||||
<TextBlock Tag="TextLayerPreview" Text="{Binding TextContent}"
|
||||
FontFamily="{Binding TextFontFamily}"
|
||||
FontSize="{Binding TextFontSize}"
|
||||
Foreground="{Binding TextColor, Converter={StaticResource HexToBrush}}"
|
||||
TextWrapping="Wrap" TextTrimming="CharacterEllipsis"
|
||||
IsHitTestVisible="False">
|
||||
<TextBlock.Style>
|
||||
<Style TargetType="TextBlock">
|
||||
<Setter Property="Visibility" Value="Collapsed"/>
|
||||
<Setter Property="TextAlignment" Value="Left"/>
|
||||
<Setter Property="FontWeight" Value="Normal"/>
|
||||
<Setter Property="FontStyle" Value="Normal"/>
|
||||
<Style.Triggers>
|
||||
<DataTrigger Binding="{Binding IsTextOverlay}" Value="True">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding TextBold}" Value="True">
|
||||
<Setter Property="FontWeight" Value="Bold"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding TextItalic}" Value="True">
|
||||
<Setter Property="FontStyle" Value="Italic"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding TextUnderline}" Value="True">
|
||||
<Setter Property="TextDecorations" Value="{x:Static TextDecorations.Underline}"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding TextAlignment}" Value="{x:Static models:TextLayerAlignment.Center}">
|
||||
<Setter Property="TextAlignment" Value="Center"/>
|
||||
</DataTrigger>
|
||||
<DataTrigger Binding="{Binding TextAlignment}" Value="{x:Static models:TextLayerAlignment.Right}">
|
||||
<Setter Property="TextAlignment" Value="Right"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</TextBlock.Style>
|
||||
</TextBlock>
|
||||
<Viewbox Stretch="Uniform">
|
||||
<Grid Width="1" Height="1">
|
||||
<!-- Round webcam: a plain Image (the same efficient WriteableBitmap
|
||||
|
||||
+2
-2
@@ -10,9 +10,9 @@ See [`schema.md`](../schema.md) for the memory-map conventions.
|
||||
| `BottomBar.xaml(.cs)` | Footer (Row 3): intentionally empty, height-preserving spacer; stream readouts and resolution picker moved to the top-left cell in the 2026-10-26 surface redesign |
|
||||
| `HealthBanner.xaml(.cs)` | Row 1 status banner + the 7 value→visibility converters live in the app theme, not here |
|
||||
| `TopBar.xaml(.cs)` | Row 0 session surface: brand + gear menu (left), explicit `EDIT` toggle beside the REC|ON-AIR mode switch and world actions (Record: Start Recording · Stream: Go Live + Test), running-state reality line + End (center), account zone (right). Left-click gear opens the menu; right-click opens App Settings directly. `RefreshAvatar()`, `GearButton_Click` |
|
||||
| `PreviewPane.xaml(.cs)` | Row 2 Col 1 center: 1920×1080 `OverlayCanvas`/`SelectionOverlay`/`BrandFlashLayer`/`SocialBarElement`, text drawer, outgoing-picture transition overlay, right-edge Stream Info/YPP/Socials/TEST tabs and drawers, and live controls (TRAX / game / mic meters + sliders + speakers). The list-selected draggable layer is temporarily raised in preview z-order and custom hit-testing; deselection restores the scene's unchanged order (`SelectedLayerPreviewHitTestingTests`). **TASK 57:** dragging/resizing a webcam in the vertical tier routes through `MainViewModel.MoveWebcamInVerticalTier` / `ResizeWebcamInVerticalTier` for the six-spot snap or Chat camera's two sizes. |
|
||||
| `PreviewPane.xaml(.cs)` | Row 2 Col 1 center: 1920×1080 `OverlayCanvas`/`SelectionOverlay`/`BrandFlashLayer`/`SocialBarElement`, static TextOverlay `TextBlock` preview, text drawer, outgoing-picture transition overlay, right-edge Stream Info/YPP/Socials/TEST tabs and drawers, and live controls (TRAX / game / mic meters + sliders + speakers). The list-selected draggable layer is temporarily raised in preview z-order and custom hit-testing; deselection restores the scene's unchanged order (`SelectedLayerPreviewHitTestingTests`). **TASK 57:** dragging/resizing a webcam in the vertical tier routes through `MainViewModel.MoveWebcamInVerticalTier` / `ResizeWebcamInVerticalTier` for the six-spot snap or Chat camera's two sizes. |
|
||||
| `SceneThumbnailStrip.xaml(.cs)` | Top row over the preview column: five fixed-size scene cards with scene name, hotkey, staged `PROGRAM` badge, orange staged/live border |
|
||||
| `LeftPanel.xaml(.cs)` | Row 2 Col 0 left panel: explicit EditingActive toggle on → Scene Layers+properties; off → YouTube chat, independent of stream status. Source add/edit/remove, list drag-to-reorder, snapshot revert-accept. **TASK 51:** the webcam properties carry the Background section — `RemoveBackgroundPill` / `BlurBackgroundPill` (`PillToggle`, radio-exclusive via `WebcamSceneConfig.RemoveBackground`/`BlurBackground`) + `BackgroundStrengthSlider` (0..1, 5% snaps, shown only while an effect is on). **TASK 57:** the webcam properties carry a Geometry section (X/Y/W/H) — manual placement of the camera, bound to the **live** placement (the one the current tier shows), gated on `IsWebcamSelected` (not `SelectedElement.IsWebcam` — the TASK 51 test finds that panel by binding text and would match twice); the Shape button's `Visibility` is bound to `CanToggleClipShape`, so the circle option is NOT OFFERED in the vertical Chat layout. **TASK 58:** every drop-down, check box and field in the properties panel carries the theme's style (`YtComboBox`/`YtCheckBox`/`YtTextBoxCompact`) and paints no colour of its own — the guard is `ytLive.Tests/PropertiesStyleTests.cs`. **TASK 59:** the webcam's properties are a HIERARCHY — a `Shape` title owns the mirror switch and the shape choice (`Rect` | `Circle` as two `SegmentToggle` options, Rect selected by default, the circle option hidden where `CanToggleClipShape` is false), and every titled block (Geometry, Border Color, Border Thickness, Background) owns its content one indent deeper (Strength sits under Blur). **TASK 60:** the border-colour drop-down builds its menu from `Models/BorderColorPalette.cs` — each row is the hex plus the colour's NAME (`#e94560 Crimson`, tooltip "Crimson (#e94560)"); the swatch icon is unchanged. **TASK 61:** the four geometry fields (X/Y/W/H) share one fixed width (44px — the largest value, 1920, plus a sign) and their rows set `LastChildFill="False"`, so Y/H no longer stretch to fill the row. **TASK 62:** the border-colour field shows the palette's name beside the hex (`SelectedElement.BorderColorName`, empty for a custom hex) and keeps a fixed width so the name stays next to the hex. **TASK 63:** the border colour is picked with the **app's own drop-down** (`YtComboBox`) — the `...` button and its code-built `ContextMenu` are gone; the closed box shows the swatch + the colour's NAME, the rows show swatch + hex + name (the panel's own `DataTemplate`s + `Helpers/ClosedBoxTemplateSelector.cs`), and the hex field stays for a custom colour; `IsDescendantOf` falls back to `LogicalTreeHelper.GetParent` / `TemplatedParent` when `VisualTreeHelper.GetParent` returns `null` (at `PopupRoot`) so clicking a popup row does not trigger `MainWindow`'s deselection guard |
|
||||
| `LeftPanel.xaml(.cs)` | Row 2 Col 0 left panel: explicit EditingActive toggle on → Scene Layers+properties; off → YouTube chat, independent of stream status. Source add/edit/remove, list drag-to-reorder, snapshot revert-accept; TextOverlay properties edit multiline content, font, size, color, emphasis, and alignment with a static-text notice. **TASK 51:** the webcam properties carry the Background section — `RemoveBackgroundPill` / `BlurBackgroundPill` (`PillToggle`, radio-exclusive via `WebcamSceneConfig.RemoveBackground`/`BlurBackground`) + `BackgroundStrengthSlider` (0..1, 5% snaps, shown only while an effect is on). **TASK 57:** the webcam properties carry a Geometry section (X/Y/W/H) — manual placement of the camera, bound to the **live** placement (the one the current tier shows), gated on `IsWebcamSelected` (not `SelectedElement.IsWebcam` — the TASK 51 test finds that panel by binding text and would match twice); the Shape button's `Visibility` is bound to `CanToggleClipShape`, so the circle option is NOT OFFERED in the vertical Chat layout. **TASK 58:** every drop-down, check box and field in the properties panel carries the theme's style (`YtComboBox`/`YtCheckBox`/`YtTextBoxCompact`) and paints no colour of its own — the guard is `ytLive.Tests/PropertiesStyleTests.cs`. **TASK 59:** the webcam's properties are a HIERARCHY — a `Shape` title owns the mirror switch and the shape choice (`Rect` | `Circle` as two `SegmentToggle` options, Rect selected by default, the circle option hidden where `CanToggleClipShape` is false), and every titled block (Geometry, Border Color, Border Thickness, Background) owns its content one indent deeper (Strength sits under Blur). **TASK 60:** the border-colour drop-down builds its menu from `Models/BorderColorPalette.cs` — each row is the hex plus the colour's NAME (`#e94560 Crimson`, tooltip "Crimson (#e94560)"); the swatch icon is unchanged. **TASK 61:** the four geometry fields (X/Y/W/H) share one fixed width (44px — the largest value, 1920, plus a sign) and their rows set `LastChildFill="False"`, so Y/H no longer stretch to fill the row. **TASK 62:** the border-colour field shows the palette's name beside the hex (`SelectedElement.BorderColorName`, empty for a custom hex) and keeps a fixed width so the name stays next to the hex. **TASK 63:** the border colour is picked with the **app's own drop-down** (`YtComboBox`) — the `...` button and its code-built `ContextMenu` are gone; the closed box shows the swatch + the colour's NAME, the rows show swatch + hex + name (the panel's own `DataTemplate`s + `Helpers/ClosedBoxTemplateSelector.cs`), and the hex field stays for a custom colour; `IsDescendantOf` falls back to `LogicalTreeHelper.GetParent` / `TemplatedParent` when `VisualTreeHelper.GetParent` returns `null` (at `PopupRoot`) so clicking a popup row does not trigger `MainWindow`'s deselection guard |
|
||||
| `OverlayHost.xaml(.cs)` | Root-grid overlays: App Settings / Report Bug / Feature Request / About; About includes the output-quality conversion copy and an explicit llamacasty.com link, while the licensing/legal panel remains in-app. App Settings groups recording, brand-logo, preview, and transition controls. `BackToAbout_Click` lives here |
|
||||
| `SceneThumbnailStrip.xaml.cs` | Code-behind for the strip above |
|
||||
| `LeftPanel.xaml.cs` | Code-behind for the panel above |
|
||||
|
||||
Reference in New Issue
Block a user