"Clicking on everything produces no visible effect" — the cause was upstream of the controls, and it is the same defect as the invisible alert box (TASK 47): - `Source.DisplaySource` returned `VideoImageSource` only for ChatBox / WebSource / MediaSource / AlertBox. The scoreboard's frame was rendered and published, then never displayed. - the preview's Image has a per-type Visibility trigger for each of those and none for IsScoreboard, so even a correct DisplaySource would have stayed collapsed. Both are fixed, and the layer test now pins the frame-source list AND the trigger so the pair cannot drift apart again. Dev-only affordances are ORANGE from now on (creator ruling): "Simulate (dev)" is a TextBlock in Brush.Attention, so a development control can never be mistaken for product. The panel test pins the colour.
977 lines
87 KiB
XML
977 lines
87 KiB
XML
<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"
|
|
xmlns:local="clr-namespace:ytLive">
|
|
<Border Background="{StaticResource Brush.SurfaceContainerLowest}" 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). The zoom box samples the
|
|
whole CanvasGrid, not just this layer (TASK 51). -->
|
|
<Grid x:Name="BackdropLayer" IsHitTestVisible="False">
|
|
<Image Source="{Binding BackgroundImage}" Stretch="UniformToFill"
|
|
Visibility="{Binding BackgroundVisible, Converter={StaticResource BoolToVis}}"/>
|
|
<Image Source="{Binding ActiveBackgroundImage}" Stretch="UniformToFill"/>
|
|
</Grid>
|
|
<Image x:Name="TransitionOverlay" Width="1920" Height="1080" Stretch="Fill"
|
|
IsHitTestVisible="False" Visibility="Collapsed"/>
|
|
<Canvas x:Name="OverlayCanvas" Width="1920" Height="1080">
|
|
<ItemsControl x:Name="OverlayElements" 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>
|
|
<!-- The zoom layer never renders here — the ZoomBox magnifier (and the
|
|
output resolver) own its pixels (TASK 51). -->
|
|
<DataTrigger Binding="{Binding IsZoomLayer}" Value="True">
|
|
<Setter Property="Visibility" Value="Collapsed"/>
|
|
<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>
|
|
<!-- The scoreboard strip is a frame-source too: without this trigger its
|
|
Image stays collapsed and every property change looks inert. -->
|
|
<DataTrigger Binding="{Binding IsScoreboard}" 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="{StaticResource Brush.OnSurface}" 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="{StaticResource Brush.OnSurface}" VerticalAlignment="Center"/>
|
|
<Slider Width="120" Minimum="0" Maximum="1" Margin="8,0,0,0"
|
|
Value="{Binding BorderOpacity, Mode=TwoWay}"
|
|
VerticalAlignment="Center"/>
|
|
</StackPanel>
|
|
</MenuItem>
|
|
<!-- Filled from Models/BorderColorPalette on open — the list used to be
|
|
seven hand-written rows here, a second source of truth beside the
|
|
properties drop-down (TASK 63). -->
|
|
<MenuItem x:Name="WebcamBorderColorMenu" Header="Border Color"
|
|
SubmenuOpened="WebcamBorderColorMenu_SubmenuOpened"/>
|
|
<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="{StaticResource Brush.Attention}" StrokeDashArray="4 3" StrokeThickness="5"/>
|
|
<Ellipse Width="26" Height="26" Fill="{StaticResource Brush.Attention}" Stroke="White" StrokeThickness="2"
|
|
HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,-13,-13"/>
|
|
</Grid>
|
|
|
|
<Rectangle IsHitTestVisible="False" Stroke="{StaticResource Brush.Tertiary}" 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>
|
|
<Border HorizontalAlignment="Center" Margin="40,8" CornerRadius="20"
|
|
Background="{Binding SocialBarBackgroundBrush}">
|
|
<Border.Style>
|
|
<Style TargetType="Border">
|
|
<Setter Property="Padding" Value="0"/>
|
|
<Style.Triggers>
|
|
<DataTrigger Binding="{Binding Socials.BarBackgroundEnabled}" Value="True">
|
|
<Setter Property="Padding" Value="18,6"/>
|
|
</DataTrigger>
|
|
</Style.Triggers>
|
|
</Style>
|
|
</Border.Style>
|
|
<ItemsControl ItemsSource="{Binding Socials.Entries}"
|
|
HorizontalAlignment="Center"
|
|
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}" Width="24" Height="24"
|
|
Fill="{Binding DataContext.SocialBarTextBrush, RelativeSource={RelativeSource AncestorType=ItemsControl}}"
|
|
Stretch="Uniform" VerticalAlignment="Center">
|
|
<Path.Style>
|
|
<Style TargetType="Path">
|
|
<Style.Triggers>
|
|
<DataTrigger Binding="{Binding DataContext.Socials.BarTextShadow, RelativeSource={RelativeSource AncestorType=ItemsControl}}" Value="True">
|
|
<Setter Property="Effect">
|
|
<Setter.Value>
|
|
<DropShadowEffect Color="Black" BlurRadius="4" ShadowDepth="2" Direction="315" Opacity="0.85"/>
|
|
</Setter.Value>
|
|
</Setter>
|
|
</DataTrigger>
|
|
</Style.Triggers>
|
|
</Style>
|
|
</Path.Style>
|
|
</Path>
|
|
<TextBlock Text="{Binding Handle}" FontSize="14" VerticalAlignment="Center"
|
|
Margin="8,0,0,0"
|
|
Foreground="{Binding DataContext.SocialBarTextBrush, RelativeSource={RelativeSource AncestorType=ItemsControl}}">
|
|
<TextBlock.Style>
|
|
<Style TargetType="TextBlock">
|
|
<Style.Triggers>
|
|
<DataTrigger Binding="{Binding DataContext.Socials.BarTextShadow, RelativeSource={RelativeSource AncestorType=ItemsControl}}" Value="True">
|
|
<Setter Property="Effect">
|
|
<Setter.Value>
|
|
<DropShadowEffect Color="Black" BlurRadius="4" ShadowDepth="2" Direction="315" Opacity="0.85"/>
|
|
</Setter.Value>
|
|
</Setter>
|
|
</DataTrigger>
|
|
</Style.Triggers>
|
|
</Style>
|
|
</TextBlock.Style>
|
|
</TextBlock>
|
|
</StackPanel>
|
|
</DataTemplate>
|
|
</ItemsControl.ItemTemplate>
|
|
</ItemsControl>
|
|
</Border>
|
|
</Grid>
|
|
</Canvas>
|
|
</Grid>
|
|
|
|
<!-- The zoom dims: preview chrome ONLY — a SIBLING of CanvasGrid,
|
|
never a descendant — so the zoom box's VisualBrush sample of
|
|
CanvasGrid can never pick up the dim shading. The box must show
|
|
the captured area clean at all times, even when dragged over the
|
|
dims (creator ruling 2026-10-08). Renders above the composed
|
|
content (dims the whole scene outside the box incl. the global
|
|
overlays) and below the ZoomBox. -->
|
|
<ItemsControl x:Name="DimRectsLayer" 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>
|
|
|
|
<!-- The zoom box: layer n+1 — a SIBLING of CanvasGrid (not a
|
|
descendant), so its VisualBrush can sample the whole
|
|
composed preview (CanvasGrid = backdrop + every element
|
|
layer) without a circular reference, and it renders ABOVE
|
|
everything. It shows a fixed capture window (box/2 at the
|
|
click); dragging the box relocates it, never the capture. -->
|
|
<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="{StaticResource Brush.Secondary}" StrokeThickness="3" Fill="Transparent"/>
|
|
<Ellipse x:Name="ZoomCircleOutline" Stroke="{StaticResource Brush.Secondary}" StrokeThickness="3" Fill="Transparent" Visibility="Collapsed"/>
|
|
<Grid.Background>
|
|
<VisualBrush x:Name="ZoomVisualBrush" ViewboxUnits="Absolute" Stretch="Fill"/>
|
|
</Grid.Background>
|
|
</Grid>
|
|
</Canvas>
|
|
</Viewbox>
|
|
<Border HorizontalAlignment="Left" VerticalAlignment="Top" Margin="8,8,0,0"
|
|
Background="{StaticResource Brush.Primary}" 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="{StaticResource Brush.OnSurface}" 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="{StaticResource Brush.OnSurfaceVariant}" 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="{StaticResource Brush.SurfaceContainer}" 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="{StaticResource Brush.OnSurfaceVariant}" 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" Style="{StaticResource YtCheckBox}"
|
|
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="{StaticResource Brush.OnSurfaceVariant}" 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="{StaticResource Brush.Error}" FontSize="11" TextWrapping="Wrap" Margin="0,6,0,0"
|
|
Visibility="{Binding BroadcastForm.UpdateError, Converter={StaticResource NotNullToVis}}"/>
|
|
</StackPanel>
|
|
</Grid>
|
|
</Border>
|
|
<!-- The Socials drawer (same pattern as the other pull-outs) -->
|
|
<Border Background="{StaticResource Brush.SurfaceContainer}" ClipToBounds="True">
|
|
<Border.Style>
|
|
<Style TargetType="Border">
|
|
<Setter Property="Width" Value="0"/>
|
|
<Style.Triggers>
|
|
<DataTrigger Binding="{Binding SocialsDrawerOpen}" 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="*"/>
|
|
</Grid.RowDefinitions>
|
|
<Grid Grid.Row="0" Margin="0,0,0,8">
|
|
<TextBlock Text="SOCIALS" Style="{StaticResource SectionHeader}" HorizontalAlignment="Left"/>
|
|
<Button HorizontalAlignment="Right" VerticalAlignment="Top"
|
|
Background="Transparent" BorderThickness="0" Cursor="Hand"
|
|
Command="{Binding OpenSocialDialogCommand}"
|
|
ToolTip="Close">
|
|
<Button.Template>
|
|
<ControlTemplate TargetType="Button">
|
|
<ContentPresenter/>
|
|
</ControlTemplate>
|
|
</Button.Template>
|
|
<TextBlock Text="✕" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="14" FontWeight="Bold"/>
|
|
</Button>
|
|
</Grid>
|
|
<local:SocialsDialog Grid.Row="1" DataContext="{Binding SocialsDrawer}"/>
|
|
</Grid>
|
|
</Border>
|
|
<!-- The YPP-progress drawer (below Stream Settings on the rail) -->
|
|
<Border Background="{StaticResource Brush.SurfaceContainer}" 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="{StaticResource Brush.OnSurfaceVariant}" 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="{StaticResource Brush.OnSurface}" FontSize="13" TextWrapping="Wrap"
|
|
Visibility="{Binding Ypp.IsSignedOut, Converter={StaticResource BoolToVis}}"/>
|
|
<TextBlock Text="Refreshing from YouTube…" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="12"
|
|
Margin="0,0,0,4"
|
|
Visibility="{Binding Ypp.IsLoading, Converter={StaticResource BoolToVis}}"/>
|
|
<TextBlock Text="{Binding Ypp.LoadError}" Foreground="{StaticResource Brush.Error}" 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="{StaticResource Brush.OnSurface}" 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="{StaticResource Brush.OnSurface}" FontSize="13"/>
|
|
<Border Height="8" Background="{StaticResource Brush.SurfaceContainerHigh}" CornerRadius="4" Margin="0,4,0,10">
|
|
<Border Height="8" Background="{StaticResource Brush.Tertiary}" 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="{StaticResource Brush.OnSurface}" FontSize="13"/>
|
|
<Border Height="8" Background="{StaticResource Brush.SurfaceContainerHigh}" CornerRadius="4" Margin="0,4,0,10">
|
|
<Border Height="8" Background="{StaticResource Brush.Tertiary}" 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="{StaticResource Brush.OnSurface}" FontSize="13"/>
|
|
<Border Height="8" Background="{StaticResource Brush.SurfaceContainerHigh}" CornerRadius="4" Margin="0,4,0,6">
|
|
<Border Height="8" Background="{StaticResource Brush.Tertiary}" 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="{StaticResource Brush.OnSurfaceVariant}" 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="{StaticResource Brush.OnSurfaceVariant}" FontSize="11"
|
|
TextWrapping="Wrap" Margin="0,0,0,8"/>
|
|
<CheckBox Content="Two-Step Verification switched on" Style="{StaticResource YtCheckBox}"
|
|
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="{StaticResource Brush.Tertiary}" FontSize="12" Margin="0,0,0,6"/>
|
|
</Button>
|
|
<CheckBox Content="AdSense account linked" Style="{StaticResource YtCheckBox}"
|
|
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="{StaticResource Brush.Tertiary}" 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="{StaticResource Brush.OnSurfaceVariant}" 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="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" Margin="0,6,0,0"/>
|
|
</StackPanel>
|
|
</Grid>
|
|
</Border>
|
|
<!-- The Test-stream drawer (below YPP on the rail) -->
|
|
<Border Background="{StaticResource Brush.SurfaceContainer}" 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="{StaticResource Brush.OnSurfaceVariant}" FontSize="14" FontWeight="Bold"/>
|
|
</Button>
|
|
</Grid>
|
|
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto">
|
|
<StackPanel>
|
|
<TextBlock Text="{Binding TestSession.SessionCaption}"
|
|
Foreground="{StaticResource Brush.OnSurface}" FontSize="13" TextWrapping="Wrap" Margin="0,0,0,8"/>
|
|
<TextBlock Text="{Binding TestSession.ChatIdLine}"
|
|
Foreground="{StaticResource Brush.OnSurfaceVariant}" 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="{StaticResource Brush.OnSurfaceVariant}" 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="{StaticResource Brush.OnSurfaceVariant}" 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="{StaticResource Brush.Positive}" 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="{StaticResource Brush.OnSurfaceVariant}" 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="{StaticResource Brush.Youtube}"
|
|
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>
|
|
<StackPanel HorizontalAlignment="Center">
|
|
<Border Width="18" Height="13" CornerRadius="3" Background="White" HorizontalAlignment="Center" Margin="0,0,0,6">
|
|
<Border.LayoutTransform><RotateTransform Angle="90"/></Border.LayoutTransform>
|
|
<Path Data="M0,0 L6,3.5 L0,7 Z" Fill="{StaticResource Brush.Primary}" Width="6" Height="7"
|
|
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
|
</Border>
|
|
<TextBlock Text="Stream Info" Foreground="White" FontWeight="Bold" FontSize="15"
|
|
HorizontalAlignment="Center">
|
|
<TextBlock.LayoutTransform>
|
|
<RotateTransform Angle="90"/>
|
|
</TextBlock.LayoutTransform>
|
|
</TextBlock>
|
|
</StackPanel>
|
|
</Border>
|
|
<Border Width="30" VerticalAlignment="Center" Background="{StaticResource Brush.Youtube}"
|
|
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>
|
|
<StackPanel HorizontalAlignment="Center">
|
|
<Border Width="18" Height="13" CornerRadius="3" Background="White" HorizontalAlignment="Center" Margin="0,0,0,6">
|
|
<Border.LayoutTransform><RotateTransform Angle="90"/></Border.LayoutTransform>
|
|
<Path Data="M0,0 L6,3.5 L0,7 Z" Fill="{StaticResource Brush.Primary}" Width="6" Height="7"
|
|
HorizontalAlignment="Center" VerticalAlignment="Center"/>
|
|
</Border>
|
|
<TextBlock Text="YPP" Foreground="White" FontWeight="Bold" FontSize="15"
|
|
HorizontalAlignment="Center">
|
|
<TextBlock.LayoutTransform>
|
|
<RotateTransform Angle="90"/>
|
|
</TextBlock.LayoutTransform>
|
|
</TextBlock>
|
|
</StackPanel>
|
|
</Border>
|
|
<Border Width="30" VerticalAlignment="Center" Background="{StaticResource Brush.Tertiary}"
|
|
CornerRadius="4,0,0,4" Padding="0,10" Cursor="Hand" Margin="0,4,0,0"
|
|
ToolTip="Add or manage your social handles">
|
|
<Border.InputBindings>
|
|
<MouseBinding Gesture="LeftClick" Command="{Binding OpenSocialDialogCommand}"/>
|
|
</Border.InputBindings>
|
|
<StackPanel HorizontalAlignment="Center">
|
|
<Path Data="M4,4 m-2,0 a2,2 0 1 0 4,0 a2,2 0 1 0 -4,0 M16,8 m-2,0 a2,2 0 1 0 4,0 a2,2 0 1 0 -4,0 M4,14 m-2,0 a2,2 0 1 0 4,0 a2,2 0 1 0 -4,0 M5.8,5.2 L14.2,7.2 M5.8,12.8 L14.2,8.8"
|
|
Stroke="{StaticResource Brush.Background}" StrokeThickness="1.5" Width="18" Height="18" Stretch="Uniform"
|
|
HorizontalAlignment="Center" Margin="0,0,0,6">
|
|
<Path.LayoutTransform><RotateTransform Angle="90"/></Path.LayoutTransform></Path>
|
|
<TextBlock Text="Socials" Foreground="{StaticResource Brush.Background}" FontWeight="Bold" FontSize="15"
|
|
HorizontalAlignment="Center">
|
|
<TextBlock.LayoutTransform>
|
|
<RotateTransform Angle="90"/>
|
|
</TextBlock.LayoutTransform>
|
|
</TextBlock>
|
|
</StackPanel>
|
|
</Border>
|
|
<Border Width="30" VerticalAlignment="Center" Background="{StaticResource Brush.Attention}"
|
|
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>
|
|
<StackPanel HorizontalAlignment="Center">
|
|
<Path Data="M8,2 H16 M9,2 V8 L4,18 H20 L15,8 V2 M7,14 H17"
|
|
Stroke="{StaticResource Brush.Background}" StrokeThickness="1.5" Width="18" Height="18" Stretch="Uniform"
|
|
HorizontalAlignment="Center" Margin="0,0,0,6">
|
|
<Path.LayoutTransform><RotateTransform Angle="90"/></Path.LayoutTransform></Path>
|
|
<TextBlock Text="TEST" Foreground="{StaticResource Brush.Background}" FontWeight="Bold" FontSize="15"
|
|
HorizontalAlignment="Center">
|
|
<TextBlock.LayoutTransform>
|
|
<RotateTransform Angle="90"/>
|
|
</TextBlock.LayoutTransform>
|
|
</TextBlock>
|
|
</StackPanel>
|
|
</Border>
|
|
</StackPanel>
|
|
</StackPanel>
|
|
</Grid>
|
|
|
|
<!-- LIVE CONTROLS moved to the audio dock (2026-10-30): the mic row, the game
|
|
chip, TRAX and the sync slider all live there now. One audio surface. -->
|
|
|
|
</Grid>
|
|
</Border>
|
|
</UserControl>
|