A dead encoder used to run the whole stop path, so a one-second blip ended the broadcast. Now a dropped push is relaunched against the same ingest while the broadcast is still alive. - `Services/StreamRetryPolicy.cs` — immediate, +1 s, +2 s, +3 s. Minimum waits on purpose (creator ruling): YouTube's grace is short, so every second not pushing is grace spent. - `Services/StreamRecovery.cs` — the loop, with the audio pipe recreated before every attempt (its server is single-instance and unusable after the old client dies, so a naive relaunch fails on a missing input). After a push comes up it asks the API whether the broadcast survived: "complete" ends recovery as `BroadcastComplete` instead of showing a live sign over a dead stream; an API hiccup counts as recovered rather than ending a healthy session. - `FramePump.EncoderFailed` — a dead push is recoverable; a compositor fault still goes through `Failed` and ends the session, because retrying it would loop. - `MainViewModel.Recovery.cs` — wires the steps, reports each episode into the left panel alert stack (one row per episode, updated in place, log range painted), and cancels on Stop. - Alert stack polish: a rule and a gap now separate the stack from the scene layers (they read as one list before), and the licence indicator uses the creator's wording — `STEAM ID NOT PRESENT` / `Steam ID Limit: 30fps` / `Click for More Info`, becoming `STEAM ID PRESENT` / `60fps` with a close control once owned. Tests: `StreamRecoveryTests` (schedule, recover on attempt 2 with the pipe restarted each time, complete-broadcast honesty, unknown liveness, give-up, cancel, audio-restart fault), `FramePumpTests` now pin that process death raises `EncoderFailed` and not `Failed`. FAST gate: 538 passed (one documented pre-existing audio failure), 0 warnings.
1291 lines
115 KiB
XML
1291 lines
115 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:LogInRangeConverter x:Key="LogInRange"/>
|
||
<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="Auto"/>
|
||
<RowDefinition Height="*"/>
|
||
</Grid.RowDefinitions>
|
||
|
||
<!-- ALERT STACK (creator design 2026-11-05): standing state on top, transient
|
||
events beneath, newest first, pushing the panel's content down. Only red
|
||
rows live here. Row 1 is the Steam licence indicator and is never displaced;
|
||
it shows a pin while higher tiers are unavailable, and a close control once
|
||
ownership is verified (that close is permanent). Clicking a row runs its
|
||
action: the licence indicator opens About, an event opens the Logs tab with
|
||
that event's lines painted. -->
|
||
<!-- Alerts and the panel content are different kinds of thing (state vs. the
|
||
scene you are editing), so they are separated by space AND a rule: without it
|
||
the stack read as the first rows of the layer list (creator, 2026-11-05). -->
|
||
<StackPanel Grid.Row="0" Visibility="{Binding HasAttentionAlerts, Converter={StaticResource BoolToVis}}">
|
||
<ItemsControl ItemsSource="{Binding AttentionAlerts}" Margin="0,0,0,2">
|
||
<ItemsControl.ItemTemplate>
|
||
<DataTemplate>
|
||
<Border Margin="0,0,0,4" Padding="8,6" CornerRadius="4" Cursor="Hand"
|
||
Background="{StaticResource Brush.ErrorContainer}"
|
||
BorderBrush="{StaticResource Brush.Error}" BorderThickness="1">
|
||
<Border.InputBindings>
|
||
<MouseBinding MouseAction="LeftClick"
|
||
Command="{Binding DataContext.ActivateAlertCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||
CommandParameter="{Binding}"/>
|
||
</Border.InputBindings>
|
||
<DockPanel LastChildFill="True">
|
||
<Button DockPanel.Dock="Right" VerticalAlignment="Top" Margin="6,0,0,0"
|
||
Style="{StaticResource IconButton}" Width="20" Height="20"
|
||
ToolTip="Dismiss"
|
||
Command="{Binding DataContext.DismissAlertCommand, RelativeSource={RelativeSource AncestorType=UserControl}}"
|
||
CommandParameter="{Binding}"
|
||
Visibility="{Binding CanDismiss, Converter={StaticResource BoolToVis}}">
|
||
<Path Data="M4,4 L16,16 M16,4 L4,16" Stroke="{StaticResource Brush.Error}"
|
||
StrokeThickness="2" Width="12" Height="12" Stretch="Uniform"/>
|
||
</Button>
|
||
<!-- Pinned rows show the pin INSTEAD of a close control: the icon is
|
||
the state ("standing indicator"), not a disabled button. -->
|
||
<Path DockPanel.Dock="Right" Data="M12,2 L12,9 M8,9 H16 L14,15 H10 Z M10,15 L10,21 M14,15 L14,21"
|
||
Stroke="{StaticResource Brush.Error}" StrokeThickness="1.6"
|
||
Width="14" Height="14" Stretch="Uniform" VerticalAlignment="Top"
|
||
Margin="6,2,2,0" ToolTip="Standing indicator"
|
||
Visibility="{Binding ShowsPin, Converter={StaticResource BoolToVis}}"/>
|
||
<StackPanel>
|
||
<TextBlock Text="{Binding Label}" Style="{StaticResource AppLogEntryText}"
|
||
FontWeight="Bold" Foreground="{StaticResource Brush.Error}"/>
|
||
<TextBlock Text="{Binding Text}" Style="{StaticResource AppLogEntryText}"
|
||
TextWrapping="Wrap" Margin="0,2,0,0"/>
|
||
</StackPanel>
|
||
</DockPanel>
|
||
</Border>
|
||
</DataTemplate>
|
||
</ItemsControl.ItemTemplate>
|
||
</ItemsControl>
|
||
<Border Style="{StaticResource PropertyDivider}" Margin="0,4,0,10"/>
|
||
</StackPanel>
|
||
|
||
<!-- Edit mode (offline): layers + properties -->
|
||
<Grid Grid.Row="1" 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 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="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">
|
||
<DockPanel Margin="0,0,0,0">
|
||
<Button DockPanel.Dock="Right" Content="▶ Play"
|
||
Command="{Binding ReplayMediaCommand}"
|
||
CommandParameter="{Binding SelectedElement}"
|
||
Style="{StaticResource PropertyButton}" Padding="8,2" FontSize="11"
|
||
ToolTip="Play this clip in the preview from the beginning"/>
|
||
<TextBlock Text="Video File" Style="{StaticResource PropertyHeader}" VerticalAlignment="Center"/>
|
||
</DockPanel>
|
||
<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"/>
|
||
<TextBlock Text="Clip finished — holding the last frame."
|
||
Style="{StaticResource PropertyHint}"
|
||
Visibility="{Binding SelectedElement.IsMediaFinished, Converter={StaticResource BoolToVis}}"/>
|
||
<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}"/>
|
||
|
||
</StackPanel>
|
||
|
||
<!-- Green/Blue Screen (TASK 64): a video layer OR the alert box's
|
||
own clip. The app offers, the creator accepts — never silent. -->
|
||
<StackPanel Visibility="{Binding SelectedElement.ShowsChromaKey, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
|
||
<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"/>
|
||
<TextBlock Text="Removed automatically — green screen detected."
|
||
Style="{StaticResource PropertyHint}"
|
||
Visibility="{Binding SelectedElement.HasAutoChromaKey, Converter={StaticResource BoolToVis}}"/>
|
||
<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>
|
||
<!-- Geometry is ALWAYS the last section (creator ruling 2026-11-05):
|
||
opacity sits at the top of the properties list and X/Y/W/H at the
|
||
bottom, for every draggable element, so the eye can find them in
|
||
the same place whatever layer is selected. For the webcam under
|
||
the vertical tier this edits the LIVE placement (TASK 57). -->
|
||
<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>
|
||
|
||
</StackPanel>
|
||
</StackPanel>
|
||
</ScrollViewer>
|
||
</Grid>
|
||
|
||
<!-- Live mode: YouTube Live Chat (entire left panel replaced) -->
|
||
<Grid Grid.Row="1" 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>
|
||
<!-- An alert row is an entry point to its detail: clicking it opens
|
||
this tab and paints the sequence range that event produced. -->
|
||
<Border Padding="2,1" CornerRadius="3">
|
||
<Border.Style>
|
||
<Style TargetType="Border">
|
||
<Setter Property="Background" Value="Transparent"/>
|
||
<Style.Triggers>
|
||
<DataTrigger Value="True">
|
||
<DataTrigger.Binding>
|
||
<MultiBinding Converter="{StaticResource LogInRange}">
|
||
<Binding Path="Sequence"/>
|
||
<Binding Path="DataContext.HighlightedLogFrom"
|
||
RelativeSource="{RelativeSource AncestorType=UserControl}"/>
|
||
<Binding Path="DataContext.HighlightedLogTo"
|
||
RelativeSource="{RelativeSource AncestorType=UserControl}"/>
|
||
</MultiBinding>
|
||
</DataTrigger.Binding>
|
||
<Setter Property="Background" Value="{StaticResource Brush.ErrorContainer}"/>
|
||
</DataTrigger>
|
||
</Style.Triggers>
|
||
</Style>
|
||
</Border.Style>
|
||
<DockPanel Margin="4,3">
|
||
<TextBlock DockPanel.Dock="Left" Text="{Binding TypeLabel}"
|
||
Style="{StaticResource AppLogEntryText}" FontWeight="Bold"
|
||
Margin="0,0,8,0"/>
|
||
<TextBlock Text="{Binding Text}" Style="{StaticResource AppLogEntryText}"
|
||
TextWrapping="Wrap"/>
|
||
</DockPanel>
|
||
</Border>
|
||
</DataTemplate>
|
||
</ListBox.ItemTemplate>
|
||
</ListBox>
|
||
</Grid>
|
||
</Grid>
|
||
</Grid>
|
||
</Grid>
|
||
</Border>
|
||
</UserControl>
|