Files
LlamaCasty/Controls/PreviewPane.xaml
T
gramps 966e718f1e Zoom: fix preview zoom box + shape-aspect anchoring (Unit: zoom box visuals)
Right-click in the live preview now procs a box that is actually visible and
populated with the zoomed region, and each Zoom View Shape sculpts the box per
spec. OBS-style derivative: magnifier box shows the composited region and dims
the surroundings (same pattern as OBS "zoom" / preview region highlights).

Root causes of the invisible box (two WPF traps, recorded in MyMistakes.md):
- Empty <VisualBrush Stretch="None"/> used as an OpacityMask paints nothing,
  so the mask alpha is 0 and the WHOLE box (border included) went transparent.
- ZoomBox lived inside CanvasGrid while its VisualBrush sampled CanvasGrid — a
  circular reference WPF renders as nothing. Hoisted ZoomBox to a sibling of
  CanvasGrid inside a new PreviewCanvas wrapper (same 1920x1080 space).

Shape semantics (MainViewModel.ActivateZoom):
- Circle: square, click point = center.
- VerticalRectangle (phone): tall 9:16, click point = top-left corner.
- HorizontalRectangle (tablet): wide 16:9, click point = top-left corner.
- Boxes stay fully inside 1920x1080 via Math.Clamp.

UI: ZoomRectOutline/ZoomCircleOutline shapes replace the square Border;
circle clips the box to an EllipseGeometry; brush Visual released when inactive.
Zoom View Shape dropdown now uses the standard YtComboBox style.

Test (Good Dog rule, one integration test): ZoomBoxInteractionTests verifies
aspect/anchoring/clamp per shape and the matching outline+clip in the real
MainWindow. Full suite 404 passed, 0 warnings.

Memory: HANDOFF.md zoom state updated; WPF VisualBrush traps appended to
MyMistakes.md.
2026-10-07 20:46:45 -07:00

