feat: Elements panel, Countdown/Web sources, webcam border config (TASK 14 Branch 2)

- New SourceType.Countdown (Starting/BRB only, timer 1-60 min) and SourceType.WebSource (all scenes, URI)
- Elements panel beneath Sources: per-element config with live preview, ✕ revert / ✓ applied indicator
- Webcam border: hex color input + swatch picker, thickness 0-10 slider
- Countdown: timer minutes slider (1-60, default 15), Web: URI text input
- SourceTypeToVisibilityConverter for conditional panel visibility
- Snapshot/revert mechanism in code-behind for live-preview cancel
This commit is contained in:
2026-08-17 07:53:03 -07:00
parent a3dd0791db
commit 16f905c8d7
7 changed files with 230 additions and 6 deletions
+107
View File
@@ -37,6 +37,11 @@
xmlns:Helpers="clr-namespace:ytLive.Helpers"/>
<Helpers:EnumToBoolConverter x:Key="EnumToBool"
xmlns:Helpers="clr-namespace:ytLive.Helpers"/>
<Helpers:SourceTypeToVisibilityConverter x:Key="SourceTypeToCountdownVis"
xmlns:Helpers="clr-namespace:ytLive.Helpers"
xmlns:models2="clr-namespace:ytLive.Models"/>
<Helpers:SourceTypeToVisibilityConverter x:Key="SourceTypeToWebVis"
xmlns:Helpers="clr-namespace:ytLive.Helpers"/>
<!-- Red dot shown in the taskbar icon while live -->
<DrawingImage x:Key="LiveOverlay">
@@ -162,6 +167,9 @@
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<!-- Scenes: the app has five canonical scenes. Work with less,
@@ -236,6 +244,11 @@
<MenuItem Header="Background" Command="{Binding AddSourceCommand}" CommandParameter="{x:Static models:SourceType.Background}"/>
<MenuItem Header="Image" Command="{Binding AddImageCommand}"/>
<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 scenes only"/>
<MenuItem Header="Web" Command="{Binding AddSourceCommand}" CommandParameter="{x:Static models:SourceType.WebSource}"
ToolTip="Embedded web page — set URI in Elements panel"/>
</ContextMenu>
</Button.ContextMenu>
<Path Data="M12,4 L12,20 M4,12 L20,12" Stroke="#e94560" StrokeThickness="2"
@@ -342,6 +355,100 @@
<TextBlock Grid.Row="4" Text="No sources yet" Foreground="#555" FontSize="12"
HorizontalAlignment="Center" VerticalAlignment="Center"
Visibility="{Binding ShowSourcesEmptyHint, Converter={StaticResource BoolToVis}}"/>
<!-- ELEMENTS panel: per-element config beneath Sources -->
<Rectangle Grid.Row="5" Height="1" Fill="#2c3a5c" Margin="0,10,0,2"/>
<StackPanel Grid.Row="6" Orientation="Horizontal" Margin="0,8,0,0">
<TextBlock Text="ELEMENTS" Style="{StaticResource SectionHeader}" VerticalAlignment="Center"/>
</StackPanel>
<ScrollViewer Grid.Row="7" VerticalScrollBarVisibility="Auto" Background="Transparent">
<StackPanel x:Name="ElementsPanel" Margin="0,4,0,0">
<!-- Webcam border config -->
<StackPanel Visibility="{Binding SelectedElement.IsWebcam, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
<TextBlock Text="Border Color" Foreground="#a0a0b0" FontSize="11" Margin="0,0,0,2"/>
<DockPanel Margin="0,0,0,6">
<Button DockPanel.Dock="Right" Content="..." Width="24" Height="24"
Click="WebcamBorderColorPicker_Click" Margin="4,0,0,0"
ToolTip="Pick color"/>
<TextBox Text="{Binding SelectedElement.BorderColor, UpdateSourceTrigger=PropertyChanged}"
Background="#0f3460" Foreground="#e0d0e0" BorderThickness="1"
BorderBrush="#2c3a5c" Padding="4,2" FontSize="12"
MaxLength="7" ToolTip="#RRGGBB"/>
</DockPanel>
<TextBlock Text="Border Thickness" Foreground="#a0a0b0" FontSize="11" Margin="0,0,0,2"/>
<DockPanel Margin="0,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}" Foreground="#a0a0b0" FontSize="11"
VerticalAlignment="Center" Margin="8,0,0,0" Width="20"/>
</DockPanel>
<DockPanel Margin="0,4,0,0">
<Button DockPanel.Dock="Right" Content="✓" Width="22" Height="22"
Background="#22c55e" Foreground="White" FontSize="11" FontWeight="Bold"
ToolTip="Applied" IsEnabled="False" Margin="4,0,0,0"/>
<Button DockPanel.Dock="Right" Content="✕" Width="22" Height="22"
Background="#ef4444" Foreground="White" FontSize="11" FontWeight="Bold"
ToolTip="Revert" Click="WebcamBorderRevert_Click" Margin="4,0,0,0"/>
</DockPanel>
</StackPanel>
<!-- Countdown config -->
<StackPanel Visibility="{Binding SelectedElement.Type, Converter={StaticResource SourceTypeToCountdownVis}, ConverterParameter=Countdown}" Margin="0,0,0,8">
<TextBlock Text="Timer (minutes)" Foreground="#a0a0b0" FontSize="11" Margin="0,0,0,2"/>
<DockPanel Margin="0,0,0,6">
<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}" Foreground="#a0a0b0" FontSize="11"
VerticalAlignment="Center" Margin="8,0,0,0" Width="20"/>
</DockPanel>
<DockPanel Margin="0,4,0,0">
<Button DockPanel.Dock="Right" Content="✓" Width="22" Height="22"
Background="#22c55e" Foreground="White" FontSize="11" FontWeight="Bold"
ToolTip="Applied" IsEnabled="False" Margin="4,0,0,0"/>
<Button DockPanel.Dock="Right" Content="✕" Width="22" Height="22"
Background="#ef4444" Foreground="White" FontSize="11" FontWeight="Bold"
ToolTip="Revert" Click="CountdownRevert_Click" Margin="4,0,0,0"/>
</DockPanel>
</StackPanel>
<!-- Web source config -->
<StackPanel Visibility="{Binding SelectedElement.Type, Converter={StaticResource SourceTypeToWebVis}, ConverterParameter=WebSource}" Margin="0,0,0,8">
<TextBlock Text="URI" Foreground="#a0a0b0" FontSize="11" Margin="0,0,0,2"/>
<TextBox Text="{Binding SelectedElement.WebUri, UpdateSourceTrigger=PropertyChanged}"
Background="#0f3460" Foreground="#e0d0e0" BorderThickness="1"
BorderBrush="#2c3a5c" Padding="4,2" FontSize="12"
ToolTip="https://example.com"/>
<DockPanel Margin="0,4,0,0">
<Button DockPanel.Dock="Right" Content="✓" Width="22" Height="22"
Background="#22c55e" Foreground="White" FontSize="11" FontWeight="Bold"
ToolTip="Applied" IsEnabled="False" Margin="4,0,0,0"/>
<Button DockPanel.Dock="Right" Content="✕" Width="22" Height="22"
Background="#ef4444" Foreground="White" FontSize="11" FontWeight="Bold"
ToolTip="Revert" Click="WebRevert_Click" Margin="4,0,0,0"/>
</DockPanel>
</StackPanel>
<!-- Placeholder hint when no config available -->
<TextBlock Text="No configurable elements" Foreground="#555" FontSize="11"
HorizontalAlignment="Center" Margin="0,8,0,0">
<TextBlock.Style>
<Style TargetType="TextBlock">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding SelectedElement}" Value="{x:Null}"/>
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</TextBlock.Style>
</TextBlock>
</StackPanel>
</ScrollViewer>
</Grid>
</Border>