The vertical tier is a centred band crop of the SAME 1920x1080 master (band x
656.25, w 607.5, h 1080), so the 16:9 camera placement (x~1408) falls outside the
cropped window and the creator is simply not in the vertical stream. Each scene
now keeps its own vertical position, size and shape; switching back to 16:9
restores the desktop layout exactly (the creator's locked table, 2026-10-17).
Geometry only - NO render-path change. The preview and the compositor both read
the element's own X/Y/W/H + ClipShape, so the tier switch swaps the LIVE geometry:
- Models/VerticalCameraLayout.cs (new): the pure math - the band, its two 607x540
areas, the six spots (top/bottom x left/centre/right), the 32px inset and the
270x152 default tile (480x270 in the 1080x1920 output) in the bottom-right spot,
the corner the 16:9 default webcam uses.
- Models/WebcamSceneConfig.cs: the vertical placement lives in seven additive
columns (VerticalX/Y/Width/Height/ClipShape/RectWidth/RectHeight; NULL = "this
scene has no vertical placement yet"). The 16:9 layout stays in the old columns
and is parked in the transient Landscape* fields while the vertical tier is
live; Begin/EndVerticalLayout + CommitLiveLayoutToVertical do the swap. The
X/Y/W/H overrides clamp every write into the band - outside it the camera is
invisible in the vertical output.
- Models/SceneElement.cs: X/Y/Width/Height became virtual so the derived element
constrains every writer (drag, resize handle, typed fields) in ONE choke point
instead of three writers each re-implementing the rule.
- ViewModels/MainViewModel.Webcam.cs + .Streaming.cs: IsVerticalTier/VerticalBand,
ResolveVerticalCameraLayout(s) (the tier swap), MoveWebcamInVerticalTier /
SnapWebcamInVerticalTier (the six-spot snap, by the tile's centre),
EnsureVerticalCameraLayout; ApplyStreamQuality is the tier-change hook. The
Live scene gets its small tile automatically on the first vertical switch (the
vertical stream would otherwise have no camera at all); the other scenes keep
their 16:9 placement until the creator drags them into the band.
- Controls/PreviewPane.xaml.cs: the drag/resize route through the VM snap under
the vertical tier instead of free-floating over the master.
Controls/LeftPanel.xaml: the webcam properties gained a Geometry section
(X/Y/W/H) for the manual entry the creator asked for, bound to the LIVE
placement, gated on the VM's IsWebcamSelected (NOT SelectedElement.IsWebcam -
the TASK 51 test finds that panel by binding text and would match twice).
- The store: seven guarded ADD COLUMNs, the INSERT/SELECT extended, and the
X/Y/W/H/ClipShape columns stay the 16:9 layout - while the vertical tier is
live the save writes the parked values there and the live ones into Vertical*.
Test: ytLive.Tests/VerticalWebcamLayoutTests.cs - the ONE Good Dog test. The
vertical tier brings the Live camera INTO the band as a small tile; dragging it to
the top-right lands it top-right inside the band; back to 16:9 the desktop layout
is restored exactly (1408/778/480x270); both placements survive a save + reload.
Full suite 429/429, 0 warnings. LayerReorderPersistenceTests.RealMouseDrag fails
ALONE on a clean tree in this environment - pre-existing flake, proven by stashing
this work, not a regression.
Derivative work: OBS has no built-in per-aspect-ratio layout storage - the
maintainers' answer is "create a second scene and add the scene you want to send
as source" (obsproject.com/forum/threads/161307) - and Streamyard's dual-aspect
guide says the same ("no built-in way to store separate layouts per aspect ratio",
so you rely on manual workarounds and plugins):
https://streamyard.com/blog/dual-aspect-ratio-streaming-tool
Our per-scene, per-tier stored placement, resolved on the tier change, is the
step past both.
Memory in the same commit: ai.md (new section), TASKS.md + TASKS/task-57-*.md
(Unit 1 shipped, Unit 2 = the Chat scene still open), HANDOFF.md, MyMistakes.md
(the per-tier geometry-swap recipe, the binding-text panel trap, the store's
webcam-identity rule) and the Models/Services/ViewModels/Controls index rows.
684 lines
57 KiB
XML
684 lines
57 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">
|
|
<Border Background="#16213e" 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">
|
|
<TextBlock Text="LAYERS" Style="{StaticResource SectionHeader}" VerticalAlignment="Center"/>
|
|
<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="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"/>
|
|
</ContextMenu>
|
|
</Button.ContextMenu>
|
|
<Path Data="M12,4 L12,20 M4,12 L20,12" Stroke="#e94560" StrokeThickness="2"
|
|
Width="14" Height="14" Stretch="Uniform"/>
|
|
</Button>
|
|
</StackPanel>
|
|
|
|
<Helpers:FocusPreservingListBox x:Name="SourceList" Grid.Row="1" Background="Transparent" BorderThickness="0"
|
|
ItemsSource="{Binding StagedScene.Elements}"
|
|
SelectedItem="{Binding SelectedElement, Mode=TwoWay}"
|
|
PreviewMouseLeftButtonDown="List_PreviewMouseLeftButtonDown"
|
|
PreviewMouseMove="List_PreviewMouseMove"
|
|
PreviewMouseLeftButtonUp="List_PreviewMouseLeftButtonUp"
|
|
Foreground="#e0e0e0" 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="0,2"
|
|
Tag="{Binding DataContext.CanChangeBackground, RelativeSource={RelativeSource AncestorType=Window}}">
|
|
<Grid.ColumnDefinitions>
|
|
<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>
|
|
|
|
<TextBlock Grid.Column="0" 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="0" Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"
|
|
Background="#0f3460" Foreground="#e0e0e0" BorderThickness="0"
|
|
Padding="4,2" VerticalContentAlignment="Center"
|
|
IsVisibleChanged="SourceNameBox_IsVisibleChanged"
|
|
KeyDown="SourceNameBox_KeyDown" LostFocus="SourceNameBox_LostFocus">
|
|
<TextBox.Style>
|
|
<Style TargetType="TextBox">
|
|
<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="1" 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="#d0d0d0" Width="13" Height="13" Stretch="Uniform"/>
|
|
</Button>
|
|
|
|
<Button Grid.Column="2" Style="{StaticResource EyeButton}"
|
|
Command="{Binding DataContext.ToggleElementVisibilityCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
|
|
CommandParameter="{Binding}">
|
|
<Path Style="{StaticResource EyeIconStyle}" Fill="#d0d0d0"
|
|
Width="13" Height="13" Stretch="Uniform"/>
|
|
</Button>
|
|
|
|
<Button Grid.Column="3" 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="#d0d0d0" Width="13" Height="13" Stretch="Uniform"/>
|
|
</Button>
|
|
</Grid>
|
|
</DataTemplate>
|
|
</ListBox.ItemTemplate>
|
|
</Helpers:FocusPreservingListBox>
|
|
|
|
<TextBlock Grid.Row="2" Text="No layers yet" Foreground="#555" 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="#3a1216" BorderBrush="#e94560" BorderThickness="1"
|
|
Visibility="{Binding WebcamLockAlert, Converter={StaticResource NotNullToVis}}">
|
|
<StackPanel>
|
|
<TextBlock Text="{Binding WebcamLockAlert}" Foreground="#ffb4b4" FontSize="11"
|
|
TextWrapping="Wrap"/>
|
|
<Button Content="Retry" Command="{Binding RetryWebcamLockCommand}"
|
|
Style="{StaticResource YtButtonSecondary}" 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="#2c3a5c" Margin="0,10,0,2"
|
|
Visibility="{Binding SelectedElement, Converter={StaticResource NotNullToVis}}"/>
|
|
<StackPanel Grid.Row="5" Orientation="Horizontal" Margin="0,8,0,0"
|
|
Visibility="{Binding SelectedElement, Converter={StaticResource NotNullToVis}}">
|
|
<TextBlock Text="PROPERTIES" Style="{StaticResource SectionHeader}" VerticalAlignment="Center"/>
|
|
</StackPanel>
|
|
<ScrollViewer x:Name="ElementsScrollViewer" Grid.Row="6" VerticalScrollBarVisibility="Auto" Background="Transparent"
|
|
Visibility="{Binding SelectedElement, Converter={StaticResource NotNullToVis}}">
|
|
<StackPanel x:Name="ElementsPanel" Margin="0,4,0,0">
|
|
<!-- 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}}">
|
|
<TextBlock Text="Opacity" Foreground="#a0a0b0" FontSize="11" Margin="0,0,0,2"/>
|
|
<DockPanel Margin="0,0,0,6">
|
|
<TextBlock x:Name="OpacityValueText" Text="100%" Foreground="#a0a0b0" FontSize="11"
|
|
DockPanel.Dock="Right" VerticalAlignment="Center" 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 Orientation="Horizontal" Margin="0,0,0,4"
|
|
Visibility="{Binding IsWebcamSelected, Converter={StaticResource BoolToVis}}">
|
|
<Button Content="{Binding SelectedElement.MirrorButtonText}"
|
|
Style="{StaticResource YtButtonSecondary}" Padding="10,4"
|
|
Click="MirrorButton_Click"/>
|
|
<Button Content="{Binding SelectedElement.ShapeButtonText}"
|
|
Style="{StaticResource YtButtonSecondary}" Padding="10,4"
|
|
Margin="8,0,0,0" Click="ShapeButton_Click"/>
|
|
</StackPanel>
|
|
<TextBlock Text="Drag to move · drag the corner dot to resize"
|
|
Foreground="#a0a0b0" FontSize="10" Margin="0,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 IsWebcamSelected, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
|
|
<TextBlock Text="Geometry" Foreground="#a0a0b0" FontSize="11" FontWeight="SemiBold" Margin="0,0,0,4"/>
|
|
<DockPanel Margin="0,0,0,4">
|
|
<TextBlock DockPanel.Dock="Left" Text="X" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" Width="16"/>
|
|
<TextBox Text="{Binding SelectedElement.X, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
|
Background="#0f3460" Foreground="#e0d0e0" BorderThickness="1"
|
|
BorderBrush="#2c3a5c" Padding="4,2" FontSize="12"/>
|
|
<TextBlock DockPanel.Dock="Left" Text=" Y" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" Width="20" Margin="8,0,0,0"/>
|
|
<TextBox Text="{Binding SelectedElement.Y, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
|
Background="#0f3460" Foreground="#e0d0e0" BorderThickness="1"
|
|
BorderBrush="#2c3a5c" Padding="4,2" FontSize="12"/>
|
|
</DockPanel>
|
|
<DockPanel Margin="0,0,0,4">
|
|
<TextBlock DockPanel.Dock="Left" Text="W" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" Width="16"/>
|
|
<TextBox Text="{Binding SelectedElement.Width, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
|
Background="#0f3460" Foreground="#e0d0e0" BorderThickness="1"
|
|
BorderBrush="#2c3a5c" Padding="4,2" FontSize="12"/>
|
|
<TextBlock DockPanel.Dock="Left" Text=" H" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" Width="20" Margin="8,0,0,0"/>
|
|
<TextBox Text="{Binding SelectedElement.Height, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
|
Background="#0f3460" Foreground="#e0d0e0" BorderThickness="1"
|
|
BorderBrush="#2c3a5c" Padding="4,2" FontSize="12"/>
|
|
</DockPanel>
|
|
</StackPanel>
|
|
|
|
<!-- 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">
|
|
<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="#22c55e" 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="#e94560" 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}" Foreground="#a0a0b0" FontSize="11"
|
|
VerticalAlignment="Center" 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). -->
|
|
<TextBlock Text="Background" Foreground="#a0a0b0" FontSize="11" FontWeight="SemiBold" Margin="0,6,0,6"/>
|
|
<DockPanel Margin="0,0,0,4">
|
|
<TextBlock DockPanel.Dock="Left" Text="Remove" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" 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="0,0,0,4">
|
|
<TextBlock DockPanel.Dock="Left" Text="Blur" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" 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="0,0,0,6"
|
|
Visibility="{Binding SelectedElement.HasBackgroundEffect, Converter={StaticResource BoolToVis}}">
|
|
<TextBlock DockPanel.Dock="Left" Text="Strength" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" Width="84"/>
|
|
<TextBlock DockPanel.Dock="Right" Text="{Binding SelectedElement.BackgroundStrength, StringFormat={}{0:P0}}"
|
|
Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" 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>
|
|
|
|
<!-- Countdown config -->
|
|
<StackPanel Visibility="{Binding SelectedElement.IsCountdown, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
|
|
<TextBlock Text="Timer (minutes)" Foreground="#a0a0b0" FontSize="11" Margin="0,0,0,2"/>
|
|
<DockPanel Margin="0,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="#22c55e" 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="#e94560" 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}" Foreground="#a0a0b0" FontSize="11"
|
|
VerticalAlignment="Center" 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" Foreground="#a0a0b0" FontSize="11" FontWeight="SemiBold" Margin="0,0,0,4"/>
|
|
<DockPanel Margin="0,0,0,4">
|
|
<TextBlock DockPanel.Dock="Left" Text="X" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" Width="16"/>
|
|
<TextBox Text="{Binding SelectedElement.X, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
|
Background="#0f3460" Foreground="#e0d0e0" BorderThickness="1"
|
|
BorderBrush="#2c3a5c" Padding="4,2" FontSize="12"/>
|
|
<TextBlock DockPanel.Dock="Left" Text=" Y" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" Width="20" Margin="8,0,0,0"/>
|
|
<TextBox Text="{Binding SelectedElement.Y, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
|
Background="#0f3460" Foreground="#e0d0e0" BorderThickness="1"
|
|
BorderBrush="#2c3a5c" Padding="4,2" FontSize="12"/>
|
|
</DockPanel>
|
|
<DockPanel Margin="0,0,0,4">
|
|
<TextBlock DockPanel.Dock="Left" Text="W" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" Width="16"/>
|
|
<TextBox Text="{Binding SelectedElement.Width, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
|
Background="#0f3460" Foreground="#e0d0e0" BorderThickness="1"
|
|
BorderBrush="#2c3a5c" Padding="4,2" FontSize="12"/>
|
|
<TextBlock DockPanel.Dock="Left" Text=" H" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" Width="20" Margin="8,0,0,0"/>
|
|
<TextBox Text="{Binding SelectedElement.Height, UpdateSourceTrigger=PropertyChanged, StringFormat=F0}"
|
|
Background="#0f3460" Foreground="#e0d0e0" BorderThickness="1"
|
|
BorderBrush="#2c3a5c" Padding="4,2" FontSize="12"/>
|
|
</DockPanel>
|
|
<TextBlock Text="URI" Foreground="#a0a0b0" FontSize="11" Margin="0,0,0,2"/>
|
|
<DockPanel>
|
|
<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="#22c55e" 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="#e94560" StrokeThickness="2"
|
|
Width="14" Height="14" Stretch="Uniform"/>
|
|
</Button>
|
|
<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>
|
|
</StackPanel>
|
|
|
|
<!-- Chat box config -->
|
|
<StackPanel Visibility="{Binding SelectedElement.IsChatBox, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
|
|
<TextBlock Text="Chat Overlay" Foreground="#a0a0b0" FontSize="11" FontWeight="SemiBold" Margin="0,0,0,6"/>
|
|
|
|
<!-- Font Size -->
|
|
<DockPanel Margin="0,0,0,4">
|
|
<TextBlock DockPanel.Dock="Left" Text="Font Size" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" 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="#e0e0e0" 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" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" Width="70"/>
|
|
<ComboBox SelectedValue="{Binding SelectedElement.ChatFontFamily, Mode=TwoWay}"
|
|
SelectedValuePath="Content"
|
|
Background="#0f3460" Foreground="#e0e0e0" FontSize="11"
|
|
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" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" Width="70"/>
|
|
<TextBox Text="{Binding SelectedElement.ChatFontColor, UpdateSourceTrigger=PropertyChanged}"
|
|
Background="#0f3460" Foreground="#e0e0e0" BorderThickness="1"
|
|
BorderBrush="#2c3a5c" Padding="4,2" FontSize="11" Width="80"/>
|
|
</DockPanel>
|
|
|
|
<!-- Member Color -->
|
|
<DockPanel Margin="0,0,0,4">
|
|
<TextBlock DockPanel.Dock="Left" Text="Member" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" Width="70"/>
|
|
<TextBox Text="{Binding SelectedElement.ChatMemberColor, UpdateSourceTrigger=PropertyChanged}"
|
|
Background="#0f3460" Foreground="#e0e0e0" BorderThickness="1"
|
|
BorderBrush="#2c3a5c" Padding="4,2" FontSize="11" Width="80"/>
|
|
</DockPanel>
|
|
|
|
<!-- Super Chat Color -->
|
|
<DockPanel Margin="0,0,0,4">
|
|
<TextBlock DockPanel.Dock="Left" Text="Super Chat" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" Width="70"/>
|
|
<TextBox Text="{Binding SelectedElement.ChatSuperChatColor, UpdateSourceTrigger=PropertyChanged}"
|
|
Background="#0f3460" Foreground="#e0e0e0" BorderThickness="1"
|
|
BorderBrush="#2c3a5c" Padding="4,2" FontSize="11" Width="80"/>
|
|
</DockPanel>
|
|
|
|
<!-- Max Messages -->
|
|
<DockPanel Margin="0,0,0,4">
|
|
<TextBlock DockPanel.Dock="Left" Text="Max Msgs" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" 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="#e0e0e0" 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" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" Width="70"/>
|
|
<TextBox Text="{Binding SelectedElement.ChatBackgroundColor, UpdateSourceTrigger=PropertyChanged}"
|
|
Background="#0f3460" Foreground="#e0e0e0" BorderThickness="1"
|
|
BorderBrush="#2c3a5c" Padding="4,2" FontSize="11" Width="80"/>
|
|
</DockPanel>
|
|
|
|
<!-- Background Opacity -->
|
|
<DockPanel Margin="0,0,0,4">
|
|
<TextBlock DockPanel.Dock="Left" Text="Bg Opacity" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" 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="#e0e0e0" 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" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" 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="#e0e0e0" FontSize="11" Margin="6,0,0,0" VerticalAlignment="Center" Width="28"/>
|
|
</DockPanel>
|
|
|
|
<!-- Toggles -->
|
|
<CheckBox Content="Show timestamps" Foreground="#a0a0b0" FontSize="11" Margin="0,4,0,2"
|
|
IsChecked="{Binding SelectedElement.ChatShowTimestamps, Mode=TwoWay}"/>
|
|
<CheckBox Content="Show badges" Foreground="#a0a0b0" FontSize="11" 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" Foreground="#a0a0b0" FontSize="11" FontWeight="SemiBold" Margin="0,0,0,6"/>
|
|
<TextBlock Text="The built-in clip plays on every alert (Super Chat, membership…) unless a custom video is picked."
|
|
Foreground="#888" FontSize="10" TextWrapping="Wrap" Margin="0,0,0,6"/>
|
|
|
|
<DockPanel Margin="0,0,0,4">
|
|
<TextBlock DockPanel.Dock="Left" Text="Built-in video" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" Width="84"/>
|
|
<ToggleButton Style="{StaticResource PillToggle}"
|
|
IsChecked="{Binding SelectedElement.AlertUseDefaultVideo, Mode=TwoWay}"
|
|
HorizontalAlignment="Left"/>
|
|
</DockPanel>
|
|
|
|
<Grid Margin="0,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}"
|
|
IsReadOnly="True" Background="#0f3460" Foreground="#a0a0b0"
|
|
FontSize="11" VerticalAlignment="Center"/>
|
|
<Button Grid.Column="1" Content="Browse…" Command="{Binding BrowseAlertVideoCommand}"
|
|
CommandParameter="{Binding SelectedElement}" Style="{StaticResource YtButtonSecondary}"
|
|
Margin="4,0,0,0" Padding="8,2"/>
|
|
</Grid>
|
|
|
|
<Button Content="Reset to built-in" Command="{Binding ResetAlertVideoCommand}"
|
|
CommandParameter="{Binding SelectedElement}" Style="{StaticResource YtButtonSecondary}"
|
|
HorizontalAlignment="Left" Padding="8,2" Margin="0,0,4,0"/>
|
|
<TextBlock Text="Custom clips are referenced from disk — never stored in the layout."
|
|
Foreground="#888" FontSize="10" TextWrapping="Wrap" Margin="0,4,0,6"/>
|
|
|
|
<DockPanel Margin="0,0,0,4">
|
|
<TextBlock DockPanel.Dock="Left" Text="Message ticker" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" Width="84"/>
|
|
<ToggleButton Style="{StaticResource PillToggle}"
|
|
IsChecked="{Binding SelectedElement.AlertShowTicker, 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="0,0,0,4">
|
|
<TextBlock DockPanel.Dock="Left" Text="Display" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" Width="84"/>
|
|
<ComboBox SelectedValue="{Binding SelectedElement.AlertDisplayMethod, Mode=TwoWay}"
|
|
SelectedValuePath="Tag"
|
|
Background="#0f3460" Foreground="#e0e0e0" FontSize="11"
|
|
Width="140" VerticalAlignment="Center">
|
|
<ComboBoxItem Content="Scroll" Tag="TickerScroll"/>
|
|
<ComboBoxItem Content="Flash" Tag="Flash"/>
|
|
<ComboBoxItem Content="Solid" Tag="Solid"/>
|
|
</ComboBox>
|
|
</DockPanel>
|
|
|
|
<DockPanel Margin="0,0,0,4">
|
|
<TextBlock DockPanel.Dock="Left" Text="Volume" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" 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="#e0e0e0" FontSize="11" Margin="6,0,0,0" VerticalAlignment="Center" Width="32"/>
|
|
</DockPanel>
|
|
</StackPanel>
|
|
|
|
<!-- Background properties -->
|
|
<StackPanel Margin="0,8,0,0"
|
|
Visibility="{Binding SelectedElement.IsBackground, Converter={StaticResource BoolToVis}}">
|
|
<TextBlock Text="Background" Style="{StaticResource SubHeader}"/>
|
|
<StackPanel Margin="4,0,0,0">
|
|
<StackPanel Orientation="Horizontal" Margin="0,0,0,8">
|
|
<TextBlock Text="Use default background:" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center"/>
|
|
<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}"
|
|
IsReadOnly="True" Background="#0f3460" Foreground="#a0a0b0"
|
|
FontSize="11" VerticalAlignment="Center"/>
|
|
<Button Grid.Column="1" Content="Browse…" Command="{Binding BrowseBackgroundCommand}"
|
|
Style="{StaticResource YtButtonSecondary}" Margin="4,0,0,0" Padding="8,2"/>
|
|
</Grid>
|
|
<TextBlock Text="{Binding BackgroundSizeMismatchText}"
|
|
Foreground="#ef4444" 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>
|
|
|
|
<TextBlock Grid.Row="0" Text="LIVE CHAT" Style="{StaticResource SectionHeader}"/>
|
|
|
|
<!-- Inactive overlay (shown when NOT live) -->
|
|
<StackPanel Grid.Row="1" VerticalAlignment="Center" HorizontalAlignment="Center"
|
|
Visibility="{Binding ShowChatInactiveMessage, Converter={StaticResource BoolToVis}}">
|
|
<TextBlock Text="Chat is inactive" Foreground="#a0a0b0" FontSize="13"
|
|
HorizontalAlignment="Center" Margin="0,0,0,4"/>
|
|
<TextBlock Text="Chat becomes active once LlamaCasty is connected and streaming on YouTube."
|
|
Foreground="#666" FontSize="11" TextWrapping="Wrap"
|
|
HorizontalAlignment="Center" TextAlignment="Center" MaxWidth="240"/>
|
|
</StackPanel>
|
|
|
|
<!-- Pinned message -->
|
|
<Border Grid.Row="1" VerticalAlignment="Top" Margin="4,4,4,0" Padding="6,4" CornerRadius="4"
|
|
Background="#1a2a3a" BorderBrush="#38bdf8" BorderThickness="1"
|
|
Visibility="{Binding PinnedChatMessage, Converter={StaticResource NotNullToVis}}">
|
|
<StackPanel>
|
|
<TextBlock Text="{Binding PinnedChatMessage.AuthorName}" Foreground="#38bdf8" FontWeight="SemiBold" FontSize="11"/>
|
|
<TextBlock Text="{Binding PinnedChatMessage.Message}" Foreground="#e0e0e0" FontSize="11" TextWrapping="Wrap"/>
|
|
</StackPanel>
|
|
</Border>
|
|
|
|
<!-- Message feed (shown when live) -->
|
|
<ListBox Grid.Row="1" 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="#2a1a0a"/>
|
|
<Setter Property="BorderBrush" Value="#eab308"/>
|
|
<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="#e94560"
|
|
FontWeight="Bold" FontSize="12" VerticalAlignment="Center"/>
|
|
<TextBlock Text="{Binding SuperChatAmount, StringFormat=${0:F2}}"
|
|
Foreground="#eab308" FontWeight="Bold" FontSize="12"
|
|
Margin="8,0,0,0" VerticalAlignment="Center"
|
|
Visibility="{Binding IsSuperChat, Converter={StaticResource BoolToVis}}"/>
|
|
<TextBlock Text="{Binding SuperChatCurrency}" Foreground="#eab308"
|
|
FontSize="11" Margin="2,0,0,0" VerticalAlignment="Center"
|
|
Visibility="{Binding IsSuperChat, Converter={StaticResource BoolToVis}}"/>
|
|
</StackPanel>
|
|
<TextBlock Text="{Binding Message}" Foreground="#e0e0e0"
|
|
TextWrapping="Wrap" Margin="0,2,0,0"/>
|
|
</StackPanel>
|
|
</Border>
|
|
</DataTemplate></ListBox.ItemTemplate>
|
|
</ListBox>
|
|
</Grid>
|
|
</Grid>
|
|
</Border>
|
|
</UserControl>
|