981 lines
88 KiB
XML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<UserControl x:Class="ytLive.Controls.PreviewPane"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Helpers="clr-namespace:ytLive.Helpers">
<Border Background="#0a0a1a" CornerRadius="6" Margin="8,0"
BorderBrush="{Binding PreviewGlowBrush}" BorderThickness="{Binding PreviewGlowThickness}">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<!-- Preview monitor -->
<Grid x:Name="PreviewGrid" Grid.Row="0"
PreviewMouseLeftButtonDown="Preview_MouseLeftButtonDown"
PreviewMouseMove="Preview_MouseMove"
PreviewMouseLeftButtonUp="Preview_MouseLeftButtonUp"
PreviewMouseRightButtonDown="Preview_MouseRightButtonDown"
PreviewMouseRightButtonUp="Preview_MouseRightButtonUp">
<Viewbox Stretch="Uniform">
<Canvas x:Name="PreviewCanvas" Width="1920" Height="1080" ClipToBounds="True">
<Grid x:Name="CanvasGrid" Width="1920" Height="1080" ClipToBounds="True" Background="Transparent">
<Grid.RenderTransform>
<TransformGroup>
<ScaleTransform x:Name="ZoomScale"/>
<TranslateTransform x:Name="ZoomTranslate"/>
</TransformGroup>
</Grid.RenderTransform>
<Grid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Show Desktop" IsCheckable="True"
IsChecked="{Binding BackgroundShowDesktop}"
Visibility="{Binding CanChangeBackground, Converter={StaticResource BoolToVis}}"
ToolTip="Show desktop when no fullscreen game is detected"/>
<MenuItem Header="Capture Desktop"
Visibility="{Binding CanChangeBackground, Converter={StaticResource BoolToVis}}"
ItemsSource="{Binding Displays}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Label}"/>
<Setter Property="Command"
Value="{Binding DataContext.SetBackgroundDisplayCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
<MenuItem Header="Capture Window…"
Visibility="{Binding CanChangeBackground, Converter={StaticResource BoolToVis}}"
ItemsSource="{Binding OpenWindows}">
<MenuItem.ItemContainerStyle>
<Style TargetType="MenuItem">
<Setter Property="Header" Value="{Binding Label}"/>
<Setter Property="Command"
Value="{Binding DataContext.CaptureWindowCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
<Setter Property="CommandParameter" Value="{Binding}"/>
</Style>
</MenuItem.ItemContainerStyle>
</MenuItem>
<MenuItem Header="Refresh Window List" Command="{Binding RefreshWindowsCommand}"
Visibility="{Binding CanChangeBackground, Converter={StaticResource BoolToVis}}"/>
<MenuItem Header="Refresh Desktop" Command="{Binding RefreshCaptureCommand}"
Visibility="{Binding CanChangeBackground, Converter={StaticResource BoolToVis}}"/>
</ContextMenu>
</Grid.ContextMenu>
<!-- Live desktop/game background (bottom layer; the static
image renders above it). -->
<Image Source="{Binding BackgroundImage}" Stretch="UniformToFill"
Visibility="{Binding BackgroundVisible, Converter={StaticResource BoolToVis}}"
IsHitTestVisible="False"/>
<Image Source="{Binding ActiveBackgroundImage}" Stretch="UniformToFill"
IsHitTestVisible="False"/>
<Rectangle Stroke="#22c55e" StrokeThickness="6" IsHitTestVisible="False"/>
<Canvas x:Name="OverlayCanvas" Width="1920" Height="1080">
<ItemsControl ItemsSource="{Binding StagedScene.Elements}"
Width="1920" Height="1080">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Grid Width="{Binding Width}" Height="{Binding Height}"
Opacity="{Binding Opacity}"
Background="Transparent"
RenderTransformOrigin="0.5,0.5">
<Grid.Style>
<Style TargetType="Grid">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsVisible}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<!-- The background is a fixed full-frame layer (rendered separately,
below the static background): never hit-testable, so it can't be
selected, dragged, or bring up the webcam context menu. -->
<DataTrigger Binding="{Binding IsBackground}" Value="True">
<Setter Property="IsHitTestVisible" Value="False"/>
</DataTrigger>
</Style.Triggers>
</Style>
</Grid.Style>
<Grid.RenderTransform>
<ScaleTransform ScaleX="{Binding MirrorScale}"/>
</Grid.RenderTransform>
<Image Source="{Binding DisplaySource}"
RenderOptions.BitmapScalingMode="HighQuality">
<Image.Style>
<Style TargetType="Image">
<Setter Property="Visibility" Value="Collapsed"/>
<Setter Property="Stretch" Value="UniformToFill"/>
<Style.Triggers>
<DataTrigger Binding="{Binding IsImageSource}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsChatBox}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<!-- TASK 47: the alert box's frame (the decoded clip) is written onto
VideoImageSource exactly like the chat box's, so it needs the same
trigger. Without it the Image stayed Collapsed and every alert was
invisible in the preview while the decode logged healthy (2026-09-26). -->
<DataTrigger Binding="{Binding IsAlertBox}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
</DataTrigger>
<DataTrigger Binding="{Binding IsWebSource}" Value="True">
<Setter Property="Visibility" Value="Visible"/>
<Setter Property="Stretch" Value="Fill"/>
</DataTrigger>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsWebcam}" Value="True"/>
<Condition Binding="{Binding ClipShape}" Value="Traditional"/>
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
</Image>
<Viewbox Stretch="Uniform">
<Grid Width="1" Height="1">
<!-- Round webcam: a plain Image (the same efficient WriteableBitmap
path as Traditional) clipped to a unit circle and scaled by the
Viewbox — deliberately NOT an ImageBrush, whose per-frame
re-rasterization at full scale stalls the preview. -->
<Image Stretch="UniformToFill"
Source="{Binding DisplaySource}"
RenderOptions.BitmapScalingMode="HighQuality">
<Image.Style>
<Style TargetType="Image">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding IsWebcam}" Value="True"/>
<Condition Binding="{Binding ClipShape}" Value="Round"/>
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Image.Style>
<Image.Clip>
<EllipseGeometry Center="0.5,0.5" RadiusX="0.5" RadiusY="0.5"/>
</Image.Clip>
</Image>
</Grid>
</Viewbox>
<!-- OBS-style border: stroked rect (Traditional) / centered circle (Round).
Animations are driven by code-behind in WebcamMenu_SetBorderAnimation
because DataTrigger storyboards in DataTemplates cannot be stopped
(no ExitActions namescope support). -->
<Rectangle IsHitTestVisible="False"
Stroke="{Binding BorderBrush}"
StrokeThickness="{Binding BorderWidth}">
<Rectangle.Style>
<Style TargetType="Rectangle">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding HasBorder}" Value="True"/>
<Condition Binding="{Binding ClipShape}" Value="Traditional"/>
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Rectangle.Style>
</Rectangle>
<Ellipse IsHitTestVisible="False"
Stroke="{Binding BorderBrush}"
StrokeThickness="{Binding BorderWidth}"
Width="{Binding RoundBorderSize}"
Height="{Binding RoundBorderSize}"
HorizontalAlignment="Center" VerticalAlignment="Center">
<Ellipse.Style>
<Style TargetType="Ellipse">
<Setter Property="Visibility" Value="Collapsed"/>
<Style.Triggers>
<MultiDataTrigger>
<MultiDataTrigger.Conditions>
<Condition Binding="{Binding HasBorder}" Value="True"/>
<Condition Binding="{Binding ClipShape}" Value="Round"/>
</MultiDataTrigger.Conditions>
<Setter Property="Visibility" Value="Visible"/>
</MultiDataTrigger>
</Style.Triggers>
</Style>
</Ellipse.Style>
</Ellipse>
<Grid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Change Webcam…" Click="WebcamMenu_ChangeWebcam"
Visibility="{Binding IsWebcam, Converter={StaticResource BoolToVis}}"/>
<Separator/>
<MenuItem Header="Border Thickness">
<StackPanel Orientation="Horizontal" Margin="8,2">
<TextBlock Text="Thickness" Foreground="#e0e0e0" VerticalAlignment="Center"/>
<Slider Width="120" Minimum="0" Maximum="20" Margin="8,0,0,0"
Value="{Binding BorderWidth, Mode=TwoWay}"
VerticalAlignment="Center"/>
</StackPanel>
</MenuItem>
<MenuItem Header="Border Opacity">
<StackPanel Orientation="Horizontal" Margin="8,2">
<TextBlock Text="Opacity" Foreground="#e0e0e0" VerticalAlignment="Center"/>
<Slider Width="120" Minimum="0" Maximum="1" Margin="8,0,0,0"
Value="{Binding BorderOpacity, Mode=TwoWay}"
VerticalAlignment="Center"/>
</StackPanel>
</MenuItem>
<MenuItem Header="Border Color">
<MenuItem Header="None" Tag="" IsCheckable="True"
IsChecked="{Binding BorderColor, Converter={StaticResource EnumToBool}, ConverterParameter=''}"
Click="WebcamMenu_SetBorderColor"/>
<MenuItem Header="#ffffff" Tag="#ffffff" IsCheckable="True"
IsChecked="{Binding BorderColor, Converter={StaticResource EnumToBool}, ConverterParameter=#ffffff}"
Click="WebcamMenu_SetBorderColor"/>
<MenuItem Header="#000000" Tag="#000000" IsCheckable="True"
IsChecked="{Binding BorderColor, Converter={StaticResource EnumToBool}, ConverterParameter=#000000}"
Click="WebcamMenu_SetBorderColor"/>
<MenuItem Header="#e94560" Tag="#e94560" IsCheckable="True"
IsChecked="{Binding BorderColor, Converter={StaticResource EnumToBool}, ConverterParameter=#e94560}"
Click="WebcamMenu_SetBorderColor"/>
<MenuItem Header="#22c55e" Tag="#22c55e" IsCheckable="True"
IsChecked="{Binding BorderColor, Converter={StaticResource EnumToBool}, ConverterParameter=#22c55e}"
Click="WebcamMenu_SetBorderColor"/>
<MenuItem Header="#eab308" Tag="#eab308" IsCheckable="True"
IsChecked="{Binding BorderColor, Converter={StaticResource EnumToBool}, ConverterParameter=#eab308}"
Click="WebcamMenu_SetBorderColor"/>
<MenuItem Header="#38bdf8" Tag="#38bdf8" IsCheckable="True"
IsChecked="{Binding BorderColor, Converter={StaticResource EnumToBool}, ConverterParameter=#38bdf8}"
Click="WebcamMenu_SetBorderColor"/>
</MenuItem>
<MenuItem Header="Border Effect">
<MenuItem Header="None" Tag="None" IsCheckable="True"
IsChecked="{Binding BorderAnimation, Converter={StaticResource EnumToBool}, ConverterParameter=None}"
Click="WebcamMenu_SetBorderAnimation"/>
<MenuItem Header="Pulse" Tag="Pulse" IsCheckable="True"
IsChecked="{Binding BorderAnimation, Converter={StaticResource EnumToBool}, ConverterParameter=Pulse}"
Click="WebcamMenu_SetBorderAnimation"/>
<MenuItem Header="Chase" Tag="Chase" IsCheckable="True"
IsChecked="{Binding BorderAnimation, Converter={StaticResource EnumToBool}, ConverterParameter=Chase}"
Click="WebcamMenu_SetBorderAnimation"/>
<MenuItem Header="Rainbow" Tag="Rainbow" IsCheckable="True"
IsChecked="{Binding BorderAnimation, Converter={StaticResource EnumToBool}, ConverterParameter=Rainbow}"
Click="WebcamMenu_SetBorderAnimation"/>
<MenuItem Header="Shimmer" Tag="Shimmer" IsCheckable="True"
IsChecked="{Binding BorderAnimation, Converter={StaticResource EnumToBool}, ConverterParameter=Shimmer}"
Click="WebcamMenu_SetBorderAnimation"/>
<MenuItem Header="Marching Ants" Tag="MarchingAnts" IsCheckable="True"
IsChecked="{Binding BorderAnimation, Converter={StaticResource EnumToBool}, ConverterParameter=MarchingAnts}"
Click="WebcamMenu_SetBorderAnimation"/>
<MenuItem Header="Glow" Tag="Glow" IsCheckable="True"
IsChecked="{Binding BorderAnimation, Converter={StaticResource EnumToBool}, ConverterParameter=Glow}"
Click="WebcamMenu_SetBorderAnimation"/>
<MenuItem Header="Electricity" Tag="Electricity" IsCheckable="True"
IsChecked="{Binding BorderAnimation, Converter={StaticResource EnumToBool}, ConverterParameter=Electricity}"
Click="WebcamMenu_SetBorderAnimation"/>
<MenuItem Header="Sparkles" Tag="Sparkles" IsCheckable="True"
IsChecked="{Binding BorderAnimation, Converter={StaticResource EnumToBool}, ConverterParameter=Sparkles}"
Click="WebcamMenu_SetBorderAnimation"/>
</MenuItem>
<Separator/>
<MenuItem Header="Hide in this scene" Click="WebcamMenu_HideInScene"/>
<MenuItem Header="Remove" Click="WebcamMenu_Remove"/>
</ContextMenu>
</Grid.ContextMenu>
</Grid>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Grid x:Name="SelectionOverlay" IsHitTestVisible="False"
Canvas.Left="{Binding SelectedElement.X}" Canvas.Top="{Binding SelectedElement.Y}"
Width="{Binding SelectedElement.Width}" Height="{Binding SelectedElement.Height}">
<Rectangle Stroke="#e94560" StrokeDashArray="4 3" StrokeThickness="3"/>
<Ellipse Width="26" Height="26" Fill="#e94560" Stroke="White" StrokeThickness="2"
HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,-13,-13"/>
</Grid>
<ItemsControl ItemsSource="{Binding DimRects}" IsHitTestVisible="False">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<Canvas/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemContainerStyle>
<Style TargetType="ContentPresenter">
<Setter Property="Canvas.Left" Value="{Binding X}"/>
<Setter Property="Canvas.Top" Value="{Binding Y}"/>
</Style>
</ItemsControl.ItemContainerStyle>
<ItemsControl.ItemTemplate>
<DataTemplate>
<Rectangle Width="{Binding Width}" Height="{Binding Height}"
Fill="#000000" Opacity="0.55"/>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
<Rectangle IsHitTestVisible="False" Stroke="#e94560" StrokeThickness="3"
Canvas.Left="{Binding OutputRectX}" Canvas.Top="{Binding OutputRectY}"
Width="{Binding OutputRectWidth}" Height="{Binding OutputRectHeight}"
Visibility="{Binding IsOutputCropped, Converter={StaticResource BoolToVis}}"/>
<!-- Free-tier branding credit. The source is the SAME rendered
frame FramePump composites into the recording/stream
(BrandFlashPresenter → MainViewModel.BrandFlashFrame), so
the preview cannot drift from the broadcast. It replaces a
preview-only TextBlock that no viewer ever saw, and the
fade/spread/placement is the renderer's — there is no
storyboard here to keep in sync. -->
<Grid x:Name="BrandFlashElement" Width="1920" Height="1080"
Canvas.Left="0" Canvas.Top="0"
Background="Transparent" IsHitTestVisible="False"
Visibility="{Binding BrandFlashVisible, Converter={StaticResource BoolToVis}}">
<Image Source="{Binding BrandFlashImageSource}"
Stretch="Fill" RenderOptions.BitmapScalingMode="HighQuality"/>
</Grid>
<!-- Alert announcement strip (TASK 47): the same global
1920px overlay the output compositor blits along the
top edge. Global resource — never a Source. Without it
the strip reached the stream but never the preview. -->
<!-- Alert ticker: the announcement strip draws INSIDE the Stream
Alerts box (creator ruling 2026-09-26), so this element is
placed and sized to that box's rect rather than pinned as a
1920px bar to the top edge of the preview. -->
<Grid x:Name="AlertTickerElement"
Canvas.Left="{Binding AlertTickerLeft}"
Canvas.Top="{Binding AlertTickerTop}"
Width="{Binding AlertTickerWidth}"
Height="{Binding AlertTickerHeight}"
Background="Transparent" IsHitTestVisible="False"
Visibility="{Binding AlertTickerVisible, Converter={StaticResource BoolToVis}}">
<Image Source="{Binding AlertTickerImageSource}"
Stretch="Fill" RenderOptions.BitmapScalingMode="HighQuality"/>
</Grid>
<!-- Social bar: one centered horizontal row at the top or bottom of
the frame, green glow while enabled. Global resource — never a
Source, no Sources entry. Click toggles top ⇄ bottom. -->
<Grid x:Name="SocialBarElement" Width="1920" Canvas.Top="{Binding SocialBarTop}"
Background="Transparent"
Visibility="{Binding SocialBarVisible, Converter={StaticResource BoolToVis}}"
MouseLeftButtonDown="SocialBar_MouseLeftButtonDown"
Cursor="Hand">
<Grid.Effect>
<DropShadowEffect Color="{Binding SocialBarGlowBrush.Color}"
BlurRadius="18" ShadowDepth="0" Opacity="0.9"/>
</Grid.Effect>
<ItemsControl ItemsSource="{Binding Socials.Entries}"
HorizontalAlignment="Center" Margin="40,8"
IsHitTestVisible="False">
<ItemsControl.ItemsPanel>
<ItemsPanelTemplate>
<StackPanel Orientation="Horizontal"/>
</ItemsPanelTemplate>
</ItemsControl.ItemsPanel>
<ItemsControl.ItemTemplate>
<DataTemplate>
<StackPanel Orientation="Horizontal" Margin="0,0,20,0">
<Path Data="{Binding LogoData}" Fill="White" Width="24" Height="24"
Stretch="Uniform" VerticalAlignment="Center"/>
<TextBlock Text="{Binding Handle}" Foreground="White"
FontSize="14" VerticalAlignment="Center"
Margin="8,0,0,0"/>
</StackPanel>
</DataTemplate>
</ItemsControl.ItemTemplate>
</ItemsControl>
</Grid>
</Canvas>
</Grid>
<Grid x:Name="ZoomBox" IsHitTestVisible="False"
Canvas.Left="{Binding ZoomState.X}" Canvas.Top="{Binding ZoomState.Y}"
Width="{Binding ZoomState.Width}" Height="{Binding ZoomState.Height}"
Visibility="{Binding IsZoomActive, Converter={StaticResource BoolToVis}}">
<Rectangle x:Name="ZoomRectOutline" Stroke="#d8a72b" StrokeThickness="3" Fill="Transparent"/>
<Ellipse x:Name="ZoomCircleOutline" Stroke="#d8a72b" StrokeThickness="3" Fill="Transparent" Visibility="Collapsed"/>
<Grid.Background>
<VisualBrush x:Name="ZoomVisualBrush" ViewboxUnits="Absolute"/>
</Grid.Background>
</Grid>
</Canvas>
</Viewbox>
<Border HorizontalAlignment="Left" VerticalAlignment="Top" Margin="8,8,0,0"
Background="#CCe94560" CornerRadius="4" Padding="8,3" IsHitTestVisible="False"
Visibility="{Binding WebcamError, Converter={StaticResource NotNullToVis}}">
<TextBlock Text="{Binding WebcamError}" Foreground="White" FontSize="12"
MaxWidth="520" TextTrimming="CharacterEllipsis"/>
</Border>
<TextBlock Text="Preview" Foreground="#333" FontSize="24"
HorizontalAlignment="Center" VerticalAlignment="Center"
Visibility="{Binding ShowPreviewPlaceholder, Converter={StaticResource BoolToVis}}"/>
<StackPanel HorizontalAlignment="Center" VerticalAlignment="Center"
Visibility="{Binding ShowEmptySceneHint, Converter={StaticResource BoolToVis}}">
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<TextBlock Text="🖥️" Foreground="White" FontSize="30" Margin="0,0,20,0"/>
<TextBlock Text="📷" Foreground="White" FontSize="30" Margin="0,0,20,0"/>
<TextBlock Text="🖼️" Foreground="White" FontSize="30" Margin="0,0,20,0"/>
<TextBlock Text="✏️" Foreground="White" FontSize="30"/>
</StackPanel>
<TextBlock Text="Make it yours!" Foreground="#e0e0e0" FontSize="18"
FontWeight="SemiBold" HorizontalAlignment="Center" Margin="0,18,0,0"/>
<TextBlock Text="Add layers — your webcam, your screen, images, and text — to craft a brand and identity that's unmistakably you."
Foreground="#a0a0b0" FontSize="13" Margin="0,8,0,0"
TextWrapping="Wrap" MaxWidth="440" TextAlignment="Center"
HorizontalAlignment="Center"/>
</StackPanel>
</Grid>
<!-- ═══ RIGHT-EDGE PULL-OUTS (always visible) ═══
White "Stream Settings" + "YPP" tabs stacked on the preview's right
edge; each toggles its own drawer (the same animated-width pattern).
Only one drawer is open at a time — IsDrawerOpen exclusivity is
VM-enforced (opening one collapses the other). Any click outside
the rail collapses whichever is open (MainWindow code-behind). -->
<Grid Grid.Row="0">
<StackPanel x:Name="TextPullOutHost" Orientation="Horizontal"
HorizontalAlignment="Right" VerticalAlignment="Stretch">
<!-- The broadcast-metadata drawer -->
<Border Background="#16213e" ClipToBounds="True">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Width" Value="0"/>
<Style.Triggers>
<DataTrigger Binding="{Binding BroadcastForm.IsDrawerOpen}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Width"
To="320" Duration="0:0:0.2">
<DoubleAnimation.EasingFunction>
<CubicEase EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Width"
To="0" Duration="0:0:0.2">
<DoubleAnimation.EasingFunction>
<CubicEase EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<!-- Fixed-width inner so text never reflows mid-animation -->
<Grid Width="320" Margin="14,10,14,12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" Margin="0,0,0,8">
<TextBlock Text="BROADCAST TEXT" Style="{StaticResource SectionHeader}"
HorizontalAlignment="Left"/>
<Button HorizontalAlignment="Right" VerticalAlignment="Top"
Background="Transparent" BorderThickness="0" Cursor="Hand"
Command="{Binding BroadcastForm.CloseDrawerCommand}"
ToolTip="Close">
<Button.Template>
<ControlTemplate TargetType="Button">
<ContentPresenter/>
</ControlTemplate>
</Button.Template>
<TextBlock Text="✕" Foreground="#a0a0b0" FontSize="14" FontWeight="Bold"/>
</Button>
</Grid>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<StackPanel>
<TextBlock Text="TITLE" Style="{StaticResource YtLabel}" Margin="0,0,0,3"/>
<TextBox Style="{StaticResource YtTextBox}" Margin="0,0,0,10"
Text="{Binding BroadcastForm.Title, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="DESCRIPTION" Style="{StaticResource YtLabel}" Margin="0,0,0,3"/>
<TextBox Style="{StaticResource YtTextBox}" Height="90" AcceptsReturn="True"
TextWrapping="Wrap" VerticalScrollBarVisibility="Auto" Margin="0,0,0,10"
Text="{Binding BroadcastForm.Description, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="TAGS (COMMA-SEPARATED)" Style="{StaticResource YtLabel}" Margin="0,0,0,3"/>
<TextBox Style="{StaticResource YtTextBox}" Margin="0,0,0,10"
Text="{Binding BroadcastForm.TagsCsv, UpdateSourceTrigger=PropertyChanged}"/>
<TextBlock Text="VISIBILITY" Style="{StaticResource YtLabel}" Margin="0,0,0,3"/>
<ComboBox Style="{StaticResource YtComboBox}" Margin="0,0,0,10"
ItemsSource="{Binding BroadcastForm.VisibilityOptions}"
SelectedItem="{Binding BroadcastForm.Visibility}"/>
<CheckBox Content="Made for kids" Foreground="#e0e0e0" FontSize="12"
Margin="0,2,0,10"
IsChecked="{Binding BroadcastForm.MadeForKids}"/>
<TextBlock Text="SCHEDULED START" Style="{StaticResource YtLabel}" Margin="0,0,0,3"/>
<TextBlock Text="{Binding BroadcastForm.ScheduledStartText}"
Foreground="#888899" FontSize="12" Margin="0,0,0,4"/>
</StackPanel>
</ScrollViewer>
<StackPanel Grid.Row="2" Margin="0,10,0,0">
<Button Style="{StaticResource YtButton}" Content="Update Broadcast"
Command="{Binding BroadcastForm.UpdateBroadcastCommand}"/>
<TextBlock Text="{Binding BroadcastForm.UpdateError}"
Foreground="#ef4444" FontSize="11" TextWrapping="Wrap" Margin="0,6,0,0"
Visibility="{Binding BroadcastForm.UpdateError, Converter={StaticResource NotNullToVis}}"/>
</StackPanel>
</Grid>
</Border>
<!-- The YPP-progress drawer (below Stream Settings on the rail) -->
<Border Background="#16213e" ClipToBounds="True">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Width" Value="0"/>
<Style.Triggers>
<DataTrigger Binding="{Binding Ypp.IsDrawerOpen}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Width"
To="320" Duration="0:0:0.2">
<DoubleAnimation.EasingFunction>
<CubicEase EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Width"
To="0" Duration="0:0:0.2">
<DoubleAnimation.EasingFunction>
<CubicEase EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid Width="320" Margin="14,10,14,12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" Margin="0,0,0,8">
<TextBlock Text="YPP PROGRESS" Style="{StaticResource SectionHeader}"
HorizontalAlignment="Left"/>
<Button HorizontalAlignment="Right" VerticalAlignment="Top"
Background="Transparent" BorderThickness="0" Cursor="Hand"
Command="{Binding Ypp.CloseDrawerCommand}"
ToolTip="Close">
<Button.Template>
<ControlTemplate TargetType="Button">
<ContentPresenter/>
</ControlTemplate>
</Button.Template>
<TextBlock Text="✕" Foreground="#a0a0b0" FontSize="14" FontWeight="Bold"/>
</Button>
</Grid>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<StackPanel>
<TextBlock Text="Sign in to YouTube to see your Partner Program progress and eligibility checklist here."
Foreground="#d0d0e0" FontSize="13" TextWrapping="Wrap"
Visibility="{Binding Ypp.IsSignedOut, Converter={StaticResource BoolToVis}}"/>
<TextBlock Text="Refreshing from YouTube…" Foreground="#a0a0b0" FontSize="12"
Margin="0,0,0,4"
Visibility="{Binding Ypp.IsLoading, Converter={StaticResource BoolToVis}}"/>
<TextBlock Text="{Binding Ypp.LoadError}" Foreground="#ef4444" FontSize="11"
TextWrapping="Wrap" Margin="0,0,0,4"
Visibility="{Binding Ypp.LoadError, Converter={StaticResource NotNullToVis}}"/>
<StackPanel Visibility="{Binding Ypp.IsSignedOut, Converter={StaticResource InverseBoolToVis}}">
<TextBlock Text="{Binding Ypp.DisplayName}" Foreground="#e0e0e0" FontSize="13"
FontWeight="SemiBold" Margin="0,0,0,10"/>
<TextBlock Text="SUBSCRIBERS — TIER 1 · FAN FUNDING" Style="{StaticResource YtLabel}" Margin="0,0,0,2"/>
<TextBlock Text="{Binding Ypp.Tier1SubsText}" Foreground="#e0e0e0" FontSize="13"/>
<Border Height="8" Background="#2a2a40" CornerRadius="4" Margin="0,4,0,10">
<Border Height="8" Background="#e94560" CornerRadius="4" HorizontalAlignment="Left"
Width="{Binding Ypp.Tier1SubsBarWidth}"/>
</Border>
<TextBlock Text="SUBSCRIBERS — TIER 2 · AD REVENUE" Style="{StaticResource YtLabel}" Margin="0,0,0,2"/>
<TextBlock Text="{Binding Ypp.Tier2SubsText}" Foreground="#e0e0e0" FontSize="13"/>
<Border Height="8" Background="#2a2a40" CornerRadius="4" Margin="0,4,0,10">
<Border Height="8" Background="#e94560" CornerRadius="4" HorizontalAlignment="Left"
Width="{Binding Ypp.Tier2SubsBarWidth}"/>
</Border>
<TextBlock Text="PUBLIC UPLOADS (90 DAYS)" Style="{StaticResource YtLabel}" Margin="0,0,0,2"/>
<TextBlock Text="{Binding Ypp.UploadsText}" Foreground="#e0e0e0" FontSize="13"/>
<Border Height="8" Background="#2a2a40" CornerRadius="4" Margin="0,4,0,6">
<Border Height="8" Background="#e94560" CornerRadius="4" HorizontalAlignment="Left"
Width="{Binding Ypp.UploadsBarWidth}"/>
</Border>
<TextBlock Text="LONG-FORM WATCH HOURS + SHORTS VIEWS" Style="{StaticResource YtLabel}" Margin="0,10,0,2"/>
<TextBlock Text="These arrive with the extended analytics update — subscriber + upload + standing progress is live now."
Foreground="#666677" FontSize="12" TextWrapping="Wrap"/>
<TextBlock Text="COMPLIANCE CHECKLIST" Style="{StaticResource YtLabel}" Margin="0,14,0,4"/>
<TextBlock Text="CHANNEL STANDING" Style="{StaticResource YtLabel}" Margin="0,0,0,2"/>
<TextBlock Text="{Binding Ypp.StandingDetailText}" Foreground="#888899" FontSize="11"
TextWrapping="Wrap" Margin="0,0,0,8"/>
<CheckBox Content="Two-Step Verification switched on" Foreground="#e0e0e0" FontSize="12"
Margin="0,2,0,0"
IsChecked="{Binding Ypp.TwoFactorEnabled}"/>
<Button Background="Transparent" BorderThickness="0" Cursor="Hand" HorizontalAlignment="Left"
Command="{Binding Ypp.OpenTwoStepCommand}" ToolTip="Open Google's 2-Step Verification page">
<Button.Template>
<ControlTemplate TargetType="Button">
<ContentPresenter/>
</ControlTemplate>
</Button.Template>
<TextBlock Text="Set up on Google →" Foreground="#5aa7ff" FontSize="12" Margin="0,0,0,6"/>
</Button>
<CheckBox Content="AdSense account linked" Foreground="#e0e0e0" FontSize="12"
Margin="0,2,0,0"
IsChecked="{Binding Ypp.AdSenseLinked}"/>
<Button Background="Transparent" BorderThickness="0" Cursor="Hand" HorizontalAlignment="Left"
Command="{Binding Ypp.OpenEarnCommand}" ToolTip="Open the YouTube Earn page">
<Button.Template>
<ControlTemplate TargetType="Button">
<ContentPresenter/>
</ControlTemplate>
</Button.Template>
<TextBlock Text="How to link on YouTube →" Foreground="#5aa7ff" FontSize="12" Margin="0,0,0,6"/>
</Button>
<TextBlock Text="WHAT COUNTS" Style="{StaticResource YtLabel}" Margin="0,10,0,4"/>
<TextBlock Text="{Binding Ypp.WhatCountsText}" Foreground="#a0a0b0" FontSize="12"
TextWrapping="Wrap" Margin="0,0,0,6"/>
</StackPanel>
</StackPanel>
</ScrollViewer>
<StackPanel Grid.Row="2" Margin="0,10,0,0">
<Button Style="{StaticResource YtButton}" Content="Refresh from YouTube"
Command="{Binding Ypp.RefreshCommand}"/>
<TextBlock Text="{Binding Ypp.LastUpdatedText}" Foreground="#888899" FontSize="11" Margin="0,6,0,0"/>
</StackPanel>
</Grid>
</Border>
<!-- The Test-stream drawer (below YPP on the rail) -->
<Border Background="#16213e" ClipToBounds="True">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Width" Value="0"/>
<Style.Triggers>
<DataTrigger Binding="{Binding TestSession.IsDrawerOpen}" Value="True">
<DataTrigger.EnterActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Width"
To="320" Duration="0:0:0.2">
<DoubleAnimation.EasingFunction>
<CubicEase EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.EnterActions>
<DataTrigger.ExitActions>
<BeginStoryboard>
<Storyboard>
<DoubleAnimation Storyboard.TargetProperty="Width"
To="0" Duration="0:0:0.2">
<DoubleAnimation.EasingFunction>
<CubicEase EasingMode="EaseOut"/>
</DoubleAnimation.EasingFunction>
</DoubleAnimation>
</Storyboard>
</BeginStoryboard>
</DataTrigger.ExitActions>
</DataTrigger>
</Style.Triggers>
</Style>
</Border.Style>
<Grid Width="320" Margin="14,10,14,12">
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
<RowDefinition Height="Auto"/>
</Grid.RowDefinitions>
<Grid Grid.Row="0" Margin="0,0,0,8">
<TextBlock Text="TEST STREAM" Style="{StaticResource SectionHeader}"
HorizontalAlignment="Left"/>
<Button HorizontalAlignment="Right" VerticalAlignment="Top"
Background="Transparent" BorderThickness="0" Cursor="Hand"
Command="{Binding TestSession.CloseDrawerCommand}"
ToolTip="Close">
<Button.Template>
<ControlTemplate TargetType="Button">
<ContentPresenter/>
</ControlTemplate>
</Button.Template>
<TextBlock Text="✕" Foreground="#a0a0b0" FontSize="14" FontWeight="Bold"/>
</Button>
</Grid>
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
<StackPanel>
<TextBlock Text="{Binding TestSession.SessionCaption}"
Foreground="#d0d0e0" FontSize="13" TextWrapping="Wrap" Margin="0,0,0,8"/>
<TextBlock Text="{Binding TestSession.ChatIdLine}"
Foreground="#888899" FontSize="11"
Visibility="{Binding TestSession.IsTestSession, Converter={StaticResource BoolToVis}}"/>
<TextBlock Text="MOCK CHAT INPUT" Style="{StaticResource YtLabel}" Margin="0,10,0,3"/>
<TextBlock Text="Sends a real live chat message — it round-trips through the actual chat poll and renders on your chat overlay."
Foreground="#a0a0b0" FontSize="11" TextWrapping="Wrap" Margin="0,0,0,4"/>
<TextBox Style="{StaticResource YtTextBox}" Margin="0,0,0,6"
Text="{Binding TestSession.SendMessageText, UpdateSourceTrigger=PropertyChanged}"/>
<Button Style="{StaticResource YtButton}" Content="Send Message"
Command="{Binding TestSession.SendTestMessageCommand}"/>
<TextBlock Text="SIMULATED EVENTS" Style="{StaticResource YtLabel}" Margin="0,14,0,3"/>
<TextBlock Text="Fired into your chat overlay locally — YouTube's insert API only creates text, so these never reach YouTube."
Foreground="#a0a0b0" FontSize="11" TextWrapping="Wrap" Margin="0,0,0,4"/>
<StackPanel Orientation="Horizontal" Margin="0,0,0,4">
<Button Style="{StaticResource YtButtonSecondary}" Content="Subscriber" Padding="10,4" FontSize="11"
Margin="0,0,6,0" Command="{Binding TestSession.SimulateSubscriberCommand}"/>
<Button Style="{StaticResource YtButtonSecondary}" Content="New Member" Padding="10,4" FontSize="11"
Margin="0,0,6,0" Command="{Binding TestSession.SimulateMemberCommand}"/>
</StackPanel>
<Button Style="{StaticResource YtButtonSecondary}" Content="Super Chat (5.00)" Padding="10,4" FontSize="11"
HorizontalAlignment="Left" Command="{Binding TestSession.SimulateSuperChatCommand}"/>
<TextBlock Text="{Binding TestSession.LastEventText}"
Foreground="#22c55e" FontSize="11" TextWrapping="Wrap" Margin="0,10,0,0"
Visibility="{Binding TestSession.LastEventText, Converter={StaticResource NotNullToVis}}"/>
</StackPanel>
</ScrollViewer>
<StackPanel Grid.Row="2" Margin="0,10,0,0">
<Button Style="{StaticResource YtButton}" Content="End Test"
Command="{Binding TestSession.EndTestCommand}"
Visibility="{Binding TestSession.IsTestSession, Converter={StaticResource BoolToVis}}"/>
<TextBlock Text="Test broadcasts are private — nobody is notified, and it runs through the exact same pipeline as a real live."
Foreground="#666677" FontSize="11" TextWrapping="Wrap" Margin="0,6,0,0"/>
</StackPanel>
</Grid>
</Border>
<!-- The stacked white tabs -->
<StackPanel Orientation="Vertical" HorizontalAlignment="Right" VerticalAlignment="Stretch">
<Border Width="30" VerticalAlignment="Center" Background="#ffffff"
CornerRadius="4,0,0,4" Padding="0,10" Cursor="Hand" Margin="0,4,0,0"
ToolTip="Broadcast text fields">
<Border.InputBindings>
<MouseBinding Gesture="LeftClick"
Command="{Binding BroadcastForm.ToggleDrawerCommand}"/>
</Border.InputBindings>
<TextBlock Text="Stream Settings" Foreground="#e94560" FontWeight="Bold" FontSize="15"
HorizontalAlignment="Center">
<TextBlock.LayoutTransform>
<RotateTransform Angle="90"/>
</TextBlock.LayoutTransform>
</TextBlock>
</Border>
<Border Width="30" VerticalAlignment="Center" Background="#ffffff"
CornerRadius="4,0,0,4" Padding="0,10" Cursor="Hand" Margin="0,4,0,0"
ToolTip="YouTube Partner Program progress">
<Border.InputBindings>
<MouseBinding Gesture="LeftClick"
Command="{Binding Ypp.ToggleDrawerCommand}"/>
</Border.InputBindings>
<TextBlock Text="YPP" Foreground="#e94560" FontWeight="Bold" FontSize="15"
HorizontalAlignment="Center">
<TextBlock.LayoutTransform>
<RotateTransform Angle="90"/>
</TextBlock.LayoutTransform>
</TextBlock>
</Border>
<Border Width="30" VerticalAlignment="Center" Background="#ffffff"
CornerRadius="4,0,0,4" Padding="0,10" Cursor="Hand" Margin="0,4,0,0"
ToolTip="Test stream — mock chat input and simulated events">
<Border.InputBindings>
<MouseBinding Gesture="LeftClick"
Command="{Binding TestSession.ToggleDrawerCommand}"/>
</Border.InputBindings>
<TextBlock Text="TEST" Foreground="#9c6f1c" FontWeight="Bold" FontSize="15"
HorizontalAlignment="Center">
<TextBlock.LayoutTransform>
<RotateTransform Angle="90"/>
</TextBlock.LayoutTransform>
</TextBlock>
</Border>
</StackPanel>
</StackPanel>
</Grid>
<!-- ═══ LIVE CONTROLS (below preview, never overlaps) ═══
Line 1: Socials + TRAX meter (desktop/game audio)
Line 2: Mic meter + volume -->
<StackPanel Grid.Row="2" Orientation="Vertical" HorizontalAlignment="Center"
VerticalAlignment="Center" Margin="0,4">
<!-- Line 1: Socials + TRAX -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,4">
<!-- Socials button: hover glows the social bar in the preview -->
<Button x:Name="SocialsButton"
Command="{Binding OpenSocialDialogCommand}"
Style="{StaticResource YtButtonSecondary}" Padding="12,4"
FontSize="11" VerticalAlignment="Center" Margin="0,0,12,0"
ToolTip="Add or manage your social handles"
MouseEnter="SocialsButton_MouseEnter"
MouseLeave="SocialsButton_MouseLeave">
<StackPanel Orientation="Horizontal">
<Ellipse Width="8" Height="8" Fill="{Binding SocialBarDotBrush}"
Margin="0,0,6,0" VerticalAlignment="Center"/>
<TextBlock Text="Socials"/>
</StackPanel>
</Button>
<!-- TRAX meter -->
<Border Background="#D9000000" CornerRadius="6" Padding="12,8">
<StackPanel Orientation="Horizontal">
<Button Style="{StaticResource YtButtonSecondary}" Padding="8,2"
FontSize="11" VerticalAlignment="Center" Margin="0,0,8,0"
ToolTipService.ToolTip="{Binding TraxToolTip}"
ToolTipService.Placement="Top"
Click="TraxButton_Click"
PreviewMouseRightButtonUp="TraxButton_PreviewMouseRightButtonUp">
<StackPanel Orientation="Horizontal">
<Ellipse Width="8" Height="8" Fill="{Binding TraxDotBrush}"
Margin="0,0,6,0" VerticalAlignment="Center"/>
<TextBlock Text="TRAX"/>
</StackPanel>
</Button>
<TextBlock Text="Desktop Audio" Foreground="#a0a0b0" FontSize="11"
FontWeight="SemiBold" VerticalAlignment="Center" Margin="0,0,8,0"
ToolTip="Desktop/game audio via WASAPI loopback (automatic); TRAX music rides the same channel"/>
<Border Width="288" Height="14" CornerRadius="7" Background="#3a3b52" Margin="0,0,8,0"
ClipToBounds="True" VerticalAlignment="Center">
<Grid>
<Rectangle Width="57" Fill="#4a4520" HorizontalAlignment="Left" Margin="173,0,0,0"
VerticalAlignment="Stretch"/>
<Rectangle Width="58" Fill="#4a2222" HorizontalAlignment="Left" Margin="230,0,0,0"
VerticalAlignment="Stretch"/>
<Border HorizontalAlignment="Left" Width="{Binding GameMeterFillWidth}"
Background="{Binding GameMeterBrush}" Opacity="0.75"/>
<Rectangle Width="1" Height="10" Fill="#26000000" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="36,0,0,0"/>
<Rectangle Width="1" Height="10" Fill="#26000000" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="72,0,0,0"/>
<Rectangle Width="1" Height="10" Fill="#26000000" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="108,0,0,0"/>
<Rectangle Width="1" Height="10" Fill="#26000000" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="144,0,0,0"/>
<Rectangle Width="1" Height="10" Fill="#26000000" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="180,0,0,0"/>
<Rectangle Width="1" Height="10" Fill="#26000000" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="216,0,0,0"/>
<Rectangle Width="1" Height="10" Fill="#26000000" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="252,0,0,0"/>
<Rectangle Width="2" Fill="#00000080" HorizontalAlignment="Left" Margin="173,0,0,0"
VerticalAlignment="Stretch"/>
<Rectangle Width="2" Fill="#00000080" HorizontalAlignment="Left" Margin="230,0,0,0"
VerticalAlignment="Stretch"/>
<ScrollViewer HorizontalScrollBarVisibility="Auto" VerticalScrollBarVisibility="Disabled"
Visibility="{Binding TraxError, Converter={StaticResource NotNullToVis}}"
VerticalAlignment="Center" Margin="4,0">
<TextBlock Text="{Binding TraxError}" Foreground="#e94560" FontSize="9"
FontWeight="SemiBold" VerticalAlignment="Center"/>
</ScrollViewer>
</Grid>
</Border>
<Grid Width="16" Height="16" Cursor="Hand" Margin="0,0,8,0"
VerticalAlignment="Center" ToolTip="{Binding GameMuteText}"
MouseLeftButtonUp="GameSpeaker_MouseLeftButtonUp">
<Path Fill="#d0d0d0" Stretch="Uniform"
Data="M3,9v6h4l5,5V4L7,9H3zM16.5,12c0,-1.77 -1,-3.29 -2.5,-4.03v8.05c1.5,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z"
Visibility="{Binding GameMuted, Converter={StaticResource InverseBoolToVis}}"/>
<Path Fill="#ef4444" Stretch="Uniform"
Data="M3,9v6h4l5,5V4L7,9H3zM16.5,12c0,-1.77 -1,-3.29 -2.5,-4.03v8.05c1.5,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z"
Visibility="{Binding GameMuted, Converter={StaticResource BoolToVis}}"/>
<Path Stroke="#ef4444" StrokeThickness="2.5" StrokeEndLineCap="Round" Stretch="Uniform"
Data="M21,3 L3,21"
Visibility="{Binding GameMuted, Converter={StaticResource BoolToVis}}"/>
</Grid>
<Slider Width="130" Minimum="0" Maximum="1"
Value="{Binding GameAudioVolume, Mode=TwoWay}" VerticalAlignment="Center"
PreviewMouseLeftButtonDown="GameVolumeSlider_PreviewMouseLeftButtonDown"
PreviewMouseLeftButtonUp="GameVolumeSlider_PreviewMouseLeftButtonUp"
LostMouseCapture="GameVolumeSlider_LostMouseCapture"/>
</StackPanel>
</Border>
</StackPanel>
<!-- Line 2: Mic meter + volume -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
<Button Command="{Binding OpenMicPickerCommand}"
Style="{StaticResource YtButtonSecondary}" Padding="12,4"
FontSize="11" VerticalAlignment="Center" Margin="0,0,8,0"
ToolTip="{Binding MicStatusToolTip}">
<StackPanel Orientation="Horizontal">
<Ellipse Width="8" Height="8" Fill="{Binding MicStatusBrush}"
Margin="0,0,6,0" VerticalAlignment="Center"/>
<TextBlock Text="MIC"/>
</StackPanel>
</Button>
<Border Width="288" Height="14" CornerRadius="7" Background="#3a3b52" Margin="0,0,8,0"
ClipToBounds="True" VerticalAlignment="Center">
<Grid>
<Rectangle Width="57" Fill="#4a4520" HorizontalAlignment="Left" Margin="173,0,0,0"
VerticalAlignment="Stretch"/>
<Rectangle Width="58" Fill="#4a2222" HorizontalAlignment="Left" Margin="230,0,0,0"
VerticalAlignment="Stretch"/>
<TextBlock Text="{Binding MicSourceName}" Foreground="#b8b8c8" FontSize="10"
VerticalAlignment="Center" Margin="6,0,0,0" MaxWidth="270"
TextTrimming="CharacterEllipsis"
Visibility="{Binding MicSourceName, Converter={StaticResource NotNullToVis}}"/>
<Border HorizontalAlignment="Left" Width="{Binding MeterFillWidth}"
Background="{Binding MeterBrush}" Opacity="0.75"/>
<Rectangle Width="1" Height="10" Fill="#26000000" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="36,0,0,0"/>
<Rectangle Width="1" Height="10" Fill="#26000000" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="72,0,0,0"/>
<Rectangle Width="1" Height="10" Fill="#26000000" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="108,0,0,0"/>
<Rectangle Width="1" Height="10" Fill="#26000000" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="144,0,0,0"/>
<Rectangle Width="1" Height="10" Fill="#26000000" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="180,0,0,0"/>
<Rectangle Width="1" Height="10" Fill="#26000000" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="216,0,0,0"/>
<Rectangle Width="1" Height="10" Fill="#26000000" VerticalAlignment="Center" HorizontalAlignment="Left" Margin="252,0,0,0"/>
<Rectangle Width="2" Fill="#00000080" HorizontalAlignment="Left" Margin="173,0,0,0"
VerticalAlignment="Stretch"/>
<Rectangle Width="2" Fill="#00000080" HorizontalAlignment="Left" Margin="230,0,0,0"
VerticalAlignment="Stretch"/>
</Grid>
</Border>
<Grid Width="16" Height="16" Cursor="Hand" Margin="0,0,8,0"
VerticalAlignment="Center" ToolTip="{Binding MicMuteText}"
MouseLeftButtonUp="MicSpeaker_MouseLeftButtonUp">
<Path Fill="#d0d0d0" Stretch="Uniform"
Data="M12,14c1.66,0 3,-1.34 3,-3V5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v6C9,12.66 10.34,14 12,14zM17.3,11c0,3 -2.54,5.1 -5.3,5.1S6.7,14 6.7,11H5c0,3.41 2.72,6.23 6,6.72V21h2v-3.28c3.28,-0.49 6,-3.31 6,-6.72H17.3z"
Visibility="{Binding MicMuted, Converter={StaticResource InverseBoolToVis}}"/>
<Path Fill="#ef4444" Stretch="Uniform"
Data="M12,14c1.66,0 3,-1.34 3,-3V5c0,-1.66 -1.34,-3 -3,-3S9,3.34 9,5v6C9,12.66 10.34,14 12,14zM17.3,11c0,3 -2.54,5.1 -5.3,5.1S6.7,14 6.7,11H5c0,3.41 2.72,6.23 6,6.72V21h2v-3.28c3.28,-0.49 6,-3.31 6,-6.72H17.3z"
Visibility="{Binding MicMuted, Converter={StaticResource BoolToVis}}"/>
<Path Stroke="#ef4444" StrokeThickness="2.5" StrokeEndLineCap="Round" Stretch="Uniform"
Data="M21,3 L3,21"
Visibility="{Binding MicMuted, Converter={StaticResource BoolToVis}}"/>
</Grid>
<Slider Width="130" Minimum="0" Maximum="1"
Value="{Binding MicVolume, Mode=TwoWay}" VerticalAlignment="Center"
PreviewMouseLeftButtonDown="VolumeSlider_PreviewMouseLeftButtonDown"
PreviewMouseLeftButtonUp="VolumeSlider_PreviewMouseLeftButtonUp"
LostMouseCapture="VolumeSlider_LostMouseCapture"/>
<StackPanel Orientation="Horizontal" Margin="10,0,0,0" VerticalAlignment="Center"
ToolTip="Audio sync (−500..+500 ms). Positive: delays the audio when it runs ahead of the video. Negative: advances it (drops the first |N| ms of the recording) when it runs behind. Calibrate with a clap: is the clap late in the video, move negative; early, move positive.">
<TextBlock Text="AUDIO SYNC" FontSize="9" Foreground="#9898aa"
VerticalAlignment="Center" Margin="0,0,5,0"/>
<Slider Width="70" Minimum="-500" Maximum="500"
Value="{Binding AudioSyncOffsetMs, Mode=TwoWay}"
IsEnabled="{Binding IsEditMode}"
VerticalAlignment="Center"
ToolTip="{Binding AudioSyncOffsetMs, StringFormat=Audio sync: {0} ms}"/>
</StackPanel>
</StackPanel>
</StackPanel>
</Grid>
</Border>
</UserControl>