TASK 64 slices D and E: the feature is usable end to end. - Add a video file → one frame is probed (new `IChromaProbe`/`FfmpegChromaProbe`, one ffmpeg pass at 160x90, off the UI thread) and the layer gets an offer: "Looks like a green screen — Remove it", with the measured colour as a swatch. - Accepting it turns the offer into settings (Amount / Softness / Fringe) that reach ffmpeg as `colorkey` + `despill` + `format=bgra`, so the key is computed in the pipe and both consumers (the shared preview bitmap and the compositor's frame) carry the same alpha. - Media sessions are now keyed by **path + look** (`MainViewModel.MediaSessionKey`), so two layers with the same settings still share one decoder, and changing the look releases the old session and starts the new one. Removal, load reconciliation, loop and audio settings all move to the same key. - Settings persist on the `Source` row (six ALTER TABLE guards + SELECT/INSERT), so a keyed layer comes back keyed. - Panel section is plain creator words: Green/Blue Screen, Amount, Softness, Fringe. Test: `ChromaKeyApplyTests` — the pipe asks ffmpeg to key (and does not when off); a green-screen clip is offered, accepting it starts a keyed session, the layer's resolver then reads that session rather than the old one; the setting survives a save. Docs: TASK 64 D/E ticked with slice F (auto-apply setting + alert clips) left open; frame-pipeline records the decode-option design; HANDOFF rewritten. verify.sh: 544 passed, 0 failed, 0 warnings.
1214 lines
109 KiB
XML
1214 lines
109 KiB
XML
<UserControl x:Class="ytLive.Controls.LeftPanel"
|
||
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">
|
||
<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
|
||
theme's HexToBrush converter — no hand-built menu, no hand-painted brush.
|
||
The closed box shows the swatch + the colour's NAME; the rows show the
|
||
swatch + the hex + the name. -->
|
||
<DataTemplate x:Key="BorderColorClosedTemplate">
|
||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||
<Border Width="12" Height="12" CornerRadius="2" VerticalAlignment="Center"
|
||
Background="{Binding Hex, Converter={StaticResource HexToBrush}}"
|
||
BorderBrush="{StaticResource Brush.Outline}" BorderThickness="1"/>
|
||
<TextBlock Text="{Binding Name}" Margin="6,0,0,0" FontSize="12"
|
||
Foreground="{StaticResource Brush.OnSurface}" VerticalAlignment="Center"/>
|
||
</StackPanel>
|
||
</DataTemplate>
|
||
<DataTemplate x:Key="BorderColorRowTemplate">
|
||
<StackPanel Orientation="Horizontal" VerticalAlignment="Center">
|
||
<Border Width="12" Height="12" CornerRadius="2" VerticalAlignment="Center"
|
||
Background="{Binding Hex, Converter={StaticResource HexToBrush}}"
|
||
BorderBrush="{StaticResource Brush.Outline}" BorderThickness="1"/>
|
||
<TextBlock Text="{Binding Hex}" Margin="6,0,0,0" FontSize="12"
|
||
Foreground="{StaticResource Brush.OnSurface}" VerticalAlignment="Center"/>
|
||
<TextBlock Text="{Binding Name}" Margin="8,0,0,0" FontSize="12"
|
||
Foreground="{StaticResource Brush.OnSurfaceVariant}" VerticalAlignment="Center"/>
|
||
</StackPanel>
|
||
</DataTemplate>
|
||
<Helpers:ClosedBoxTemplateSelector x:Key="BorderColorTemplates"
|
||
ClosedTemplate="{StaticResource BorderColorClosedTemplate}"
|
||
RowTemplate="{StaticResource BorderColorRowTemplate}"/>
|
||
</UserControl.Resources>
|
||
|
||
<Border Background="{StaticResource Brush.SurfaceContainer}" CornerRadius="6" Padding="12">
|
||
<Grid>
|
||
<Grid.RowDefinitions>
|
||
<RowDefinition Height="*"/>
|
||
</Grid.RowDefinitions>
|
||
|
||
<!-- Edit mode (offline): layers + properties -->
|
||
<Grid Grid.Row="0" Visibility="{Binding IsEditMode, Converter={StaticResource BoolToVis}}">
|
||
<Grid.RowDefinitions>
|
||
<RowDefinition Height="Auto"/>
|
||
<RowDefinition Height="Auto"/>
|
||
<RowDefinition Height="Auto"/>
|
||
<RowDefinition Height="Auto"/>
|
||
<RowDefinition Height="Auto"/>
|
||
<RowDefinition Height="Auto"/>
|
||
<RowDefinition Height="*"/>
|
||
</Grid.RowDefinitions>
|
||
|
||
<!-- LAYERS header + Add button -->
|
||
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,8,0,0">
|
||
<Path Data="M12,3 L21,8 L12,13 L3,8 Z M3,12 L12,17 L21,12 M3,16 L12,21 L21,16"
|
||
Stroke="{StaticResource Brush.Tertiary}" StrokeThickness="1.8" StrokeLineJoin="Round"
|
||
Width="16" Height="16" Stretch="Uniform" VerticalAlignment="Center" Margin="0,-4,6,0"/>
|
||
<TextBlock Text="SCENE LAYERS" Style="{StaticResource SectionHeader}" VerticalAlignment="Center"/>
|
||
<Border Width="20" Height="20" CornerRadius="10" Background="{StaticResource Brush.Outline}" Margin="6,-4,0,0" VerticalAlignment="Center">
|
||
<TextBlock Text="{Binding LayerCount}" Foreground="{StaticResource Brush.Tertiary}" FontSize="11" FontWeight="SemiBold"
|
||
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
||
</Border>
|
||
<Button ToolTip="Add Layer" Style="{StaticResource IconButton}" Margin="4,-4,0,0"
|
||
Click="AddSourceButton_Click">
|
||
<Button.ContextMenu>
|
||
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
|
||
<MenuItem Header="Webcam" Command="{Binding AddWebcamCommand}"
|
||
IsEnabled="{Binding CanAddWebcam}"
|
||
ToolTip="{Binding WebcamAddToolTip}"/>
|
||
<MenuItem Header="Image" Command="{Binding AddImageCommand}"/>
|
||
<MenuItem Header="Video File" Command="{Binding AddMediaCommand}"
|
||
ToolTip="Play a video file in the scene (mp4, mov, avi, mkv, webm)"/>
|
||
<MenuItem Header="Text" Command="{Binding AddSourceCommand}" CommandParameter="{x:Static models:SourceType.TextOverlay}"/>
|
||
<MenuItem Header="Countdown" Command="{Binding AddSourceCommand}" CommandParameter="{x:Static models:SourceType.Countdown}"
|
||
Visibility="{Binding IsCountdownAvailable, Converter={StaticResource BoolToVis}}"
|
||
ToolTip="Timer overlay — Starting and BRB screens only"/>
|
||
<MenuItem Header="Web Resource" Command="{Binding AddSourceCommand}" CommandParameter="{x:Static models:SourceType.WebSource}"
|
||
ToolTip="Embedded web page — set URI in Properties panel"/>
|
||
<MenuItem Header="YouTube Chat" Command="{Binding AddSourceCommand}" CommandParameter="{x:Static models:SourceType.ChatBox}"
|
||
IsEnabled="{Binding CanAddYouTubeChat}"
|
||
ToolTip="One chat layer at a time — it's already in your stream"/>
|
||
<MenuItem Header="Stream Alerts" Command="{Binding AddSourceCommand}" CommandParameter="{x:Static models:SourceType.AlertBox}"
|
||
IsEnabled="{Binding CanAddAlerts}"
|
||
ToolTip="Celebration zone for Super Chat & memberships — one alert layer at a time"/>
|
||
<MenuItem Header="Ticker" Command="{Binding AddSourceCommand}" CommandParameter="{x:Static models:SourceType.Ticker}"
|
||
IsEnabled="{Binding CanAddTicker}"
|
||
ToolTip="Recent alert feed — one independently configured ticker per scene"/>
|
||
<!-- Privacy blur: Live scene only (creator ruling 2026-11-01). Sits
|
||
above the desktop and below the brand logo; several allowed. -->
|
||
<MenuItem Header="Blur" Command="{Binding AddSourceCommand}" CommandParameter="{x:Static models:SourceType.Blur}"
|
||
Visibility="{Binding CanAddBlur, Converter={StaticResource BoolToVis}}"
|
||
ToolTip="Hide part of your desktop from viewers — box or circle"/>
|
||
</ContextMenu>
|
||
</Button.ContextMenu>
|
||
<Path Data="M12,4 L12,20 M4,12 L20,12" Stroke="{StaticResource Brush.Tertiary}" StrokeThickness="2"
|
||
Width="14" Height="14" Stretch="Uniform"/>
|
||
</Button>
|
||
</StackPanel>
|
||
|
||
<Helpers:FocusPreservingListBox x:Name="SourceList" Grid.Row="1" MinHeight="84" MaxHeight="240" Background="Transparent" BorderThickness="0"
|
||
ItemsSource="{Binding StagedScene.Elements}"
|
||
SelectedItem="{Binding SelectedElement, Mode=TwoWay}"
|
||
PreviewMouseLeftButtonDown="List_PreviewMouseLeftButtonDown"
|
||
PreviewMouseMove="List_PreviewMouseMove"
|
||
PreviewMouseLeftButtonUp="List_PreviewMouseLeftButtonUp"
|
||
Foreground="{StaticResource Brush.OnSurface}" FontSize="13">
|
||
<Helpers:FocusPreservingListBox.ItemContainerStyle>
|
||
<Style TargetType="ListBoxItem" BasedOn="{StaticResource YtListBoxItem}">
|
||
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
||
<Style.Triggers>
|
||
<DataTrigger Binding="{Binding IsVisible}" Value="False">
|
||
<Setter Property="Opacity" Value="0.45"/>
|
||
</DataTrigger>
|
||
<!-- The transient zoom layer has no editing UI — never
|
||
show it as a layer row (TASK 51). -->
|
||
<DataTrigger Binding="{Binding IsZoomLayer}" Value="True">
|
||
<Setter Property="Visibility" Value="Collapsed"/>
|
||
</DataTrigger>
|
||
</Style.Triggers>
|
||
</Style>
|
||
</Helpers:FocusPreservingListBox.ItemContainerStyle>
|
||
<ListBox.ItemTemplate>
|
||
<DataTemplate>
|
||
<Grid Margin="8,2,0,2"
|
||
Tag="{Binding DataContext.CanChangeBackground, RelativeSource={RelativeSource AncestorType=Window}}">
|
||
<Grid.ColumnDefinitions>
|
||
<ColumnDefinition Width="Auto"/>
|
||
<ColumnDefinition Width="*"/>
|
||
<ColumnDefinition Width="Auto"/>
|
||
<ColumnDefinition Width="Auto"/>
|
||
<ColumnDefinition Width="Auto"/>
|
||
</Grid.ColumnDefinitions>
|
||
|
||
|
||
<Grid.ContextMenu>
|
||
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
|
||
<MenuItem Header="Change Capture…" Click="BackgroundMenu_ChangeCapture">
|
||
<MenuItem.Visibility>
|
||
<MultiBinding Converter="{StaticResource AllTrueToVis}">
|
||
<Binding Path="IsBackground"/>
|
||
<Binding Path="PlacementTarget.Tag" RelativeSource="{RelativeSource AncestorType={x:Type ContextMenu}}"/>
|
||
</MultiBinding>
|
||
</MenuItem.Visibility>
|
||
</MenuItem>
|
||
<MenuItem Header="Refresh Capture" Click="BackgroundMenu_RefreshCapture">
|
||
<MenuItem.Visibility>
|
||
<MultiBinding Converter="{StaticResource AllTrueToVis}">
|
||
<Binding Path="IsBackground"/>
|
||
<Binding Path="PlacementTarget.Tag" RelativeSource="{RelativeSource AncestorType={x:Type ContextMenu}}"/>
|
||
</MultiBinding>
|
||
</MenuItem.Visibility>
|
||
</MenuItem>
|
||
<MenuItem Header="Capture Window…"
|
||
ItemsSource="{Binding DataContext.OpenWindows, RelativeSource={RelativeSource AncestorType=Window}}">
|
||
<MenuItem.Visibility>
|
||
<MultiBinding Converter="{StaticResource AllTrueToVis}">
|
||
<Binding Path="IsBackground"/>
|
||
<Binding Path="PlacementTarget.Tag" RelativeSource="{RelativeSource AncestorType={x:Type ContextMenu}}"/>
|
||
</MultiBinding>
|
||
</MenuItem.Visibility>
|
||
<MenuItem.ItemContainerStyle>
|
||
<Style TargetType="MenuItem">
|
||
<Setter Property="Header" Value="{Binding Label}"/>
|
||
<Setter Property="Command"
|
||
Value="{Binding DataContext.CaptureWindowCommand, RelativeSource={RelativeSource AncestorType=Window}}"/>
|
||
<Setter Property="CommandParameter" Value="{Binding}"/>
|
||
</Style>
|
||
</MenuItem.ItemContainerStyle>
|
||
</MenuItem>
|
||
<MenuItem Header="Refresh Window List" Click="BackgroundMenu_RefreshWindowList">
|
||
<MenuItem.Visibility>
|
||
<MultiBinding Converter="{StaticResource AllTrueToVis}">
|
||
<Binding Path="IsBackground"/>
|
||
<Binding Path="PlacementTarget.Tag" RelativeSource="{RelativeSource AncestorType={x:Type ContextMenu}}"/>
|
||
</MultiBinding>
|
||
</MenuItem.Visibility>
|
||
</MenuItem>
|
||
<Separator>
|
||
<Separator.Visibility>
|
||
<MultiBinding Converter="{StaticResource AllTrueToVis}">
|
||
<Binding Path="IsBackground"/>
|
||
<Binding Path="PlacementTarget.Tag" RelativeSource="{RelativeSource AncestorType={x:Type ContextMenu}}"/>
|
||
</MultiBinding>
|
||
</Separator.Visibility>
|
||
</Separator>
|
||
<MenuItem Header="Remove Layer" Click="SourceMenu_Remove"
|
||
Visibility="{Binding IsBackground, Converter={StaticResource InverseBoolToVis}}"/>
|
||
</ContextMenu>
|
||
</Grid.ContextMenu>
|
||
|
||
<Grid Grid.Column="0" Width="16" Height="16" Margin="0,0,6,0" VerticalAlignment="Center">
|
||
<Path Data="M7,8 H17 A4,4 0 0 1 21,12 V14 A3,3 0 0 1 17,16 L15,14 H9 L7,16 A3,3 0 0 1 3,14 V12 A4,4 0 0 1 7,8 Z M8,11 V13 M7,12 H9 M15,11 H15.01 M17,13 H17.01" Stroke="{StaticResource Brush.Tertiary}" StrokeThickness="1.6" StrokeLineJoin="Round" Stretch="Uniform">
|
||
<Path.Style><Style TargetType="Path"><Setter Property="Visibility" Value="Collapsed"/><Style.Triggers><DataTrigger Binding="{Binding IsLiveCapture}" Value="True"><Setter Property="Visibility" Value="Visible"/></DataTrigger></Style.Triggers></Style></Path.Style>
|
||
</Path>
|
||
<Path Data="M3,5 H21 V19 H3 Z M3,16 L9,10 L13,14 L16,11 L21,16" Stroke="{StaticResource Brush.Tertiary}" StrokeThickness="1.6" StrokeLineJoin="Round" Stretch="Uniform">
|
||
<Path.Style><Style TargetType="Path"><Setter Property="Visibility" Value="Collapsed"/><Style.Triggers><MultiDataTrigger><MultiDataTrigger.Conditions><Condition Binding="{Binding IsBackground}" Value="True"/><Condition Binding="{Binding IsLiveCapture}" Value="False"/></MultiDataTrigger.Conditions><Setter Property="Visibility" Value="Visible"/></MultiDataTrigger></Style.Triggers></Style></Path.Style>
|
||
</Path>
|
||
<Path Data="M3,7 H15 V17 H3 Z M15,11 L21,8 V16 L15,13" Stroke="{StaticResource Brush.Tertiary}" StrokeThickness="1.6" StrokeLineJoin="Round" Stretch="Uniform">
|
||
<Path.Style><Style TargetType="Path"><Setter Property="Visibility" Value="Collapsed"/><Style.Triggers><DataTrigger Binding="{Binding IsWebcam}" Value="True"><Setter Property="Visibility" Value="Visible"/></DataTrigger></Style.Triggers></Style></Path.Style>
|
||
</Path>
|
||
<Path Data="M4,4 H20 V20 H4 Z M4,16 L9,11 L13,15 L16,12 L20,16 M15,8 H15.01" Stroke="{StaticResource Brush.Tertiary}" StrokeThickness="1.6" StrokeLineJoin="Round" Stretch="Uniform">
|
||
<Path.Style><Style TargetType="Path"><Setter Property="Visibility" Value="Collapsed"/><Style.Triggers><DataTrigger Binding="{Binding IsImageSource}" Value="True"><Setter Property="Visibility" Value="Visible"/></DataTrigger></Style.Triggers></Style></Path.Style>
|
||
</Path>
|
||
<Path Data="M5,5 H19 M12,5 V19 M8,19 H16" Stroke="{StaticResource Brush.Tertiary}" StrokeThickness="1.6" StrokeLineJoin="Round" Stretch="Uniform">
|
||
<Path.Style><Style TargetType="Path"><Setter Property="Visibility" Value="Collapsed"/><Style.Triggers><DataTrigger Binding="{Binding IsTextOverlay}" Value="True"><Setter Property="Visibility" Value="Visible"/></DataTrigger></Style.Triggers></Style></Path.Style>
|
||
</Path>
|
||
<Path Data="M12,3 A9,9 0 1 0 12.01,3 M12,7 V12 L15,14" Stroke="{StaticResource Brush.Tertiary}" StrokeThickness="1.6" StrokeLineJoin="Round" Stretch="Uniform">
|
||
<Path.Style><Style TargetType="Path"><Setter Property="Visibility" Value="Collapsed"/><Style.Triggers><DataTrigger Binding="{Binding IsCountdown}" Value="True"><Setter Property="Visibility" Value="Visible"/></DataTrigger></Style.Triggers></Style></Path.Style>
|
||
</Path>
|
||
<Path Data="M12,3 A9,9 0 1 0 12.01,3 M3,12 H21 M12,3 C8,7 8,17 12,21 M12,3 C16,7 16,17 12,21" Stroke="{StaticResource Brush.Tertiary}" StrokeThickness="1.6" StrokeLineJoin="Round" Stretch="Uniform">
|
||
<Path.Style><Style TargetType="Path"><Setter Property="Visibility" Value="Collapsed"/><Style.Triggers><DataTrigger Binding="{Binding IsWebSource}" Value="True"><Setter Property="Visibility" Value="Visible"/></DataTrigger></Style.Triggers></Style></Path.Style>
|
||
</Path>
|
||
<Path Data="M4,5 H20 V16 H10 L6,20 V16 H4 Z" Stroke="{StaticResource Brush.Tertiary}" StrokeThickness="1.6" StrokeLineJoin="Round" Stretch="Uniform">
|
||
<Path.Style><Style TargetType="Path"><Setter Property="Visibility" Value="Collapsed"/><Style.Triggers><DataTrigger Binding="{Binding IsChatBox}" Value="True"><Setter Property="Visibility" Value="Visible"/></DataTrigger></Style.Triggers></Style></Path.Style>
|
||
</Path>
|
||
<Path Data="M4,5 H20 V19 H4 Z M10,9 L15,12 L10,15 Z" Stroke="{StaticResource Brush.Tertiary}" StrokeThickness="1.6" StrokeLineJoin="Round" Stretch="Uniform">
|
||
<Path.Style><Style TargetType="Path"><Setter Property="Visibility" Value="Collapsed"/><Style.Triggers><DataTrigger Binding="{Binding IsMediaSource}" Value="True"><Setter Property="Visibility" Value="Visible"/></DataTrigger></Style.Triggers></Style></Path.Style>
|
||
</Path>
|
||
<Path Data="M3,9 H21 V15 H3 Z M6,12 H12 M15,12 H18" Stroke="{StaticResource Brush.Tertiary}" StrokeThickness="1.6" StrokeLineJoin="Round" Stretch="Uniform">
|
||
<Path.Style><Style TargetType="Path"><Setter Property="Visibility" Value="Collapsed"/><Style.Triggers><DataTrigger Binding="{Binding IsScoreboard}" Value="True"><Setter Property="Visibility" Value="Visible"/></DataTrigger></Style.Triggers></Style></Path.Style>
|
||
</Path>
|
||
<Path Data="M6,16 V11 A6,6 0 0 1 18,11 V16 L20,18 H4 Z M10,20 H14" Stroke="{StaticResource Brush.Tertiary}" StrokeThickness="1.6" StrokeLineJoin="Round" Stretch="Uniform">
|
||
<Path.Style><Style TargetType="Path"><Setter Property="Visibility" Value="Collapsed"/><Style.Triggers><DataTrigger Binding="{Binding IsAlertBox}" Value="True"><Setter Property="Visibility" Value="Visible"/></DataTrigger></Style.Triggers></Style></Path.Style>
|
||
</Path>
|
||
<Path Data="M3,6 H21 M3,12 H21 M3,18 H21" Stroke="{StaticResource Brush.Tertiary}" StrokeThickness="1.6" Stretch="Uniform">
|
||
<Path.Style><Style TargetType="Path"><Setter Property="Visibility" Value="Collapsed"/><Style.Triggers><DataTrigger Binding="{Binding IsTicker}" Value="True"><Setter Property="Visibility" Value="Visible"/></DataTrigger></Style.Triggers></Style></Path.Style>
|
||
</Path>
|
||
</Grid>
|
||
|
||
<TextBlock Grid.Column="1" Text="{Binding Name}" VerticalAlignment="Center"
|
||
Padding="4,2" TextTrimming="CharacterEllipsis">
|
||
<TextBlock.Style>
|
||
<Style TargetType="TextBlock">
|
||
<Setter Property="Visibility" Value="Visible"/>
|
||
<Style.Triggers>
|
||
<DataTrigger Binding="{Binding IsEditing}" Value="True">
|
||
<Setter Property="Visibility" Value="Collapsed"/>
|
||
</DataTrigger>
|
||
</Style.Triggers>
|
||
</Style>
|
||
</TextBlock.Style>
|
||
</TextBlock>
|
||
|
||
<TextBox Grid.Column="1" Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"
|
||
BorderThickness="0" FontSize="12"
|
||
IsVisibleChanged="SourceNameBox_IsVisibleChanged"
|
||
KeyDown="SourceNameBox_KeyDown" LostFocus="SourceNameBox_LostFocus">
|
||
<TextBox.Style>
|
||
<Style TargetType="TextBox" BasedOn="{StaticResource YtTextBoxCompact}">
|
||
<Setter Property="Visibility" Value="Collapsed"/>
|
||
<Style.Triggers>
|
||
<DataTrigger Binding="{Binding IsEditing}" Value="True">
|
||
<Setter Property="Visibility" Value="Visible"/>
|
||
</DataTrigger>
|
||
</Style.Triggers>
|
||
</Style>
|
||
</TextBox.Style>
|
||
</TextBox>
|
||
|
||
<Button Grid.Column="2" ToolTip="Edit Layer Name"
|
||
Command="{Binding DataContext.EditElementCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
|
||
CommandParameter="{Binding}" Style="{StaticResource IconButton}">
|
||
<Path Data="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"
|
||
Fill="{StaticResource Brush.OnSurface}" Width="13" Height="13" Stretch="Uniform"/>
|
||
</Button>
|
||
|
||
<Button Grid.Column="3" Style="{StaticResource EyeButton}"
|
||
Command="{Binding DataContext.ToggleElementVisibilityCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
|
||
CommandParameter="{Binding}">
|
||
<Path Style="{StaticResource EyeIconStyle}" Fill="{StaticResource Brush.OnSurface}"
|
||
Width="13" Height="13" Stretch="Uniform"/>
|
||
</Button>
|
||
|
||
<Button Grid.Column="4" ToolTip="Remove Layer"
|
||
Command="{Binding DataContext.RemoveSourceCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
|
||
CommandParameter="{Binding}" Style="{StaticResource IconButton}"
|
||
Visibility="{Binding IsBackground, Converter={StaticResource HiddenBoolToVis}}">
|
||
<Path Data="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"
|
||
Fill="{StaticResource Brush.OnSurface}" Width="13" Height="13" Stretch="Uniform"/>
|
||
</Button>
|
||
</Grid>
|
||
</DataTemplate>
|
||
</ListBox.ItemTemplate>
|
||
</Helpers:FocusPreservingListBox>
|
||
|
||
<TextBlock Grid.Row="2" Text="No layers yet" Foreground="{StaticResource Brush.OnSurfaceDisabled}" FontSize="12"
|
||
HorizontalAlignment="Center" VerticalAlignment="Center"
|
||
Visibility="{Binding ShowSourcesEmptyHint, Converter={StaticResource BoolToVis}}"/>
|
||
|
||
<!-- Resource alert: pinned to the bottom of the Layers panel.
|
||
Red when a required resource (the one-and-only webcam)
|
||
could not be locked; persists and re-polls until resolved. -->
|
||
<Border Grid.Row="3" Margin="0,8,0,0" Padding="8,6" CornerRadius="4"
|
||
Background="{StaticResource Brush.ErrorContainer}" BorderBrush="{StaticResource Brush.Error}" BorderThickness="1"
|
||
Visibility="{Binding WebcamLockAlert, Converter={StaticResource NotNullToVis}}">
|
||
<StackPanel>
|
||
<TextBlock Text="{Binding WebcamLockAlert}" Foreground="{StaticResource Brush.OnError}" FontSize="11"
|
||
TextWrapping="Wrap"/>
|
||
<Button Content="Retry" Command="{Binding RetryWebcamLockCommand}"
|
||
Style="{StaticResource PropertyButton}" Padding="10,2"
|
||
HorizontalAlignment="Right" Margin="0,6,0,0"/>
|
||
</StackPanel>
|
||
</Border>
|
||
|
||
<!-- ELEMENTS panel: per-element config beneath Sources.
|
||
Collapses entirely when no element is selected. -->
|
||
<Rectangle Grid.Row="4" Height="1" Fill="{StaticResource Brush.Outline}" Margin="0,22,0,10"/>
|
||
<StackPanel Grid.Row="5" Orientation="Horizontal" Margin="0,8,0,0">
|
||
<Path Data="M12,8 A4,4 0 1 0 12.01,8 M19.4,15 L21,16.2 L19.4,19 L17.2,18.3 A7,7 0 0 1 15,19.4 L14.5,21.5 H9.5 L9,19.4 A7,7 0 0 1 6.8,18.3 L4.6,19 L3,16.2 L4.6,15 A7,7 0 0 1 4.6,9 L3,7.8 L4.6,5 L6.8,5.7 A7,7 0 0 1 9,4.6 L9.5,2.5 H14.5 L15,4.6 A7,7 0 0 1 17.2,5.7 L19.4,5 L21,7.8 L19.4,9 A7,7 0 0 1 19.4,15 Z"
|
||
Stroke="{StaticResource Brush.Tertiary}" StrokeThickness="1.6" StrokeLineJoin="Round"
|
||
Width="16" Height="16" Stretch="Uniform" VerticalAlignment="Center" Margin="0,0,6,0"/>
|
||
<TextBlock Text="PROPERTIES" Style="{StaticResource SectionHeader}" VerticalAlignment="Center"/>
|
||
</StackPanel>
|
||
<TextBlock Grid.Row="6" Text="Select an item to edit its properties" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="12"
|
||
Margin="8,4,0,0" TextWrapping="Wrap"
|
||
Visibility="{Binding SelectedElement, Converter={StaticResource NullToVis}}"/>
|
||
<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="10,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" Margin="10,0,0,8"
|
||
AutomationProperties.Name="Text layer content"/>
|
||
<TextBlock Text="Typography" Style="{StaticResource PropertyHeader}"/>
|
||
<StackPanel x:Name="TextTypographyOptions" Margin="10,0,0,6">
|
||
<TextBlock Text="Font Type" Style="{StaticResource PropertyFieldLabel}" Margin="0,0,0,3"/>
|
||
<ComboBox x:Name="TextLayerFontTypePicker"
|
||
ItemsSource="{Binding TextFontTypeOptions}"
|
||
SelectedItem="{Binding SelectedTextFontType, Mode=TwoWay}"
|
||
Style="{StaticResource YtComboBox}" Height="30" Margin="0,0,0,6"/>
|
||
<TextBlock Text="Font" Style="{StaticResource PropertyFieldLabel}" Margin="0,0,0,3"/>
|
||
<ComboBox x:Name="TextLayerFontPicker"
|
||
ItemsSource="{Binding TextFontFamilies}"
|
||
SelectedItem="{Binding SelectedTextFontFamily, Mode=TwoWay}"
|
||
Style="{StaticResource YtComboBox}" Height="30" Margin="0,0,0,6"
|
||
AutomationProperties.Name="Text layer font"/>
|
||
<DockPanel Margin="0,0,0,0" LastChildFill="False">
|
||
<TextBlock DockPanel.Dock="Left" Text="Size" Style="{StaticResource PropertyFieldLabel}"
|
||
Width="30" VerticalAlignment="Center"/>
|
||
<ComboBox x:Name="TextLayerSizePicker" DockPanel.Dock="Left"
|
||
ItemsSource="{Binding TextFontSizeOptions}"
|
||
SelectedItem="{Binding SelectedElement.TextFontSize, Mode=TwoWay}"
|
||
Style="{StaticResource YtComboBox}" Height="30" Width="62"
|
||
AutomationProperties.Name="Text layer size"/>
|
||
<TextBlock DockPanel.Dock="Left" Text="Color" Style="{StaticResource PropertyFieldLabel}"
|
||
Width="34" Margin="6,0,0,0" VerticalAlignment="Center"/>
|
||
<TextBox x:Name="TextLayerColorEditor" DockPanel.Dock="Left"
|
||
Text="{Binding SelectedElement.TextColor, Mode=TwoWay, UpdateSourceTrigger=LostFocus}"
|
||
Style="{StaticResource YtTextBoxCompact}" MaxLength="9" Width="68"
|
||
ToolTip="#RRGGBB or #AARRGGBB" AutomationProperties.Name="Text layer color"/>
|
||
<ComboBox x:Name="TextLayerColorPicker" DockPanel.Dock="Left"
|
||
ItemsSource="{Binding SelectedElement.TextColorOptions}"
|
||
SelectedItem="{Binding SelectedElement.TextColorEntry, Mode=TwoWay}"
|
||
ItemTemplateSelector="{StaticResource BorderColorTemplates}"
|
||
Style="{StaticResource YtComboBox}" Width="100" Margin="4,0,0,0"
|
||
ToolTip="Choose a text color" AutomationProperties.Name="Text layer color palette"/>
|
||
</DockPanel>
|
||
</StackPanel>
|
||
<TextBlock Text="Emphasis" Style="{StaticResource PropertyHeader}"/>
|
||
<StackPanel x:Name="TextEmphasisOptions" Margin="10,0,0,4">
|
||
<DockPanel Margin="0,0,0,4" LastChildFill="False">
|
||
<TextBlock DockPanel.Dock="Left" Text="Bold" Style="{StaticResource PropertyFieldLabel}"
|
||
Width="72" VerticalAlignment="Center"/>
|
||
<ToggleButton x:Name="TextBoldToggle" Style="{StaticResource PillToggle}"
|
||
IsChecked="{Binding SelectedElement.TextBold, Mode=TwoWay}"
|
||
ToolTip="Bold text" HorizontalAlignment="Left"/>
|
||
</DockPanel>
|
||
<DockPanel Margin="0,0,0,4" LastChildFill="False">
|
||
<TextBlock DockPanel.Dock="Left" Text="Italic" Style="{StaticResource PropertyFieldLabel}"
|
||
Width="72" VerticalAlignment="Center"/>
|
||
<ToggleButton x:Name="TextItalicToggle" Style="{StaticResource PillToggle}"
|
||
IsChecked="{Binding SelectedElement.TextItalic, Mode=TwoWay}"
|
||
ToolTip="Italic text" HorizontalAlignment="Left"/>
|
||
</DockPanel>
|
||
<DockPanel LastChildFill="False">
|
||
<TextBlock DockPanel.Dock="Left" Text="Underline" Style="{StaticResource PropertyFieldLabel}"
|
||
Width="72" VerticalAlignment="Center"/>
|
||
<ToggleButton x:Name="TextUnderlineToggle" Style="{StaticResource PillToggle}"
|
||
IsChecked="{Binding SelectedElement.TextUnderline, Mode=TwoWay}"
|
||
ToolTip="Underline text" HorizontalAlignment="Left"/>
|
||
</DockPanel>
|
||
</StackPanel>
|
||
<TextBlock Text="Alignment" Style="{StaticResource PropertyHeader}"/>
|
||
<DockPanel Margin="10,0,0,4">
|
||
<TextBlock DockPanel.Dock="Left" Text="Horizontal" Style="{StaticResource PropertyFieldLabel}"
|
||
Width="72" VerticalAlignment="Center"/>
|
||
<ComboBox x:Name="TextLayerHorizontalAlignmentPicker"
|
||
ItemsSource="{Binding TextHorizontalAlignmentOptions}"
|
||
SelectedItem="{Binding SelectedElement.TextAlignment, Mode=TwoWay}"
|
||
Style="{StaticResource YtComboBox}" Height="30"
|
||
AutomationProperties.Name="Horizontal text alignment"/>
|
||
</DockPanel>
|
||
<DockPanel Margin="10,0,0,0">
|
||
<TextBlock DockPanel.Dock="Left" Text="Vertical" Style="{StaticResource PropertyFieldLabel}"
|
||
Width="72" VerticalAlignment="Center"/>
|
||
<ComboBox x:Name="TextLayerVerticalAlignmentPicker"
|
||
ItemsSource="{Binding TextVerticalAlignmentOptions}"
|
||
SelectedItem="{Binding SelectedElement.TextVerticalAlignment, Mode=TwoWay}"
|
||
Style="{StaticResource YtComboBox}" Height="30"
|
||
AutomationProperties.Name="Vertical text alignment"/>
|
||
</DockPanel>
|
||
</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}}">
|
||
<StackPanel Visibility="{Binding SelectedElement.IsBlur, Converter={StaticResource InverseBoolToVis}}">
|
||
<TextBlock Text="Opacity" Style="{StaticResource PropertyHeader}"/>
|
||
<DockPanel Margin="10,0,0,6">
|
||
<TextBlock x:Name="OpacityValueText" Text="100%" Style="{StaticResource PropertyFieldLabel}"
|
||
DockPanel.Dock="Right" Margin="8,0,0,0" Width="32"/>
|
||
<Slider x:Name="OpacitySlider" Width="120" Minimum="0" Maximum="1"
|
||
Value="{Binding SelectedElement.Opacity, Mode=TwoWay}"
|
||
ValueChanged="OpacitySlider_ValueChanged" VerticalAlignment="Center"/>
|
||
</DockPanel>
|
||
</StackPanel>
|
||
</StackPanel>
|
||
<!-- TASK 59: the shape is a CHOICE (Rect | Circle) under a
|
||
"Shape" title, with Rect selected by default — the circle
|
||
is not offered in the vertical Chat layout, so that option
|
||
is not even shown there (CanToggleClipShape). -->
|
||
<StackPanel Visibility="{Binding IsWebcamSelected, Converter={StaticResource BoolToVis}}">
|
||
<TextBlock Text="Shape" Style="{StaticResource PropertyHeader}"/>
|
||
<StackPanel Orientation="Horizontal" Margin="10,0,0,4">
|
||
<Button Content="{Binding SelectedElement.MirrorButtonText}"
|
||
Style="{StaticResource PropertyButton}" Padding="10,4"
|
||
Click="MirrorButton_Click"/>
|
||
<ToggleButton Style="{StaticResource SegmentToggle}" Padding="10,4" Margin="8,0,0,0"
|
||
IsChecked="{Binding SelectedElement.IsRectShape, Mode=TwoWay}">
|
||
<TextBlock Text="Rect" Style="{StaticResource SegmentLabel}"/>
|
||
</ToggleButton>
|
||
<ToggleButton Style="{StaticResource SegmentToggle}" Padding="10,4" Margin="4,0,0,0"
|
||
IsChecked="{Binding SelectedElement.IsRoundShape, Mode=TwoWay}"
|
||
Visibility="{Binding CanToggleClipShape, Converter={StaticResource BoolToVis}}">
|
||
<TextBlock Text="Circle" Style="{StaticResource SegmentLabel}"/>
|
||
</ToggleButton>
|
||
</StackPanel>
|
||
</StackPanel>
|
||
<!-- Privacy blur (creator ruling 2026-11-01): a shape choice and one
|
||
strength slider, on the SAME 0–1 scale and default as the
|
||
webcam's background blur — one radius curve, shared. -->
|
||
<StackPanel Visibility="{Binding IsBlurSelected, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
|
||
<TextBlock Text="Blur" Style="{StaticResource PropertyHeader}"/>
|
||
<StackPanel Orientation="Horizontal" Margin="10,0,0,6">
|
||
<ToggleButton Style="{StaticResource SegmentToggle}" Padding="10,4"
|
||
IsChecked="{Binding SelectedElement.IsRectShape, Mode=TwoWay}">
|
||
<TextBlock Text="Box" Style="{StaticResource SegmentLabel}"/>
|
||
</ToggleButton>
|
||
<ToggleButton Style="{StaticResource SegmentToggle}" Padding="10,4" Margin="4,0,0,0"
|
||
IsChecked="{Binding SelectedElement.IsRoundShape, Mode=TwoWay}">
|
||
<TextBlock Text="Circle" Style="{StaticResource SegmentLabel}"/>
|
||
</ToggleButton>
|
||
</StackPanel>
|
||
<DockPanel Margin="10,0,0,4">
|
||
<TextBlock DockPanel.Dock="Right" Style="{StaticResource PropertyFieldLabel}" Margin="8,0,0,0" Width="34">
|
||
<Run Text="{Binding SelectedElement.BlurAmount, Mode=OneWay, StringFormat={}{0:P0}}"/>
|
||
</TextBlock>
|
||
<Slider Minimum="0" Maximum="1" SmallChange="0.05" LargeChange="0.1"
|
||
Value="{Binding SelectedElement.BlurAmount, Mode=TwoWay}"
|
||
VerticalAlignment="Center"/>
|
||
</DockPanel>
|
||
<TextBlock Text="Strongest blur wins where they overlap — layers cannot add up." Style="{StaticResource PropertyHint}"/>
|
||
</StackPanel>
|
||
|
||
<!-- The drag hint sits under the shape row — indented, so the
|
||
parentage shows. -->
|
||
<TextBlock Text="Drag to move · drag the corner dot to resize"
|
||
Style="{StaticResource PropertyHint}" Margin="10,2,0,0"/>
|
||
</StackPanel>
|
||
|
||
<!-- Webcam geometry (TASK 57): under the vertical tier the camera
|
||
keeps its OWN placement, so these fields edit the LIVE one —
|
||
the placement the current tier shows. Gated on the VM's
|
||
IsWebcamSelected (the same condition as the mirror/shape row
|
||
above) so the webcam's border/background section stays the
|
||
one panel bound to SelectedElement.IsWebcam. -->
|
||
<StackPanel Visibility="{Binding SelectedElement.IsDraggable, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
|
||
<TextBlock Text="Geometry" Style="{StaticResource PropertyHeader}"/>
|
||
<!-- TASK 61: the four fields share ONE fixed width — wide enough
|
||
for the largest geometry value (1920, plus a typed sign) and
|
||
no wider, so Y/H no longer stretch to fill the row. -->
|
||
<DockPanel Margin="10,0,0,4" LastChildFill="False">
|
||
<TextBlock DockPanel.Dock="Left" Text="X" Style="{StaticResource PropertyFieldLabel}" Width="16"/>
|
||
<TextBox Text="{Binding SelectedElement.X, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
||
Style="{StaticResource YtTextBoxCompact}" Width="44"/>
|
||
<TextBlock DockPanel.Dock="Left" Text=" Y" Style="{StaticResource PropertyFieldLabel}" Width="20" Margin="8,0,0,0"/>
|
||
<TextBox Text="{Binding SelectedElement.Y, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
||
Style="{StaticResource YtTextBoxCompact}" Width="44"/>
|
||
</DockPanel>
|
||
<DockPanel Margin="10,0,0,4" LastChildFill="False">
|
||
<TextBlock DockPanel.Dock="Left" Text="W" Style="{StaticResource PropertyFieldLabel}" Width="16"/>
|
||
<TextBox Text="{Binding SelectedElement.Width, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
||
Style="{StaticResource YtTextBoxCompact}" Width="44"/>
|
||
<TextBlock DockPanel.Dock="Left" Text=" H" Style="{StaticResource PropertyFieldLabel}" Width="20" Margin="8,0,0,0"/>
|
||
<TextBox Text="{Binding SelectedElement.Height, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
||
Style="{StaticResource YtTextBoxCompact}" Width="44"/>
|
||
</DockPanel>
|
||
</StackPanel>
|
||
|
||
<!-- Webcam border config -->
|
||
<StackPanel Visibility="{Binding SelectedElement.IsWebcam, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
|
||
<TextBlock Text="Border Color" Style="{StaticResource PropertyHeader}"/>
|
||
<!-- TASK 63: the border colour is picked with the app's OWN
|
||
drop-down (YtComboBox) — the same widget the Display
|
||
and Font pickers use — not a hand-rolled menu. The hex
|
||
field stays for a colour the creator types; the
|
||
drop-down carries the colour's name. -->
|
||
<DockPanel Margin="10,0,0,6">
|
||
<TextBox DockPanel.Dock="Left"
|
||
Text="{Binding SelectedElement.BorderColor, UpdateSourceTrigger=PropertyChanged}"
|
||
Style="{StaticResource YtTextBoxCompact}"
|
||
MaxLength="7" ToolTip="#RRGGBB" Width="60"
|
||
VerticalAlignment="Center"/>
|
||
<ComboBox ItemsSource="{Binding SelectedElement.BorderColorOptions}"
|
||
SelectedItem="{Binding SelectedElement.BorderColorEntry, Mode=TwoWay}"
|
||
ItemTemplateSelector="{StaticResource BorderColorTemplates}"
|
||
Style="{StaticResource YtComboBox}"
|
||
ToolTip="Border colour" MinWidth="100"
|
||
Margin="8,0,0,0" VerticalAlignment="Center"/>
|
||
</DockPanel>
|
||
<TextBlock Text="Border Thickness" Style="{StaticResource PropertyHeader}"/>
|
||
<DockPanel Margin="10,0,0,6">
|
||
<Button DockPanel.Dock="Right" Style="{StaticResource IconButton}"
|
||
Width="22" Height="22" Margin="4,0,0,0"
|
||
Click="WebcamBorderAccept_Click">
|
||
<Path Data="M4,9 L8,14 L16,4" Stroke="{StaticResource Brush.Positive}" StrokeThickness="2"
|
||
Width="14" Height="14" Stretch="Uniform"/>
|
||
</Button>
|
||
<Button DockPanel.Dock="Right" Style="{StaticResource IconButton}"
|
||
Width="22" Height="22" ToolTip="Revert"
|
||
Click="WebcamBorderRevert_Click" Margin="4,0,0,0">
|
||
<Path Data="M5,5 L15,15 M15,5 L5,15" Stroke="{StaticResource Brush.OnSurfaceVariant}" StrokeThickness="2"
|
||
Width="14" Height="14" Stretch="Uniform"/>
|
||
</Button>
|
||
<Slider DockPanel.Dock="Left" Width="120" Minimum="0" Maximum="10"
|
||
Value="{Binding SelectedElement.BorderWidth, Mode=TwoWay}"
|
||
VerticalAlignment="Center" TickFrequency="1" IsSnapToTickEnabled="True"/>
|
||
<TextBlock Text="{Binding SelectedElement.BorderWidth}" Style="{StaticResource PropertyFieldLabel}" Margin="8,0,0,0" Width="20"/>
|
||
</DockPanel>
|
||
|
||
<!-- TASK 51: background effects — two pills acting as radio buttons
|
||
(one on turns the other off; both off = no effect). TASK 59:
|
||
the pills are indented under the Background title, and the
|
||
strength row one level deeper, under Blur. -->
|
||
<TextBlock Text="Background" Style="{StaticResource PropertyHeader}"/>
|
||
<DockPanel Margin="10,0,0,4">
|
||
<TextBlock DockPanel.Dock="Left" Text="Remove" Style="{StaticResource PropertyFieldLabel}" Width="84"/>
|
||
<ToggleButton x:Name="RemoveBackgroundPill" Style="{StaticResource PillToggle}"
|
||
IsChecked="{Binding SelectedElement.RemoveBackground, Mode=TwoWay}"
|
||
AutomationProperties.Name="Remove background"
|
||
ToolTip="Remove the background behind you — the layers below show through"
|
||
HorizontalAlignment="Left"/>
|
||
</DockPanel>
|
||
<DockPanel Margin="10,0,0,4">
|
||
<TextBlock DockPanel.Dock="Left" Text="Blur" Style="{StaticResource PropertyFieldLabel}" Width="84"/>
|
||
<ToggleButton x:Name="BlurBackgroundPill" Style="{StaticResource PillToggle}"
|
||
IsChecked="{Binding SelectedElement.BlurBackground, Mode=TwoWay}"
|
||
AutomationProperties.Name="Blur background"
|
||
ToolTip="Blur the background behind you"
|
||
HorizontalAlignment="Left"/>
|
||
</DockPanel>
|
||
<DockPanel Margin="20,0,0,6"
|
||
Visibility="{Binding SelectedElement.HasBackgroundEffect, Converter={StaticResource BoolToVis}}">
|
||
<TextBlock DockPanel.Dock="Left" Text="Strength" Style="{StaticResource PropertyFieldLabel}" Width="84"/>
|
||
<TextBlock DockPanel.Dock="Right" Text="{Binding SelectedElement.BackgroundStrength, StringFormat={}{0:P0}}"
|
||
Style="{StaticResource PropertyFieldLabel}" Margin="8,0,0,0" Width="34"/>
|
||
<Slider x:Name="BackgroundStrengthSlider" Minimum="0" Maximum="1"
|
||
Value="{Binding SelectedElement.BackgroundStrength, Mode=TwoWay}"
|
||
TickFrequency="0.05" IsSnapToTickEnabled="True" VerticalAlignment="Center"
|
||
AutomationProperties.Name="Background effect strength"
|
||
ToolTip="Blur: how strong the blur is · Remove: how soft the edge is"/>
|
||
</DockPanel>
|
||
</StackPanel>
|
||
|
||
<!-- Scoreboard config (creator 2026-10-30): the ego wall's billboard —
|
||
which eight facts show, and how the strip is painted. -->
|
||
<!-- The scoreboard is fed by the signed-in channel, so its controls are
|
||
inert until there is an account; signing in re-enables them through
|
||
the ordinary IsConnected notification. -->
|
||
<StackPanel Visibility="{Binding SelectedElement.IsScoreboard, Converter={StaticResource BoolToVis}}"
|
||
IsEnabled="{Binding CanUseScoreboard}" Margin="0,0,0,8">
|
||
<TextBlock Text="Sign in to YouTube to use the scoreboard."
|
||
Style="{StaticResource PropertyHint}" Margin="0,0,0,4"
|
||
Visibility="{Binding CanUseScoreboard, Converter={StaticResource InverseBoolToVis}}"/>
|
||
<!-- DEBUG builds only: prove the strip in the preview before a real
|
||
stream. Absent from Release, not merely hidden by a flag. -->
|
||
<CheckBox Style="{StaticResource PropertyPillCheck}" Margin="0,0,0,4"
|
||
IsChecked="{Binding ScoreboardSimulate, Mode=TwoWay}"
|
||
ToolTip="Push sample events so the strip appears in the preview."
|
||
Visibility="{Binding IsDevelopmentBuild, Converter={StaticResource BoolToVis}}">
|
||
<!-- Dev-only affordances wear orange so a development control can never
|
||
be mistaken for product. -->
|
||
<TextBlock Text="Simulate (dev)" Style="{StaticResource PropertyFieldLabel}"
|
||
Foreground="{StaticResource Brush.Attention}"/>
|
||
</CheckBox>
|
||
<CheckBox Style="{StaticResource PropertyPillCheck}" Margin="0,0,0,4"
|
||
Content="Show the scoreboard" IsChecked="{Binding ScoreboardEnabled, Mode=TwoWay}"
|
||
ToolTip="Off hides the strip in every scene AND stops the channel polling."/>
|
||
<TextBlock Text="Edge (socials take the other)" Style="{StaticResource PropertyHeader}"/>
|
||
<ComboBox Style="{StaticResource YtComboBox}" Height="24" FontSize="11" Margin="10,0,0,6"
|
||
ItemsSource="{Binding ScoreboardEdgeOptions}"
|
||
SelectedItem="{Binding ScoreboardEdge, Mode=TwoWay}"/>
|
||
|
||
<DockPanel Margin="10,0,0,6">
|
||
<TextBlock DockPanel.Dock="Left" Text="Scroll speed" Style="{StaticResource PropertyFieldLabel}" Width="76"/>
|
||
<TextBlock DockPanel.Dock="Right" Width="56" TextAlignment="Right"
|
||
Style="{StaticResource PropertyFieldLabel}"
|
||
Text="{Binding ScoreboardScrollSpeed, StringFormat={}{0:0} px/s}"/>
|
||
<Slider Minimum="20" Maximum="200" SmallChange="10" LargeChange="40"
|
||
Value="{Binding ScoreboardScrollSpeed, Mode=TwoWay}" VerticalAlignment="Center"/>
|
||
</DockPanel>
|
||
|
||
<TextBlock Text="Facts" Style="{StaticResource PropertyHeader}"/>
|
||
<WrapPanel Margin="10,0,0,0">
|
||
<CheckBox Style="{StaticResource PropertyPillCheck}" Content="Subscribers" Margin="0,0,10,4"
|
||
IsChecked="{Binding ScoreboardShowSubscribers, Mode=TwoWay}"/>
|
||
<CheckBox Style="{StaticResource PropertyPillCheck}" Content="Watch hours" Margin="0,0,10,4"
|
||
IsChecked="{Binding ScoreboardShowWatchHours, Mode=TwoWay}"/>
|
||
<CheckBox Style="{StaticResource PropertyPillCheck}" Content="Super Chats" Margin="0,0,10,4"
|
||
IsChecked="{Binding ScoreboardShowSuperChats, Mode=TwoWay}"/>
|
||
<CheckBox Style="{StaticResource PropertyPillCheck}" Content="New members" Margin="0,0,10,4"
|
||
IsChecked="{Binding ScoreboardShowNewMembers, Mode=TwoWay}"/>
|
||
<CheckBox Style="{StaticResource PropertyPillCheck}" Content="Milestones" Margin="0,0,10,4"
|
||
IsChecked="{Binding ScoreboardShowMilestones, Mode=TwoWay}"/>
|
||
<CheckBox Style="{StaticResource PropertyPillCheck}" Content="Last member" Margin="0,0,10,4"
|
||
IsChecked="{Binding ScoreboardShowLastMember, Mode=TwoWay}"/>
|
||
<CheckBox Style="{StaticResource PropertyPillCheck}" Content="Last Super Chat" Margin="0,0,10,4"
|
||
IsChecked="{Binding ScoreboardShowLastSuperChat, Mode=TwoWay}"/>
|
||
<CheckBox Style="{StaticResource PropertyPillCheck}" Content="Last gift" Margin="0,0,10,4"
|
||
IsChecked="{Binding ScoreboardShowLastGift, Mode=TwoWay}"/>
|
||
</WrapPanel>
|
||
|
||
<TextBlock Text="Text colour" Style="{StaticResource PropertyHeader}"/>
|
||
<ComboBox Style="{StaticResource YtComboBox}" Height="24" FontSize="11" Margin="10,0,0,6"
|
||
ItemsSource="{Binding ScoreboardColorOptions}" DisplayMemberPath="Name"
|
||
SelectedValuePath="Hex" SelectedValue="{Binding ScoreboardTextColor, Mode=TwoWay}"/>
|
||
|
||
<CheckBox Style="{StaticResource PropertyPillCheck}" Content="Background" Margin="10,0,0,4"
|
||
IsChecked="{Binding ScoreboardBackgroundEnabled, Mode=TwoWay}"/>
|
||
<ComboBox Style="{StaticResource YtComboBox}" Height="24" FontSize="11" Margin="10,0,0,6"
|
||
ItemsSource="{Binding ScoreboardColorOptions}" DisplayMemberPath="Name"
|
||
SelectedValuePath="Hex" SelectedValue="{Binding ScoreboardBackgroundColor, Mode=TwoWay}"/>
|
||
<DockPanel Margin="10,0,0,6">
|
||
<TextBlock DockPanel.Dock="Left" Text="Opacity" Style="{StaticResource PropertyFieldLabel}" Width="60"/>
|
||
<TextBlock DockPanel.Dock="Right" Width="42" TextAlignment="Right"
|
||
Style="{StaticResource PropertyFieldLabel}"
|
||
Text="{Binding ScoreboardBackgroundOpacity, StringFormat={}{0:P0}}"/>
|
||
<Slider Minimum="0" Maximum="1" SmallChange="0.05" LargeChange="0.1"
|
||
Value="{Binding ScoreboardBackgroundOpacity, Mode=TwoWay}" VerticalAlignment="Center"/>
|
||
</DockPanel>
|
||
|
||
<DockPanel Margin="10,0,0,0">
|
||
<TextBlock DockPanel.Dock="Left" Text="Text size" Style="{StaticResource PropertyFieldLabel}" Width="60"/>
|
||
<TextBlock DockPanel.Dock="Right" Width="56" TextAlignment="Right"
|
||
Style="{StaticResource PropertyFieldLabel}"
|
||
Text="{Binding ScoreboardFontSizeLabel}"/>
|
||
<Slider Minimum="14" Maximum="24" SmallChange="1" LargeChange="2" TickFrequency="1" IsSnapToTickEnabled="True"
|
||
Value="{Binding ScoreboardFontSize, Mode=TwoWay}" VerticalAlignment="Center"/>
|
||
</DockPanel>
|
||
</StackPanel>
|
||
|
||
<!-- Countdown config -->
|
||
<StackPanel Visibility="{Binding SelectedElement.IsCountdown, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
|
||
<TextBlock Text="Timer (minutes)" Style="{StaticResource PropertyHeader}"/>
|
||
<DockPanel Margin="10,0,0,6">
|
||
<Button DockPanel.Dock="Right" Style="{StaticResource IconButton}"
|
||
Width="22" Height="22" Margin="4,0,0,0"
|
||
Click="CountdownAccept_Click">
|
||
<Path Data="M4,9 L8,14 L16,4" Stroke="{StaticResource Brush.Positive}" StrokeThickness="2"
|
||
Width="14" Height="14" Stretch="Uniform"/>
|
||
</Button>
|
||
<Button DockPanel.Dock="Right" Style="{StaticResource IconButton}"
|
||
Width="22" Height="22" ToolTip="Revert"
|
||
Click="CountdownRevert_Click" Margin="4,0,0,0">
|
||
<Path Data="M5,5 L15,15 M15,5 L5,15" Stroke="{StaticResource Brush.OnSurfaceVariant}" StrokeThickness="2"
|
||
Width="14" Height="14" Stretch="Uniform"/>
|
||
</Button>
|
||
<Slider DockPanel.Dock="Left" Width="120" Minimum="1" Maximum="60"
|
||
Value="{Binding SelectedElement.TimerMinutes, Mode=TwoWay}"
|
||
VerticalAlignment="Center" TickFrequency="1" IsSnapToTickEnabled="True"/>
|
||
<TextBlock Text="{Binding SelectedElement.TimerMinutes}" Style="{StaticResource PropertyFieldLabel}" Margin="8,0,0,0" Width="20"/>
|
||
</DockPanel>
|
||
</StackPanel>
|
||
|
||
<!-- Web source config -->
|
||
<StackPanel Visibility="{Binding SelectedElement.IsWebSource, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
|
||
<TextBlock Text="Geometry" Style="{StaticResource PropertyHeader}"/>
|
||
<DockPanel Margin="0,0,0,4">
|
||
<TextBlock DockPanel.Dock="Left" Text="X" Style="{StaticResource PropertyFieldLabel}" Width="16"/>
|
||
<TextBox Text="{Binding SelectedElement.X, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
||
Style="{StaticResource YtTextBoxCompact}"/>
|
||
<TextBlock DockPanel.Dock="Left" Text=" Y" Style="{StaticResource PropertyFieldLabel}" Width="20" Margin="8,0,0,0"/>
|
||
<TextBox Text="{Binding SelectedElement.Y, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
||
Style="{StaticResource YtTextBoxCompact}"/>
|
||
</DockPanel>
|
||
<DockPanel Margin="0,0,0,4">
|
||
<TextBlock DockPanel.Dock="Left" Text="W" Style="{StaticResource PropertyFieldLabel}" Width="16"/>
|
||
<TextBox Text="{Binding SelectedElement.Width, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
||
Style="{StaticResource YtTextBoxCompact}"/>
|
||
<TextBlock DockPanel.Dock="Left" Text=" H" Style="{StaticResource PropertyFieldLabel}" Width="20" Margin="8,0,0,0"/>
|
||
<TextBox Text="{Binding SelectedElement.Height, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
||
Style="{StaticResource YtTextBoxCompact}"/>
|
||
</DockPanel>
|
||
<TextBlock Text="URI" Style="{StaticResource PropertyHeader}"/>
|
||
<DockPanel Margin="10,0,0,0">
|
||
<Button DockPanel.Dock="Right" Style="{StaticResource IconButton}"
|
||
Width="22" Height="22" Margin="4,0,0,0" IsTabStop="False"
|
||
Click="WebAccept_Click">
|
||
<Path Data="M4,9 L8,14 L16,4" Stroke="{StaticResource Brush.Positive}" StrokeThickness="2"
|
||
Width="14" Height="14" Stretch="Uniform"/>
|
||
</Button>
|
||
<Button DockPanel.Dock="Right" Style="{StaticResource IconButton}"
|
||
Width="22" Height="22" ToolTip="Revert" IsTabStop="False"
|
||
Click="WebRevert_Click" Margin="4,0,0,0">
|
||
<Path Data="M5,5 L15,15 M15,5 L5,15" Stroke="{StaticResource Brush.OnSurfaceVariant}" StrokeThickness="2"
|
||
Width="14" Height="14" Stretch="Uniform"/>
|
||
</Button>
|
||
<TextBox Text="{Binding SelectedElement.WebUri, UpdateSourceTrigger=PropertyChanged}"
|
||
Style="{StaticResource YtTextBoxCompact}"
|
||
ToolTip="https://example.com"/>
|
||
</DockPanel>
|
||
</StackPanel>
|
||
|
||
<!-- Media (video file) config — TASK 21 -->
|
||
<StackPanel Visibility="{Binding SelectedElement.IsMediaSource, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
|
||
<TextBlock Text="Video File" Style="{StaticResource PropertyHeader}"/>
|
||
<Grid Margin="10,0,0,2">
|
||
<Grid.ColumnDefinitions>
|
||
<ColumnDefinition Width="*"/>
|
||
<ColumnDefinition Width="Auto"/>
|
||
</Grid.ColumnDefinitions>
|
||
<TextBlock Grid.Column="0" Text="{Binding SelectedElement.MediaFileDisplay}"
|
||
Style="{StaticResource PropertyFieldLabel}"
|
||
TextTrimming="CharacterEllipsis" VerticalAlignment="Center"
|
||
ToolTip="{Binding SelectedElement.MediaPath}"/>
|
||
<Button Grid.Column="1" Content="Change…" Command="{Binding ChangeMediaFileCommand}"
|
||
CommandParameter="{Binding SelectedElement}" Style="{StaticResource PropertyButton}"
|
||
Margin="4,0,0,0" Padding="8,2" FontSize="11"/>
|
||
</Grid>
|
||
<CheckBox Style="{StaticResource PropertyPillCheck}" Margin="10,4,0,4"
|
||
Content="Loop" IsChecked="{Binding SelectedElement.MediaIsLooping, Mode=TwoWay}"
|
||
ToolTip="Restart from the beginning at the end of the file — for starting-soon and BRB loops"/>
|
||
<DockPanel Margin="10,0,0,4">
|
||
<CheckBox DockPanel.Dock="Left" Style="{StaticResource PropertyPillCheck}"
|
||
Content="Mute" IsChecked="{Binding SelectedElement.MediaMuted, Mode=TwoWay}"
|
||
ToolTip="Silence this layer's sound. The picture keeps playing."/>
|
||
</DockPanel>
|
||
<DockPanel Margin="10,0,0,4">
|
||
<TextBlock DockPanel.Dock="Left" Text="Level" Style="{StaticResource PropertyFieldLabel}" Width="40"/>
|
||
<TextBlock DockPanel.Dock="Right" Text="{Binding SelectedElement.MediaVolume, StringFormat={}{0:P0}}"
|
||
Style="{StaticResource PropertyFieldLabel}" Width="34" TextAlignment="Right"/>
|
||
<Slider Minimum="0" Maximum="1" Width="120"
|
||
Value="{Binding SelectedElement.MediaVolume, Mode=TwoWay}"
|
||
VerticalAlignment="Center"/>
|
||
</DockPanel>
|
||
<TextBlock Text="Sound plays through the desktop audio controls, like game audio."
|
||
Style="{StaticResource PropertyHint}"/>
|
||
|
||
<!-- Green/Blue Screen (TASK 64): the app offers, the creator
|
||
accepts. No engineering words — Amount/Softness/Fringe. -->
|
||
<Border Style="{StaticResource PropertyDivider}"/>
|
||
<TextBlock Text="Green/Blue Screen" Style="{StaticResource PropertyHeader}"/>
|
||
|
||
<Border Margin="10,0,0,4" Padding="6,5" CornerRadius="4"
|
||
Background="{StaticResource Brush.Outline}"
|
||
Visibility="{Binding SelectedElement.HasChromaSuggestion, Converter={StaticResource BoolToVis}}">
|
||
<DockPanel>
|
||
<Border DockPanel.Dock="Left" Width="12" Height="12" CornerRadius="2"
|
||
Margin="0,0,6,0" VerticalAlignment="Center"
|
||
Background="{Binding SelectedElement.ChromaSuggestionColor}"
|
||
ToolTip="The screen colour this clip was shot against"/>
|
||
<Button DockPanel.Dock="Right" Content="Remove it"
|
||
Style="{StaticResource PropertyButton}" Padding="8,2" FontSize="11"
|
||
Command="{Binding AcceptChromaSuggestionCommand}"
|
||
CommandParameter="{Binding SelectedElement}"/>
|
||
<TextBlock Text="{Binding SelectedElement.ChromaSuggestionText}"
|
||
Style="{StaticResource PropertyFieldLabel}" VerticalAlignment="Center"/>
|
||
</DockPanel>
|
||
</Border>
|
||
|
||
<CheckBox Style="{StaticResource PropertyPillCheck}" Margin="10,0,0,4"
|
||
Content="Remove the background" IsChecked="{Binding SelectedElement.ChromaKeyEnabled, Mode=TwoWay}"
|
||
ToolTip="Key out the green/blue backdrop so the layers beneath show through"/>
|
||
<DockPanel Margin="10,0,0,4">
|
||
<TextBlock DockPanel.Dock="Left" Text="Amount" Style="{StaticResource PropertyFieldLabel}" Width="52"/>
|
||
<Slider Minimum="0" Maximum="0.5" Width="110"
|
||
Value="{Binding SelectedElement.ChromaKeySimilarity, Mode=TwoWay}"
|
||
VerticalAlignment="Center"/>
|
||
</DockPanel>
|
||
<DockPanel Margin="10,0,0,4">
|
||
<TextBlock DockPanel.Dock="Left" Text="Softness" Style="{StaticResource PropertyFieldLabel}" Width="52"/>
|
||
<Slider Minimum="0" Maximum="0.5" Width="110"
|
||
Value="{Binding SelectedElement.ChromaKeySoftness, Mode=TwoWay}"
|
||
VerticalAlignment="Center"/>
|
||
</DockPanel>
|
||
<CheckBox Style="{StaticResource PropertyPillCheck}" Margin="10,0,0,4"
|
||
Content="Fringe" IsChecked="{Binding SelectedElement.ChromaKeySpill, Mode=TwoWay}"
|
||
ToolTip="Clean the coloured rim the screen leaves on hair and edges"/>
|
||
</StackPanel>
|
||
|
||
<!-- Chat box config -->
|
||
<StackPanel Visibility="{Binding SelectedElement.IsChatBox, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
|
||
<TextBlock Text="Chat Overlay" Style="{StaticResource PropertyHeader}"/>
|
||
|
||
<!-- Border (same controls as the webcam's border) -->
|
||
<TextBlock Text="Border Color" Style="{StaticResource PropertyHeader}"/>
|
||
<DockPanel Margin="10,0,0,6">
|
||
<TextBox DockPanel.Dock="Left"
|
||
Text="{Binding SelectedElement.BorderColor, UpdateSourceTrigger=PropertyChanged}"
|
||
Style="{StaticResource YtTextBoxCompact}"
|
||
MaxLength="7" ToolTip="#RRGGBB" Width="60"
|
||
VerticalAlignment="Center"/>
|
||
<ComboBox ItemsSource="{Binding SelectedElement.BorderColorOptions}"
|
||
SelectedItem="{Binding SelectedElement.BorderColorEntry, Mode=TwoWay}"
|
||
ItemTemplateSelector="{StaticResource BorderColorTemplates}"
|
||
Style="{StaticResource YtComboBox}"
|
||
ToolTip="Border colour" MinWidth="100"
|
||
Margin="8,0,0,0" VerticalAlignment="Center"/>
|
||
</DockPanel>
|
||
<TextBlock Text="Border Thickness" Style="{StaticResource PropertyHeader}"/>
|
||
<DockPanel Margin="10,0,0,6">
|
||
<Slider DockPanel.Dock="Left" Width="120" Minimum="0" Maximum="10"
|
||
Value="{Binding SelectedElement.BorderWidth, Mode=TwoWay}"
|
||
VerticalAlignment="Center" TickFrequency="1" IsSnapToTickEnabled="True"/>
|
||
<TextBlock Text="{Binding SelectedElement.BorderWidth}" Style="{StaticResource PropertyFieldLabel}" Margin="8,0,0,0" Width="20"/>
|
||
</DockPanel>
|
||
<TextBlock Text="Border Opacity" Style="{StaticResource PropertyHeader}"/>
|
||
<DockPanel Margin="10,0,0,8">
|
||
<Slider DockPanel.Dock="Left" Width="120" Minimum="0" Maximum="1"
|
||
Value="{Binding SelectedElement.BorderOpacity, Mode=TwoWay}"
|
||
VerticalAlignment="Center"/>
|
||
<TextBlock Text="{Binding SelectedElement.BorderOpacity, StringFormat={}{0:P0}}" Style="{StaticResource PropertyFieldLabel}" Margin="8,0,0,0" Width="34"/>
|
||
</DockPanel>
|
||
|
||
<!-- Font Size -->
|
||
<DockPanel Margin="0,0,0,4">
|
||
<TextBlock DockPanel.Dock="Left" Text="Font Size" Style="{StaticResource PropertyFieldLabel}" Width="70"/>
|
||
<Slider x:Name="ChatFontSizeSlider" Width="120" Minimum="8" Maximum="72" TickFrequency="2" IsSnapToTickEnabled="True"
|
||
Value="{Binding SelectedElement.ChatFontSize, Mode=TwoWay}"
|
||
VerticalAlignment="Center"/>
|
||
<TextBlock DockPanel.Dock="Right" Text="{Binding SelectedElement.ChatFontSize, StringFormat={}{0:0}}" Foreground="{StaticResource Brush.OnSurface}" FontSize="11" Margin="6,0,0,0" VerticalAlignment="Center" Width="24"/>
|
||
</DockPanel>
|
||
|
||
<!-- Font Family -->
|
||
<DockPanel Margin="0,0,0,4">
|
||
<TextBlock DockPanel.Dock="Left" Text="Font" Style="{StaticResource PropertyFieldLabel}" Width="70"/>
|
||
<ComboBox SelectedValue="{Binding SelectedElement.ChatFontFamily, Mode=TwoWay}"
|
||
SelectedValuePath="Content"
|
||
Style="{StaticResource YtComboBox}"
|
||
Width="140" VerticalAlignment="Center">
|
||
<ComboBoxItem Content="Segoe UI"/>
|
||
<ComboBoxItem Content="Arial"/>
|
||
<ComboBoxItem Content="Consolas"/>
|
||
<ComboBoxItem Content="Courier New"/>
|
||
<ComboBoxItem Content="Comic Sans MS"/>
|
||
<ComboBoxItem Content="Mouse Memoirs"/>
|
||
</ComboBox>
|
||
</DockPanel>
|
||
|
||
<!-- Font Color -->
|
||
<DockPanel Margin="0,0,0,4">
|
||
<TextBlock DockPanel.Dock="Left" Text="Font Color" Style="{StaticResource PropertyFieldLabel}" Width="70"/>
|
||
<TextBox Text="{Binding SelectedElement.ChatFontColor, UpdateSourceTrigger=PropertyChanged}"
|
||
Style="{StaticResource YtTextBoxCompact}" Width="80"/>
|
||
</DockPanel>
|
||
|
||
<!-- Member Color -->
|
||
<DockPanel Margin="0,0,0,4">
|
||
<TextBlock DockPanel.Dock="Left" Text="Member" Style="{StaticResource PropertyFieldLabel}" Width="70"/>
|
||
<TextBox Text="{Binding SelectedElement.ChatMemberColor, UpdateSourceTrigger=PropertyChanged}"
|
||
Style="{StaticResource YtTextBoxCompact}" Width="80"/>
|
||
</DockPanel>
|
||
|
||
<!-- Super Chat Color -->
|
||
<DockPanel Margin="0,0,0,4">
|
||
<TextBlock DockPanel.Dock="Left" Text="Super Chat" Style="{StaticResource PropertyFieldLabel}" Width="70"/>
|
||
<TextBox Text="{Binding SelectedElement.ChatSuperChatColor, UpdateSourceTrigger=PropertyChanged}"
|
||
Style="{StaticResource YtTextBoxCompact}" Width="80"/>
|
||
</DockPanel>
|
||
|
||
<!-- Max Messages -->
|
||
<DockPanel Margin="0,0,0,4">
|
||
<TextBlock DockPanel.Dock="Left" Text="Max Msgs" Style="{StaticResource PropertyFieldLabel}" Width="70"/>
|
||
<Slider x:Name="ChatMaxMsgsSlider" Width="120" Minimum="1" Maximum="20" TickFrequency="1" IsSnapToTickEnabled="True"
|
||
Value="{Binding SelectedElement.ChatMaxMessages, Mode=TwoWay}"
|
||
VerticalAlignment="Center"/>
|
||
<TextBlock DockPanel.Dock="Right" Text="{Binding SelectedElement.ChatMaxMessages}" Foreground="{StaticResource Brush.OnSurface}" FontSize="11" Margin="6,0,0,0" VerticalAlignment="Center" Width="24"/>
|
||
</DockPanel>
|
||
|
||
<!-- Background Color -->
|
||
<DockPanel Margin="0,0,0,4">
|
||
<TextBlock DockPanel.Dock="Left" Text="Background" Style="{StaticResource PropertyFieldLabel}" Width="70"/>
|
||
<TextBox Text="{Binding SelectedElement.ChatBackgroundColor, UpdateSourceTrigger=PropertyChanged}"
|
||
Style="{StaticResource YtTextBoxCompact}" Width="80"/>
|
||
</DockPanel>
|
||
|
||
<!-- Background Opacity -->
|
||
<DockPanel Margin="0,0,0,4">
|
||
<TextBlock DockPanel.Dock="Left" Text="Bg Opacity" Style="{StaticResource PropertyFieldLabel}" Width="70"/>
|
||
<Slider x:Name="ChatBgOpacitySlider" Width="120" Minimum="0" Maximum="1"
|
||
Value="{Binding SelectedElement.ChatBackgroundOpacity, Mode=TwoWay}"
|
||
VerticalAlignment="Center"/>
|
||
<TextBlock DockPanel.Dock="Right" Text="{Binding SelectedElement.ChatBackgroundOpacity, StringFormat={}{0:0%}}" Foreground="{StaticResource Brush.OnSurface}" FontSize="11" Margin="6,0,0,0" VerticalAlignment="Center" Width="32"/>
|
||
</DockPanel>
|
||
|
||
<!-- Fade After (seconds) -->
|
||
<DockPanel Margin="0,0,0,4">
|
||
<TextBlock DockPanel.Dock="Left" Text="Fade After" Style="{StaticResource PropertyFieldLabel}" Width="70"/>
|
||
<Slider x:Name="ChatFadeSlider" Width="120" Minimum="0" Maximum="60" TickFrequency="5" IsSnapToTickEnabled="True"
|
||
Value="{Binding SelectedElement.ChatFadeSeconds, Mode=TwoWay}"
|
||
VerticalAlignment="Center"/>
|
||
<TextBlock DockPanel.Dock="Right" Text="{Binding SelectedElement.ChatFadeSeconds, StringFormat='{}{0}s'}" Foreground="{StaticResource Brush.OnSurface}" FontSize="11" Margin="6,0,0,0" VerticalAlignment="Center" Width="28"/>
|
||
</DockPanel>
|
||
|
||
<!-- Toggles -->
|
||
<CheckBox Content="Show timestamps" Style="{StaticResource PropertyPillCheck}" Margin="0,4,0,2"
|
||
IsChecked="{Binding SelectedElement.ChatShowTimestamps, Mode=TwoWay}"/>
|
||
<CheckBox Content="Show badges" Style="{StaticResource PropertyPillCheck}" Margin="0,0,0,2"
|
||
IsChecked="{Binding SelectedElement.ChatShowBadges, Mode=TwoWay}"/>
|
||
</StackPanel>
|
||
|
||
<!-- Stream Alerts config -->
|
||
<StackPanel Visibility="{Binding SelectedElement.IsAlertBox, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
|
||
<TextBlock Text="Stream Alerts" Style="{StaticResource PropertyHeader}"/>
|
||
<TextBlock Text="The built-in clip plays on every alert (Super Chat, membership…) unless a custom video is picked." Style="{StaticResource PropertyHint}"/>
|
||
|
||
<TextBlock Text="Video" Style="{StaticResource PropertyHeader}"/>
|
||
<DockPanel Margin="10,0,0,4">
|
||
<!-- Test alert (creator ruling 2026-11-01): plays the clip this box
|
||
would play — custom if one is set, built-in otherwise. -->
|
||
<Button DockPanel.Dock="Right" Content="▶ Test alert"
|
||
Command="{Binding TestAlertCommand}"
|
||
Style="{StaticResource PropertyButton}"
|
||
Padding="8,2" FontSize="11" VerticalAlignment="Center"
|
||
ToolTip="Play the alert here, exactly as a viewer would trigger it"/>
|
||
<TextBlock DockPanel.Dock="Left" Text="Built-in" Style="{StaticResource PropertyFieldLabel}" Width="84"/>
|
||
<ToggleButton Style="{StaticResource PillToggle}"
|
||
IsChecked="{Binding SelectedElement.AlertUseDefaultVideo, Mode=TwoWay}"
|
||
HorizontalAlignment="Left"/>
|
||
</DockPanel>
|
||
|
||
<Grid Margin="10,0,0,4"
|
||
Visibility="{Binding SelectedElement.AlertUseDefaultVideo, Converter={StaticResource InverseBoolToVis}}">
|
||
<Grid.ColumnDefinitions>
|
||
<ColumnDefinition Width="*"/>
|
||
<ColumnDefinition Width="Auto"/>
|
||
</Grid.ColumnDefinitions>
|
||
<TextBox Grid.Column="0" Text="{Binding SelectedElement.AlertVideoPath, Mode=OneWay}"
|
||
Style="{StaticResource YtTextBoxCompact}"
|
||
IsReadOnly="True" Foreground="{StaticResource Brush.OnSurfaceVariant}" VerticalAlignment="Center"/>
|
||
<Button Grid.Column="1" Content="Browse…" Command="{Binding BrowseAlertVideoCommand}"
|
||
CommandParameter="{Binding SelectedElement}" Style="{StaticResource PropertyButton}"
|
||
Margin="4,0,0,0" Padding="8,2"/>
|
||
</Grid>
|
||
|
||
<Button Content="Reset to built-in" Command="{Binding ResetAlertVideoCommand}"
|
||
CommandParameter="{Binding SelectedElement}" Style="{StaticResource PropertyButton}"
|
||
HorizontalAlignment="Left" Padding="8,2" Margin="10,0,0,0"/>
|
||
<TextBlock Text="Custom clips are referenced from disk — never stored in the layout."
|
||
Style="{StaticResource PropertyHint}"/>
|
||
|
||
<!-- Sound byte: plays WITH the clip, on the default device, so it is both
|
||
audible and visible in the desktop meter. -->
|
||
<Border Style="{StaticResource PropertyDivider}"/>
|
||
<TextBlock Text="Sound byte" Style="{StaticResource PropertyHeader}"/>
|
||
<DockPanel Margin="10,0,0,2">
|
||
<Button DockPanel.Dock="Right" Content="Browse…" Command="{Binding BrowseAlertAudioCommand}"
|
||
CommandParameter="{Binding SelectedElement}" Style="{StaticResource PropertyButton}"
|
||
Padding="8,2" Margin="6,0,0,0" FontSize="11"/>
|
||
<Button DockPanel.Dock="Right" Content="✕" Command="{Binding ClearAlertAudioCommand}"
|
||
CommandParameter="{Binding SelectedElement}" Style="{StaticResource PropertyButton}"
|
||
Padding="7,2" FontSize="11" ToolTip="Remove the sound byte"
|
||
Visibility="{Binding SelectedElement.HasAlertAudio, Converter={StaticResource BoolToVis}}"/>
|
||
<TextBox Text="{Binding SelectedElement.AlertAudioFileDisplay, Mode=OneWay}"
|
||
Style="{StaticResource YtTextBoxCompact}" IsReadOnly="True"
|
||
Foreground="{StaticResource Brush.OnSurfaceVariant}" VerticalAlignment="Center"/>
|
||
</DockPanel>
|
||
<CheckBox Content="Loop until the video ends" Style="{StaticResource PropertyPillCheck}" Margin="10,4,0,2"
|
||
IsChecked="{Binding SelectedElement.AlertAudioLoop, Mode=TwoWay}"
|
||
IsEnabled="{Binding SelectedElement.HasAlertAudio}"/>
|
||
<TextBlock Text="The video always plays once, to completion. The sound may loop under it."
|
||
Style="{StaticResource PropertyHint}"/>
|
||
|
||
<Border Style="{StaticResource PropertyDivider}"/>
|
||
<TextBlock Text="Ticker" Style="{StaticResource PropertyHeader}"/>
|
||
<DockPanel Margin="10,0,0,4">
|
||
<TextBlock DockPanel.Dock="Left" Text="Current event" Style="{StaticResource PropertyFieldLabel}" Width="84"/>
|
||
<ToggleButton Style="{StaticResource PillToggle}"
|
||
IsChecked="{Binding SelectedElement.Ticker.IsEnabled, Mode=TwoWay}"
|
||
HorizontalAlignment="Left"/>
|
||
</DockPanel>
|
||
|
||
<!-- Announcement display: scroll (marquee), flash (pulse) or
|
||
solid (still). Inline items, not a bound ItemsSource —
|
||
same shape as the chat Font selector above. -->
|
||
<DockPanel Margin="10,0,0,4">
|
||
<TextBlock DockPanel.Dock="Left" Text="Display" Style="{StaticResource PropertyFieldLabel}" Width="84"/>
|
||
<ComboBox SelectedValue="{Binding SelectedElement.Ticker.DisplayMethod, Mode=TwoWay}"
|
||
SelectedValuePath="Tag"
|
||
Style="{StaticResource YtComboBox}"
|
||
Width="140" VerticalAlignment="Center">
|
||
<ComboBoxItem Content="Scroll" Tag="TickerScroll"/>
|
||
<ComboBoxItem Content="Flash" Tag="Flash"/>
|
||
<ComboBoxItem Content="Solid" Tag="Solid"/>
|
||
</ComboBox>
|
||
</DockPanel>
|
||
|
||
<DockPanel Margin="10,0,0,4">
|
||
<TextBlock DockPanel.Dock="Left" Text="Volume" Style="{StaticResource PropertyFieldLabel}" Width="84"/>
|
||
<Slider Width="120" Minimum="0" Maximum="1"
|
||
Value="{Binding SelectedElement.AlertVideoVolume, Mode=TwoWay}"
|
||
VerticalAlignment="Center"/>
|
||
<TextBlock DockPanel.Dock="Right" Text="{Binding SelectedElement.AlertVideoVolume, StringFormat={}{0:0%}}" Foreground="{StaticResource Brush.OnSurface}" FontSize="11" Margin="6,0,0,0" VerticalAlignment="Center" Width="32"/>
|
||
</DockPanel>
|
||
</StackPanel>
|
||
|
||
<!-- Per-scene ticker layer: same Ticker settings/renderer as the
|
||
ticker embedded in Stream Alerts, but it reads the recent event feed. -->
|
||
<StackPanel Visibility="{Binding SelectedElement.IsTicker, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
|
||
<TextBlock Text="Recent Alert Ticker" Style="{StaticResource PropertyHeader}"/>
|
||
<TextBlock Text="Shows current and queued alert events. This ticker's settings are independent in each scene."
|
||
Style="{StaticResource PropertyHint}"/>
|
||
<CheckBox Content="Show recent alerts" Style="{StaticResource PropertyPillCheck}" Margin="0,2,0,4"
|
||
IsChecked="{Binding SelectedElement.Ticker.IsEnabled, Mode=TwoWay}"/>
|
||
<DockPanel Margin="10,0,0,4">
|
||
<TextBlock DockPanel.Dock="Left" Text="Display" Style="{StaticResource PropertyFieldLabel}" Width="84"/>
|
||
<ComboBox SelectedValue="{Binding SelectedElement.Ticker.DisplayMethod, Mode=TwoWay}"
|
||
SelectedValuePath="Tag" Style="{StaticResource YtComboBox}"
|
||
Width="140" VerticalAlignment="Center">
|
||
<ComboBoxItem Content="Scroll" Tag="TickerScroll"/>
|
||
<ComboBoxItem Content="Flash" Tag="Flash"/>
|
||
<ComboBoxItem Content="Solid" Tag="Solid"/>
|
||
</ComboBox>
|
||
</DockPanel>
|
||
</StackPanel>
|
||
|
||
<!-- Background properties -->
|
||
<StackPanel Margin="0,8,0,0"
|
||
Visibility="{Binding SelectedElement.IsBackground, Converter={StaticResource BoolToVis}}">
|
||
<TextBlock Text="Background" Style="{StaticResource PropertyHeader}"/>
|
||
<StackPanel Margin="4,0,0,0">
|
||
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
|
||
<TextBlock Text="Use default background:" Style="{StaticResource PropertyFieldLabel}"/>
|
||
<ToggleButton Style="{StaticResource PillToggle}"
|
||
IsChecked="{Binding SelectedElement.UseDefaultBackground, Mode=TwoWay}"
|
||
Margin="8,0,0,0"/>
|
||
</StackPanel>
|
||
<Grid Margin="0,0,0,4"
|
||
Visibility="{Binding SelectedElement.UseDefaultBackground, Converter={StaticResource InverseBoolToVis}}">
|
||
<Grid.ColumnDefinitions>
|
||
<ColumnDefinition Width="*"/>
|
||
<ColumnDefinition Width="Auto"/>
|
||
</Grid.ColumnDefinitions>
|
||
<TextBox Grid.Column="0" Text="{Binding SelectedElement.CustomBackgroundPath, Mode=OneWay}"
|
||
Style="{StaticResource YtTextBoxCompact}"
|
||
IsReadOnly="True" Foreground="{StaticResource Brush.OnSurfaceVariant}" VerticalAlignment="Center"/>
|
||
<Button Grid.Column="1" Content="Browse…" Command="{Binding BrowseBackgroundCommand}"
|
||
Style="{StaticResource PropertyButton}" Margin="4,0,0,0" Padding="8,2"/>
|
||
</Grid>
|
||
<TextBlock Text="{Binding BackgroundSizeMismatchText}"
|
||
Foreground="{StaticResource Brush.Error}" FontSize="11" TextWrapping="Wrap"
|
||
Margin="0,4,0,0"
|
||
Visibility="{Binding BackgroundSizeMismatch, Converter={StaticResource BoolToVis}}"/>
|
||
</StackPanel>
|
||
</StackPanel>
|
||
</StackPanel>
|
||
</ScrollViewer>
|
||
</Grid>
|
||
|
||
<!-- Live mode: YouTube Live Chat (entire left panel replaced) -->
|
||
<Grid Grid.Row="0" Visibility="{Binding IsEditMode, Converter={StaticResource InverseBoolToVis}}">
|
||
<Grid.RowDefinitions>
|
||
<RowDefinition Height="Auto"/>
|
||
<RowDefinition Height="*"/>
|
||
</Grid.RowDefinitions>
|
||
|
||
<StackPanel Grid.Row="0" Orientation="Horizontal" Margin="0,0,0,8">
|
||
<ToggleButton x:Name="LiveChatTabButton" Style="{StaticResource SegmentToggle}"
|
||
IsChecked="{Binding IsLiveChatTabSelected, Mode=TwoWay}" Padding="10,5">
|
||
<TextBlock Text="Live Chat" Style="{StaticResource SegmentLabel}"/>
|
||
</ToggleButton>
|
||
<ToggleButton x:Name="LogsTabButton" Style="{StaticResource SegmentToggle}"
|
||
IsChecked="{Binding IsLogsTabSelected, Mode=TwoWay}" Padding="10,5" Margin="4,0,0,0">
|
||
<TextBlock Text="Logs" Style="{StaticResource SegmentLabel}"/>
|
||
</ToggleButton>
|
||
</StackPanel>
|
||
|
||
<Grid Grid.Row="1">
|
||
<Grid Visibility="{Binding IsLiveChatTabSelected, Converter={StaticResource BoolToVis}}">
|
||
<!-- Inactive overlay (shown when NOT live) -->
|
||
<StackPanel VerticalAlignment="Center" HorizontalAlignment="Center"
|
||
Visibility="{Binding ShowChatInactiveMessage, Converter={StaticResource BoolToVis}}">
|
||
<TextBlock Text="Chat is inactive" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="13"
|
||
HorizontalAlignment="Center" Margin="0,0,0,4"/>
|
||
<TextBlock Text="Chat becomes active once LlamaCasty is connected and streaming on YouTube."
|
||
Foreground="{StaticResource Brush.OnSurfaceDisabled}" FontSize="11" TextWrapping="Wrap"
|
||
HorizontalAlignment="Center" TextAlignment="Center" MaxWidth="240"/>
|
||
</StackPanel>
|
||
|
||
<!-- Pinned message -->
|
||
<Border VerticalAlignment="Top" Margin="4,4,4,0" Padding="6,4" CornerRadius="4"
|
||
Background="{StaticResource Brush.SurfaceContainerHigh}" BorderBrush="{StaticResource Brush.Tertiary}" BorderThickness="1"
|
||
Visibility="{Binding PinnedChatMessage, Converter={StaticResource NotNullToVis}}">
|
||
<StackPanel>
|
||
<TextBlock Text="{Binding PinnedChatMessage.AuthorName}" Foreground="{StaticResource Brush.Tertiary}" FontWeight="SemiBold" FontSize="11"/>
|
||
<TextBlock Text="{Binding PinnedChatMessage.Message}" Foreground="{StaticResource Brush.OnSurface}" FontSize="11" TextWrapping="Wrap"/>
|
||
</StackPanel>
|
||
</Border>
|
||
|
||
<!-- Message feed (shown when live) -->
|
||
<ListBox Background="Transparent" BorderThickness="0" Margin="0,40,0,0"
|
||
ItemsSource="{Binding ChatMessages}"
|
||
ItemContainerStyle="{StaticResource YtListBoxItem}"
|
||
Visibility="{Binding ShowChatInactiveMessage, Converter={StaticResource InverseBoolToVis}}"
|
||
x:Name="ChatListBox">
|
||
<ListBox.ItemTemplate><DataTemplate>
|
||
<Border x:Name="MessageBorder" Padding="6,4" CornerRadius="4">
|
||
<Border.Style>
|
||
<Style TargetType="Border">
|
||
<Setter Property="Background" Value="Transparent"/>
|
||
<Style.Triggers>
|
||
<DataTrigger Binding="{Binding IsSuperChat}" Value="True">
|
||
<Setter Property="Background" Value="{StaticResource Brush.SurfaceContainerHigh}"/>
|
||
<Setter Property="BorderBrush" Value="{StaticResource Brush.Secondary}"/>
|
||
<Setter Property="BorderThickness" Value="1,0,1,0"/>
|
||
</DataTrigger>
|
||
</Style.Triggers>
|
||
</Style>
|
||
</Border.Style>
|
||
<StackPanel>
|
||
<StackPanel Orientation="Horizontal" Margin="0,0,0,2">
|
||
<TextBlock Text="⭐" FontSize="10" Margin="0,0,4,0"
|
||
VerticalAlignment="Center"
|
||
Visibility="{Binding IsMember, Converter={StaticResource BoolToVis}}"/>
|
||
<TextBlock Text="{Binding AuthorName}" Foreground="{StaticResource Brush.Tertiary}"
|
||
FontWeight="Bold" FontSize="12" VerticalAlignment="Center"/>
|
||
<TextBlock Text="{Binding SuperChatAmount, StringFormat=${0:F2}}"
|
||
Foreground="{StaticResource Brush.Secondary}" FontWeight="Bold" FontSize="12"
|
||
Margin="8,0,0,0" VerticalAlignment="Center"
|
||
Visibility="{Binding IsSuperChat, Converter={StaticResource BoolToVis}}"/>
|
||
<TextBlock Text="{Binding SuperChatCurrency}" Foreground="{StaticResource Brush.Secondary}"
|
||
FontSize="11" Margin="2,0,0,0" VerticalAlignment="Center"
|
||
Visibility="{Binding IsSuperChat, Converter={StaticResource BoolToVis}}"/>
|
||
</StackPanel>
|
||
<TextBlock Text="{Binding Message}" Foreground="{StaticResource Brush.OnSurface}"
|
||
TextWrapping="Wrap" Margin="0,2,0,0"/>
|
||
</StackPanel>
|
||
</Border>
|
||
</DataTemplate></ListBox.ItemTemplate>
|
||
</ListBox>
|
||
</Grid>
|
||
|
||
<Grid Visibility="{Binding IsLogsTabSelected, Converter={StaticResource BoolToVis}}">
|
||
<Grid.RowDefinitions>
|
||
<RowDefinition Height="Auto"/>
|
||
<RowDefinition Height="*"/>
|
||
</Grid.RowDefinitions>
|
||
<ScrollViewer Grid.Row="0" MaxHeight="160" VerticalScrollBarVisibility="Auto"
|
||
Visibility="{Binding HasPinnedLogEntries, Converter={StaticResource BoolToVis}}">
|
||
<StackPanel>
|
||
<TextBlock Text="Needs attention" Style="{StaticResource PropertyHeader}" Margin="0,0,0,4"/>
|
||
<ItemsControl x:Name="PinnedLogPanel" ItemsSource="{Binding PinnedLogEntries}">
|
||
<ItemsControl.ItemTemplate>
|
||
<DataTemplate>
|
||
<Border Padding="6,4" Margin="0,0,0,4" CornerRadius="4"
|
||
Background="{StaticResource Brush.SurfaceContainerHigh}">
|
||
<DockPanel LastChildFill="True">
|
||
<Button DockPanel.Dock="Right" Content="Acknowledge"
|
||
Style="{StaticResource PropertyButton}" Margin="8,0,0,0"
|
||
VerticalAlignment="Top"
|
||
Command="{Binding DataContext.AcknowledgeLogEntryCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||
CommandParameter="{Binding}"/>
|
||
<StackPanel>
|
||
<TextBlock Text="{Binding ErrorClassLabel}"
|
||
Style="{StaticResource AppLogEntryText}" FontWeight="Bold"/>
|
||
<TextBlock Text="{Binding Text}" Style="{StaticResource AppLogEntryText}"/>
|
||
</StackPanel>
|
||
</DockPanel>
|
||
</Border>
|
||
</DataTemplate>
|
||
</ItemsControl.ItemTemplate>
|
||
</ItemsControl>
|
||
</StackPanel>
|
||
</ScrollViewer>
|
||
<ListBox x:Name="LogListBox" Grid.Row="1" Background="Transparent" BorderThickness="0"
|
||
ItemsSource="{Binding CurrentLaunchLogEntries}"
|
||
ItemContainerStyle="{StaticResource YtListBoxItem}"
|
||
ScrollViewer.CanContentScroll="True"
|
||
VirtualizingPanel.IsVirtualizing="True"
|
||
VirtualizingPanel.VirtualizationMode="Recycling">
|
||
<ListBox.ItemTemplate>
|
||
<DataTemplate>
|
||
<DockPanel Margin="6,4">
|
||
<TextBlock DockPanel.Dock="Left" Text="{Binding TypeLabel}"
|
||
Style="{StaticResource AppLogEntryText}" FontWeight="Bold"
|
||
Margin="0,0,8,0"/>
|
||
<TextBlock Text="{Binding Text}" Style="{StaticResource AppLogEntryText}"/>
|
||
</DockPanel>
|
||
</DataTemplate>
|
||
</ListBox.ItemTemplate>
|
||
</ListBox>
|
||
</Grid>
|
||
</Grid>
|
||
</Grid>
|
||
</Grid>
|
||
</Border>
|
||
</UserControl>
|