Screen backdrop (schema v5/v6) + five-scene catalog + webcam polish: live desktop/game capture as a permanent non-deletable bottom layer (Source.IsBackdrop), auto full-screen game detection (Win32FullScreenDetector) else primary display, GraphicsCapturePicker re-designation, refcounted shared ScreenCaptureManager, Live-only backdrop by policy (HasBackdrop + v5-v6 backfill + EnforceBackdropPolicy, checkbox gone), SceneCatalog (Starting/Live/BRB/Chat/Ending) with + button re-adding missing scenes, webcam mid-session transparent-container fix (GetPreviewBitmap propagation), Chat half-screen-area cap, 'Add Webcam' always opens the picker (SwapWebcamIdentityAsync, no silent resurrect), WindowsRuntimeMarshal frame-read + 5s-throttled errors, docs updated, tests (65 passing)
This commit is contained in:
+77
-13
@@ -13,6 +13,8 @@
|
||||
WindowStartupLocation="CenterScreen"
|
||||
Closing="MainWindow_Closing"
|
||||
Loaded="MainWindow_Loaded"
|
||||
Activated="MainWindow_Activated"
|
||||
Deactivated="MainWindow_Deactivated"
|
||||
PreviewMouseLeftButtonDown="Window_PreviewMouseLeftButtonDown">
|
||||
|
||||
<Window.DataContext>
|
||||
@@ -130,11 +132,27 @@
|
||||
<RowDefinition Height="180"/>
|
||||
</Grid.RowDefinitions>
|
||||
|
||||
<!-- Scenes -->
|
||||
<!-- Scenes: the app has five canonical scenes. Work with less,
|
||||
never more — the "+" only offers the missing ones, and is
|
||||
hidden entirely once all five are back. -->
|
||||
<StackPanel Grid.Row="0" Orientation="Horizontal">
|
||||
<TextBlock Text="SCENES" Style="{StaticResource SectionHeader}" VerticalAlignment="Center"/>
|
||||
<Button ToolTip="Add Scene" Command="{Binding AddSceneCommand}"
|
||||
Style="{StaticResource IconButton}" Margin="4,-4,0,0">
|
||||
<Button ToolTip="Add Missing Scene" Click="AddSceneButton_Click"
|
||||
Style="{StaticResource IconButton}" Margin="4,-4,0,0"
|
||||
Visibility="{Binding ShowAddScene, Converter={StaticResource BoolToVis}}">
|
||||
<Button.ContextMenu>
|
||||
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"
|
||||
ItemsSource="{Binding MissingScenes}">
|
||||
<ContextMenu.ItemContainerStyle>
|
||||
<Style TargetType="MenuItem">
|
||||
<Setter Property="Header" Value="{Binding}"/>
|
||||
<Setter Property="Command"
|
||||
Value="{Binding DataContext.AddSceneCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
|
||||
<Setter Property="CommandParameter" Value="{Binding}"/>
|
||||
</Style>
|
||||
</ContextMenu.ItemContainerStyle>
|
||||
</ContextMenu>
|
||||
</Button.ContextMenu>
|
||||
<Path Data="M12,4 L12,20 M4,12 L20,12" Stroke="#e94560" StrokeThickness="2"
|
||||
Width="14" Height="14" Stretch="Uniform"/>
|
||||
</Button>
|
||||
@@ -265,12 +283,25 @@
|
||||
<ColumnDefinition Width="Auto"/>
|
||||
</Grid.ColumnDefinitions>
|
||||
|
||||
<Grid.ContextMenu>
|
||||
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
|
||||
<MenuItem Header="Change Capture…" Click="BackdropMenu_ChangeCapture"
|
||||
Visibility="{Binding IsBackdrop, Converter={StaticResource BoolToVis}}"/>
|
||||
<MenuItem Header="Refresh Capture" Click="BackdropMenu_RefreshCapture"
|
||||
Visibility="{Binding IsBackdrop, Converter={StaticResource BoolToVis}}"/>
|
||||
<Separator Visibility="{Binding IsBackdrop, Converter={StaticResource BoolToVis}}"/>
|
||||
<MenuItem Header="Remove Source" Click="SourceMenu_Remove"
|
||||
Visibility="{Binding IsBackdrop, Converter={StaticResource InverseBoolToVis}}"/>
|
||||
</ContextMenu>
|
||||
</Grid.ContextMenu>
|
||||
|
||||
<TextBlock Grid.Column="0" Text="{Binding Name}" VerticalAlignment="Center"
|
||||
Padding="4,2" TextTrimming="CharacterEllipsis"/>
|
||||
|
||||
<Button Grid.Column="1" ToolTip="Remove Source"
|
||||
Command="{Binding DataContext.RemoveSourceCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
|
||||
CommandParameter="{Binding}" Style="{StaticResource IconButton}">
|
||||
CommandParameter="{Binding}" Style="{StaticResource IconButton}"
|
||||
Visibility="{Binding IsBackdrop, Converter={StaticResource InverseBoolToVis}}">
|
||||
<Path Data="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"
|
||||
Fill="#d0d0d0" Width="13" Height="13" Stretch="Uniform"/>
|
||||
</Button>
|
||||
@@ -305,10 +336,31 @@
|
||||
<Viewbox Stretch="Uniform">
|
||||
<Grid x:Name="CanvasGrid" Width="1920" Height="1080" ClipToBounds="True" Background="Transparent">
|
||||
<Grid.ContextMenu>
|
||||
<ContextMenu>
|
||||
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
|
||||
<MenuItem Header="Show Webcam" Command="{Binding ShowWebcamCommand}"/>
|
||||
<MenuItem Header="Capture Display"
|
||||
Visibility="{Binding CanChangeBackdrop, Converter={StaticResource BoolToVis}}"
|
||||
ItemsSource="{Binding Displays}">
|
||||
<MenuItem.ItemContainerStyle>
|
||||
<Style TargetType="MenuItem">
|
||||
<Setter Property="Header" Value="{Binding Label}"/>
|
||||
<Setter Property="Command"
|
||||
Value="{Binding DataContext.SetBackdropDisplayCommand, RelativeSource={RelativeSource AncestorType=ContextMenu}}"/>
|
||||
<Setter Property="CommandParameter" Value="{Binding}"/>
|
||||
</Style>
|
||||
</MenuItem.ItemContainerStyle>
|
||||
</MenuItem>
|
||||
<MenuItem Header="Change Capture…" Command="{Binding ChangeCaptureCommand}"
|
||||
Visibility="{Binding CanChangeBackdrop, Converter={StaticResource BoolToVis}}"
|
||||
ToolTip="Pick any window with the OS picker"/>
|
||||
<MenuItem Header="Refresh Capture" Command="{Binding RefreshCaptureCommand}"
|
||||
Visibility="{Binding CanChangeBackdrop, Converter={StaticResource BoolToVis}}"/>
|
||||
</ContextMenu>
|
||||
</Grid.ContextMenu>
|
||||
<!-- Live desktop/game backdrop (bottom layer; the static
|
||||
background renders above it). -->
|
||||
<Image Source="{Binding BackdropImage}" Stretch="UniformToFill"
|
||||
IsHitTestVisible="False"/>
|
||||
<Image Source="{Binding ActiveBackgroundImage}" Stretch="UniformToFill"
|
||||
IsHitTestVisible="False"/>
|
||||
<Rectangle Stroke="#22c55e" StrokeThickness="6" IsHitTestVisible="False"/>
|
||||
@@ -339,6 +391,12 @@
|
||||
<DataTrigger Binding="{Binding IsVisible}" Value="True">
|
||||
<Setter Property="Visibility" Value="Visible"/>
|
||||
</DataTrigger>
|
||||
<!-- The backdrop 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 IsBackdrop}" Value="True">
|
||||
<Setter Property="IsHitTestVisible" Value="False"/>
|
||||
</DataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Grid.Style>
|
||||
@@ -368,9 +426,15 @@
|
||||
</Image>
|
||||
<Viewbox Stretch="Uniform">
|
||||
<Grid Width="1" Height="1">
|
||||
<Ellipse>
|
||||
<Ellipse.Style>
|
||||
<Style TargetType="Ellipse">
|
||||
<!-- 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>
|
||||
@@ -382,11 +446,11 @@
|
||||
</MultiDataTrigger>
|
||||
</Style.Triggers>
|
||||
</Style>
|
||||
</Ellipse.Style>
|
||||
<Ellipse.Fill>
|
||||
<ImageBrush ImageSource="{Binding DisplaySource}" Stretch="UniformToFill"/>
|
||||
</Ellipse.Fill>
|
||||
</Ellipse>
|
||||
</Image.Style>
|
||||
<Image.Clip>
|
||||
<EllipseGeometry Center="0.5,0.5" RadiusX="0.5" RadiusY="0.5"/>
|
||||
</Image.Clip>
|
||||
</Image>
|
||||
</Grid>
|
||||
</Viewbox>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user