30a193b2c9
1. Error text shows in red inside the desktop audio meter bar (scrollable) when TRAX load or playback fails — user gets feedback, not just a silent red dot. 2. LoadTrax no longer nulls _music on error — the track path is retained so the user sees what failed and can retry. 3. OnTraxPlaybackEnded now receives the NAudio exception and surfaces it. 4. TRAX dot is green (playing) or gray (idle/no track) — no more yellow or red states. 207 tests, 0 warnings.
1326 lines
105 KiB
XML
1326 lines
105 KiB
XML
<Window x:Class="ytLive.MainWindow"
|
|
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
|
|
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
|
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
|
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
|
xmlns:vm="clr-namespace:ytLive.ViewModels"
|
|
xmlns:models="clr-namespace:ytLive.Models"
|
|
xmlns:Helpers="clr-namespace:ytLive.Helpers"
|
|
mc:Ignorable="d"
|
|
Title="{Binding WindowTitle}" Height="720" Width="1280"
|
|
MinWidth="1180" MinHeight="600"
|
|
Icon="/ytLive;component/Assets/tyllive-icon.png"
|
|
Background="#1a1a2e"
|
|
WindowStartupLocation="CenterScreen"
|
|
Closing="MainWindow_Closing"
|
|
Loaded="MainWindow_Loaded"
|
|
Activated="MainWindow_Activated"
|
|
Deactivated="MainWindow_Deactivated"
|
|
PreviewMouseLeftButtonDown="Window_PreviewMouseLeftButtonDown">
|
|
|
|
<Window.DataContext>
|
|
<vm:MainViewModel/>
|
|
</Window.DataContext>
|
|
|
|
<Window.TaskbarItemInfo>
|
|
<TaskbarItemInfo x:Name="TaskbarInfo"/>
|
|
</Window.TaskbarItemInfo>
|
|
|
|
<Window.Resources>
|
|
<ResourceDictionary>
|
|
<BooleanToVisibilityConverter x:Key="BoolToVis"/>
|
|
<Helpers:InverseBoolToVisibilityConverter x:Key="InverseBoolToVis"
|
|
xmlns:Helpers="clr-namespace:ytLive.Helpers"/>
|
|
<Helpers:NotNullToVisibilityConverter x:Key="NotNullToVis"
|
|
xmlns:Helpers="clr-namespace:ytLive.Helpers"/>
|
|
<Helpers:NullToVisibilityConverter x:Key="NullToVis"
|
|
xmlns:Helpers="clr-namespace:ytLive.Helpers"/>
|
|
<Helpers:HiddenBoolToVisibilityConverter x:Key="HiddenBoolToVis"
|
|
xmlns:Helpers="clr-namespace:ytLive.Helpers"/>
|
|
<Helpers:EnumToBoolConverter x:Key="EnumToBool"
|
|
xmlns:Helpers="clr-namespace:ytLive.Helpers"/>
|
|
<Helpers:SourceTypeToVisibilityConverter x:Key="SourceTypeToCountdownVis"
|
|
xmlns:Helpers="clr-namespace:ytLive.Helpers"
|
|
xmlns:models2="clr-namespace:ytLive.Models"/>
|
|
<Helpers:SourceTypeToVisibilityConverter x:Key="SourceTypeToWebVis"
|
|
xmlns:Helpers="clr-namespace:ytLive.Helpers"/>
|
|
|
|
<!-- Red dot shown in the taskbar icon while live -->
|
|
<DrawingImage x:Key="LiveOverlay">
|
|
<DrawingImage.Drawing>
|
|
<DrawingGroup>
|
|
<GeometryDrawing Brush="#e94560">
|
|
<GeometryDrawing.Geometry>
|
|
<EllipseGeometry Center="8,8" RadiusX="8" RadiusY="8"/>
|
|
</GeometryDrawing.Geometry>
|
|
</GeometryDrawing>
|
|
</DrawingGroup>
|
|
</DrawingImage.Drawing>
|
|
</DrawingImage>
|
|
|
|
<!-- Eye icon: open when visible, slashed when hidden (source rows) -->
|
|
<Style x:Key="EyeIconStyle" TargetType="Path">
|
|
<Setter Property="Data" Value="M12,4.5C7,4.5 2.73,7.61 1,12c1.73,4.39 6,7.5 11,7.5s9.27,-3.11 11,-7.5c-1.73,-4.39 -6,-7.5 -11,-7.5zM12,17c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,9c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
|
|
<Style.Triggers>
|
|
<DataTrigger Binding="{Binding IsVisible}" Value="False">
|
|
<Setter Property="Data" Value="M12,7c2.76,0 5,2.24 5,5 0,0.65 -0.13,1.26 -0.36,1.83l2.92,2.92c1.51,-1.26 2.7,-2.89 3.43,-4.75 -1.73,-4.39 -6,-7.5 -11,-7.5 -1.4,0 -2.74,0.25 -3.98,0.7l2.16,2.16C10.74,7.13 11.35,7 12,7zM2,4.27l2.28,2.28 0.46,0.46C3.08,8.3 1.78,10.02 1,12c1.73,4.39 6,7.5 11,7.5 1.55,0 3.03,-0.3 4.38,-0.84l0.42,0.42L19.73,22 21,20.73 3.27,3 2,4.27zM7.53,9.8l1.55,1.55c-0.05,0.21 -0.08,0.43 -0.08,0.65 0,1.66 1.34,3 3,3 0.22,0 0.44,-0.03 0.65,-0.08l1.55,1.55c-0.67,0.33 -1.41,0.53 -2.2,0.53 -2.76,0 -5,-2.24 -5,-5 0,-0.79 0.2,-1.53 0.53,-2.2zM11.84,9.02l3.15,3.15 0.02,-0.16c0,-1.66 -1.34,-3 -3,-3l-0.17,0.01z"/>
|
|
</DataTrigger>
|
|
</Style.Triggers>
|
|
</Style>
|
|
|
|
<Style x:Key="EyeButton" TargetType="Button" BasedOn="{StaticResource IconButton}">
|
|
<Setter Property="ToolTip" Value="Hide Source"/>
|
|
<Style.Triggers>
|
|
<DataTrigger Binding="{Binding IsVisible}" Value="False">
|
|
<Setter Property="ToolTip" Value="Show Source"/>
|
|
</DataTrigger>
|
|
</Style.Triggers>
|
|
</Style>
|
|
</ResourceDictionary>
|
|
</Window.Resources>
|
|
|
|
<Grid>
|
|
<Grid.RowDefinitions>
|
|
<RowDefinition Height="Auto"/>
|
|
<RowDefinition Height="Auto"/>
|
|
<RowDefinition Height="*"/>
|
|
<RowDefinition Height="Auto"/>
|
|
</Grid.RowDefinitions>
|
|
|
|
<!-- ═══ TOP BAR: Stream Controls ═══ -->
|
|
<Border Grid.Row="0" Background="{Binding TopBarBackground}" Padding="16,10">
|
|
<Grid>
|
|
<Grid.ColumnDefinitions>
|
|
<ColumnDefinition Width="Auto"/>
|
|
<ColumnDefinition Width="*"/>
|
|
<ColumnDefinition Width="Auto"/>
|
|
</Grid.ColumnDefinitions>
|
|
|
|
<!-- Logo / Brand — clicking opens the in-app About overlay -->
|
|
<Button Grid.Column="0" Background="Transparent" BorderThickness="0" Cursor="Hand"
|
|
VerticalAlignment="Center" Margin="0,0,24,0" ToolTip="About ytLlive"
|
|
Command="{Binding OpenAboutCommand}">
|
|
<Button.Template>
|
|
<ControlTemplate TargetType="Button">
|
|
<ContentPresenter/>
|
|
</ControlTemplate>
|
|
</Button.Template>
|
|
<TextBlock Text="ytLlive" Foreground="#e94560" FontSize="22" FontWeight="Bold"/>
|
|
</Button>
|
|
|
|
<!-- REC sign (center): dark offline, red live, darker red when private -->
|
|
<StackPanel Grid.Column="1" Orientation="Horizontal"
|
|
HorizontalAlignment="Center" VerticalAlignment="Center">
|
|
<Ellipse Width="12" Height="12" VerticalAlignment="Center"
|
|
Fill="{Binding RecDotBrush}" Opacity="{Binding RecDotOpacity}"/>
|
|
<TextBlock Text="REC" FontSize="16" FontWeight="Bold"
|
|
Foreground="{Binding RecTextBrush}" VerticalAlignment="Center" Margin="8,0,0,0"/>
|
|
<Border Background="#8f1f1f" CornerRadius="3" Padding="6,1" Margin="8,0,0,0"
|
|
VerticalAlignment="Center"
|
|
Visibility="{Binding IsLivePrivate, Converter={StaticResource BoolToVis}}">
|
|
<TextBlock Text="PRIVATE" FontSize="11" FontWeight="Bold" Foreground="White"/>
|
|
</Border>
|
|
<TextBlock Text="{Binding LiveElapsedText}" Foreground="White" FontSize="14"
|
|
FontFamily="Consolas" VerticalAlignment="Center" Margin="14,0,0,0"
|
|
Visibility="{Binding IsLive, Converter={StaticResource BoolToVis}}"/>
|
|
</StackPanel>
|
|
|
|
<!-- Single three-state action button -->
|
|
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center">
|
|
<Border Width="26" Height="26" CornerRadius="13" Background="#16213e" ClipToBounds="True"
|
|
Margin="0,0,8,0" VerticalAlignment="Center"
|
|
ToolTip="{Binding AccountDisplayName}"
|
|
Visibility="{Binding IsConnected, Converter={StaticResource BoolToVis}}">
|
|
<Image Source="{Binding AccountAvatarUrl}" Stretch="UniformToFill"/>
|
|
</Border>
|
|
<Button Content="Start Stream" Style="{StaticResource YtButton}"
|
|
Command="{Binding StartStreamCommand}"
|
|
Visibility="{Binding ShowStartStream, Converter={StaticResource BoolToVis}}"/>
|
|
<Button Content="End Stream" Style="{StaticResource YtButton}"
|
|
Background="#333" Command="{Binding EndStreamCommand}"
|
|
Visibility="{Binding IsLive, Converter={StaticResource BoolToVis}}"/>
|
|
</StackPanel>
|
|
</Grid>
|
|
</Border>
|
|
|
|
<!-- ═══ MIDDLE: Main Content ═══ -->
|
|
<!-- YouTube health banner (TASK 5 item 3): report-by-exception — visible
|
|
ONLY while a warning/error configuration issue exists. Amber for
|
|
warning, darker red for error (HealthIssueBackground). -->
|
|
<Border Grid.Row="1" Background="{Binding HealthIssueBackground}"
|
|
Visibility="{Binding HealthIssueBanner, Converter={StaticResource NotNullToVis}}">
|
|
<TextBlock Text="{Binding HealthIssueBanner}" Foreground="White" FontSize="13"
|
|
FontWeight="SemiBold" Margin="16,8" TextTrimming="CharacterEllipsis"/>
|
|
</Border>
|
|
|
|
<Grid Grid.Row="2" Margin="8">
|
|
<Grid.ColumnDefinitions>
|
|
<ColumnDefinition Width="1*" MinWidth="220" MaxWidth="360"/>
|
|
<ColumnDefinition Width="2*"/>
|
|
<ColumnDefinition Width="1*" MinWidth="300" MaxWidth="400"/>
|
|
</Grid.ColumnDefinitions>
|
|
|
|
<!-- LEFT PANEL: Scenes & Sources -->
|
|
<Border Grid.Column="0" Background="#16213e" CornerRadius="6" Padding="12">
|
|
<Grid>
|
|
<Grid.RowDefinitions>
|
|
<RowDefinition Height="Auto"/>
|
|
<RowDefinition Height="Auto"/>
|
|
<RowDefinition Height="Auto"/>
|
|
<RowDefinition Height="Auto"/>
|
|
<RowDefinition Height="*"/>
|
|
<RowDefinition Height="Auto"/>
|
|
<RowDefinition Height="Auto"/>
|
|
<RowDefinition Height="*"/>
|
|
</Grid.RowDefinitions>
|
|
|
|
<!-- Scenes: the app has five canonical scenes. Work with less,
|
|
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 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>
|
|
</StackPanel>
|
|
|
|
<Helpers:FocusPreservingListBox x:Name="SceneList" Grid.Row="1" Background="Transparent" BorderThickness="0"
|
|
ItemsSource="{Binding Scenes}"
|
|
SelectedItem="{Binding ActiveScene}"
|
|
SelectionChanged="SceneList_SelectionChanged"
|
|
PreviewMouseLeftButtonDown="List_PreviewMouseLeftButtonDown"
|
|
PreviewMouseMove="List_PreviewMouseMove"
|
|
PreviewMouseLeftButtonUp="List_PreviewMouseLeftButtonUp"
|
|
Foreground="#e0e0e0" FontSize="13">
|
|
<Helpers:FocusPreservingListBox.ItemContainerStyle>
|
|
<Style TargetType="ListBoxItem" BasedOn="{StaticResource YtListBoxItem}">
|
|
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
|
<Style.Triggers>
|
|
<DataTrigger Binding="{Binding IsHidden}" Value="True">
|
|
<Setter Property="Opacity" Value="0.45"/>
|
|
</DataTrigger>
|
|
</Style.Triggers>
|
|
</Style>
|
|
</Helpers:FocusPreservingListBox.ItemContainerStyle>
|
|
<ListBox.ItemTemplate>
|
|
<DataTemplate>
|
|
<Grid Margin="0,2">
|
|
<TextBlock Text="{Binding Name}" VerticalAlignment="Center"
|
|
Padding="4,2" TextTrimming="CharacterEllipsis"/>
|
|
</Grid>
|
|
</DataTemplate>
|
|
</ListBox.ItemTemplate>
|
|
</Helpers:FocusPreservingListBox>
|
|
|
|
<!-- Visual separation between the two listboxes: a hairline
|
|
with breathing room on either side (the creator's fix). -->
|
|
<Rectangle Grid.Row="2" Height="1" Fill="#2c3a5c" Margin="0,10,0,2"/>
|
|
|
|
<!-- Sources (for active scene) -->
|
|
<StackPanel Grid.Row="3" Orientation="Horizontal" Margin="0,8,0,0">
|
|
<TextBlock Text="SOURCES" Style="{StaticResource SectionHeader}" VerticalAlignment="Center"/>
|
|
<Button ToolTip="Add Source" Style="{StaticResource IconButton}" Margin="4,-4,0,0"
|
|
Click="AddSourceButton_Click">
|
|
<Button.ContextMenu>
|
|
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
|
|
<MenuItem Header="Webcam" Command="{Binding AddWebcamCommand}"
|
|
IsEnabled="{Binding CanAddWebcamToActiveScene}"
|
|
ToolTip="One webcam at a time — it's already in your stream"/>
|
|
<MenuItem Header="Screen" Command="{Binding AddSourceCommand}" CommandParameter="{x:Static models:SourceType.DisplayCapture}"/>
|
|
<MenuItem Header="Background" Command="{Binding AddSourceCommand}" CommandParameter="{x:Static models:SourceType.Background}"/>
|
|
<MenuItem Header="Image" Command="{Binding AddImageCommand}"/>
|
|
<MenuItem Header="Text" Command="{Binding AddSourceCommand}" CommandParameter="{x:Static models:SourceType.TextOverlay}"/>
|
|
<MenuItem Header="Countdown" Command="{Binding AddSourceCommand}" CommandParameter="{x:Static models:SourceType.Countdown}"
|
|
Visibility="{Binding IsCountdownAvailable, Converter={StaticResource BoolToVis}}"
|
|
ToolTip="Timer overlay — Starting and BRB scenes only"/>
|
|
<MenuItem Header="Web" Command="{Binding AddSourceCommand}" CommandParameter="{x:Static models:SourceType.WebSource}"
|
|
ToolTip="Embedded web page — set URI in Elements panel"/>
|
|
</ContextMenu>
|
|
</Button.ContextMenu>
|
|
<Path Data="M12,4 L12,20 M4,12 L20,12" Stroke="#e94560" StrokeThickness="2"
|
|
Width="14" Height="14" Stretch="Uniform"/>
|
|
</Button>
|
|
</StackPanel>
|
|
|
|
<Helpers:FocusPreservingListBox x:Name="SourceList" Grid.Row="4" Background="Transparent" BorderThickness="0"
|
|
ItemsSource="{Binding ActiveScene.Elements}"
|
|
SelectedItem="{Binding SelectedElement, Mode=TwoWay}"
|
|
PreviewMouseLeftButtonDown="List_PreviewMouseLeftButtonDown"
|
|
PreviewMouseMove="List_PreviewMouseMove"
|
|
PreviewMouseLeftButtonUp="List_PreviewMouseLeftButtonUp"
|
|
Foreground="#e0e0e0" FontSize="13">
|
|
<Helpers:FocusPreservingListBox.ItemContainerStyle>
|
|
<Style TargetType="ListBoxItem" BasedOn="{StaticResource YtListBoxItem}">
|
|
<Setter Property="HorizontalContentAlignment" Value="Stretch"/>
|
|
<Style.Triggers>
|
|
<DataTrigger Binding="{Binding IsVisible}" Value="False">
|
|
<Setter Property="Opacity" Value="0.45"/>
|
|
</DataTrigger>
|
|
</Style.Triggers>
|
|
</Style>
|
|
</Helpers:FocusPreservingListBox.ItemContainerStyle>
|
|
<ListBox.ItemTemplate>
|
|
<DataTemplate>
|
|
<Grid Margin="0,2">
|
|
<Grid.ColumnDefinitions>
|
|
<ColumnDefinition Width="*"/>
|
|
<ColumnDefinition Width="Auto"/>
|
|
<ColumnDefinition Width="Auto"/>
|
|
<ColumnDefinition Width="Auto"/>
|
|
</Grid.ColumnDefinitions>
|
|
|
|
<Grid.ContextMenu>
|
|
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
|
|
<MenuItem Header="Change Capture…" Click="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">
|
|
<TextBlock.Style>
|
|
<Style TargetType="TextBlock">
|
|
<Setter Property="Visibility" Value="Visible"/>
|
|
<Style.Triggers>
|
|
<DataTrigger Binding="{Binding IsEditing}" Value="True">
|
|
<Setter Property="Visibility" Value="Collapsed"/>
|
|
</DataTrigger>
|
|
</Style.Triggers>
|
|
</Style>
|
|
</TextBlock.Style>
|
|
</TextBlock>
|
|
|
|
<TextBox Grid.Column="0" Text="{Binding Name, UpdateSourceTrigger=PropertyChanged}"
|
|
Background="#0f3460" Foreground="#e0e0e0" BorderThickness="0"
|
|
Padding="4,2" VerticalContentAlignment="Center"
|
|
IsVisibleChanged="SourceNameBox_IsVisibleChanged"
|
|
KeyDown="SourceNameBox_KeyDown" LostFocus="SourceNameBox_LostFocus">
|
|
<TextBox.Style>
|
|
<Style TargetType="TextBox">
|
|
<Setter Property="Visibility" Value="Collapsed"/>
|
|
<Style.Triggers>
|
|
<DataTrigger Binding="{Binding IsEditing}" Value="True">
|
|
<Setter Property="Visibility" Value="Visible"/>
|
|
</DataTrigger>
|
|
</Style.Triggers>
|
|
</Style>
|
|
</TextBox.Style>
|
|
</TextBox>
|
|
|
|
<Button Grid.Column="1" ToolTip="Edit Source Name"
|
|
Command="{Binding DataContext.EditElementCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
|
|
CommandParameter="{Binding}" Style="{StaticResource IconButton}">
|
|
<Path Data="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"
|
|
Fill="#d0d0d0" Width="13" Height="13" Stretch="Uniform"/>
|
|
</Button>
|
|
|
|
<Button Grid.Column="2" Style="{StaticResource EyeButton}"
|
|
Command="{Binding DataContext.ToggleElementVisibilityCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
|
|
CommandParameter="{Binding}">
|
|
<Path Style="{StaticResource EyeIconStyle}" Fill="#d0d0d0"
|
|
Width="13" Height="13" Stretch="Uniform"/>
|
|
</Button>
|
|
|
|
<Button Grid.Column="3" ToolTip="Remove Source"
|
|
Command="{Binding DataContext.RemoveSourceCommand, RelativeSource={RelativeSource AncestorType=ListBox}}"
|
|
CommandParameter="{Binding}" Style="{StaticResource IconButton}"
|
|
Visibility="{Binding IsBackdrop, Converter={StaticResource HiddenBoolToVis}}">
|
|
<Path Data="M6,19c0,1.1 0.9,2 2,2h8c1.1,0 2,-0.9 2,-2V7H6v12zM19,4h-3.5l-1,-1h-5l-1,1H5v2h14V4z"
|
|
Fill="#d0d0d0" Width="13" Height="13" Stretch="Uniform"/>
|
|
</Button>
|
|
</Grid>
|
|
</DataTemplate>
|
|
</ListBox.ItemTemplate>
|
|
</Helpers:FocusPreservingListBox>
|
|
|
|
<TextBlock Grid.Row="4" Text="No sources yet" Foreground="#555" FontSize="12"
|
|
HorizontalAlignment="Center" VerticalAlignment="Center"
|
|
Visibility="{Binding ShowSourcesEmptyHint, Converter={StaticResource BoolToVis}}"/>
|
|
|
|
<!-- ELEMENTS panel: per-element config beneath Sources.
|
|
Collapses entirely when no element is selected. -->
|
|
<Rectangle Grid.Row="5" Height="1" Fill="#2c3a5c" Margin="0,10,0,2"
|
|
Visibility="{Binding SelectedElement, Converter={StaticResource NotNullToVis}}"/>
|
|
<StackPanel Grid.Row="6" Orientation="Horizontal" Margin="0,8,0,0"
|
|
Visibility="{Binding SelectedElement, Converter={StaticResource NotNullToVis}}">
|
|
<TextBlock Text="ELEMENTS" Style="{StaticResource SectionHeader}" VerticalAlignment="Center"/>
|
|
</StackPanel>
|
|
<ScrollViewer x:Name="ElementsScrollViewer" Grid.Row="7" VerticalScrollBarVisibility="Auto" Background="Transparent"
|
|
Visibility="{Binding SelectedElement, Converter={StaticResource NotNullToVis}}">
|
|
<StackPanel x:Name="ElementsPanel" Margin="0,4,0,0">
|
|
<!-- Opacity + mirror/shape (draggable elements: image, webcam) -->
|
|
<StackPanel Visibility="{Binding SelectedElement.IsDraggable, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
|
|
<TextBlock Text="Opacity" Foreground="#a0a0b0" FontSize="11" Margin="0,0,0,2"/>
|
|
<DockPanel Margin="0,0,0,6">
|
|
<TextBlock x:Name="OpacityValueText" Text="100%" Foreground="#a0a0b0" FontSize="11"
|
|
DockPanel.Dock="Right" VerticalAlignment="Center" Margin="8,0,0,0" Width="32"/>
|
|
<Slider x:Name="OpacitySlider" Width="120" Minimum="0" Maximum="1"
|
|
Value="{Binding SelectedElement.Opacity, Mode=TwoWay}"
|
|
ValueChanged="OpacitySlider_ValueChanged" VerticalAlignment="Center"/>
|
|
</DockPanel>
|
|
<StackPanel Orientation="Horizontal" Margin="0,0,0,4"
|
|
Visibility="{Binding IsWebcamSelected, Converter={StaticResource BoolToVis}}">
|
|
<Button Content="{Binding SelectedElement.MirrorButtonText}"
|
|
Style="{StaticResource YtButtonSecondary}" Padding="10,4"
|
|
Click="MirrorButton_Click"/>
|
|
<Button Content="{Binding SelectedElement.ShapeButtonText}"
|
|
Style="{StaticResource YtButtonSecondary}" Padding="10,4"
|
|
Margin="8,0,0,0" Click="ShapeButton_Click"/>
|
|
</StackPanel>
|
|
<TextBlock Text="Drag to move · drag the corner dot to resize"
|
|
Foreground="#a0a0b0" FontSize="10" Margin="0,2,0,0"/>
|
|
</StackPanel>
|
|
|
|
<!-- Webcam border config -->
|
|
<StackPanel Visibility="{Binding SelectedElement.IsWebcam, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
|
|
<TextBlock Text="Border Color" Foreground="#a0a0b0" FontSize="11" Margin="0,0,0,2"/>
|
|
<DockPanel Margin="0,0,0,6">
|
|
<Button DockPanel.Dock="Right" Content="..." Width="24" Height="24"
|
|
Click="WebcamBorderColorPicker_Click" Margin="4,0,0,0"
|
|
ToolTip="Pick color"/>
|
|
<TextBox Text="{Binding SelectedElement.BorderColor, UpdateSourceTrigger=PropertyChanged}"
|
|
Background="#0f3460" Foreground="#e0d0e0" BorderThickness="1"
|
|
BorderBrush="#2c3a5c" Padding="4,2" FontSize="12"
|
|
MaxLength="7" ToolTip="#RRGGBB"/>
|
|
</DockPanel>
|
|
<TextBlock Text="Border Thickness" Foreground="#a0a0b0" FontSize="11" Margin="0,0,0,2"/>
|
|
<DockPanel Margin="0,0,0,6">
|
|
<Button DockPanel.Dock="Right" Style="{StaticResource IconButton}"
|
|
Width="22" Height="22" Margin="4,0,0,0"
|
|
Click="WebcamBorderAccept_Click">
|
|
<Path Data="M4,9 L8,14 L16,4" Stroke="#22c55e" StrokeThickness="2"
|
|
Width="14" Height="14" Stretch="Uniform"/>
|
|
</Button>
|
|
<Button DockPanel.Dock="Right" Style="{StaticResource IconButton}"
|
|
Width="22" Height="22" ToolTip="Revert"
|
|
Click="WebcamBorderRevert_Click" Margin="4,0,0,0">
|
|
<Path Data="M5,5 L15,15 M15,5 L5,15" Stroke="#e94560" StrokeThickness="2"
|
|
Width="14" Height="14" Stretch="Uniform"/>
|
|
</Button>
|
|
<Slider DockPanel.Dock="Left" Width="120" Minimum="0" Maximum="10"
|
|
Value="{Binding SelectedElement.BorderWidth, Mode=TwoWay}"
|
|
VerticalAlignment="Center" TickFrequency="1" IsSnapToTickEnabled="True"/>
|
|
<TextBlock Text="{Binding SelectedElement.BorderWidth}" Foreground="#a0a0b0" FontSize="11"
|
|
VerticalAlignment="Center" Margin="8,0,0,0" Width="20"/>
|
|
</DockPanel>
|
|
</StackPanel>
|
|
|
|
<!-- Countdown config -->
|
|
<StackPanel Visibility="{Binding SelectedElement.Type, Converter={StaticResource SourceTypeToCountdownVis}, ConverterParameter=Countdown}" Margin="0,0,0,8">
|
|
<TextBlock Text="Timer (minutes)" Foreground="#a0a0b0" FontSize="11" Margin="0,0,0,2"/>
|
|
<DockPanel Margin="0,0,0,6">
|
|
<Button DockPanel.Dock="Right" Style="{StaticResource IconButton}"
|
|
Width="22" Height="22" Margin="4,0,0,0"
|
|
Click="CountdownAccept_Click">
|
|
<Path Data="M4,9 L8,14 L16,4" Stroke="#22c55e" StrokeThickness="2"
|
|
Width="14" Height="14" Stretch="Uniform"/>
|
|
</Button>
|
|
<Button DockPanel.Dock="Right" Style="{StaticResource IconButton}"
|
|
Width="22" Height="22" ToolTip="Revert"
|
|
Click="CountdownRevert_Click" Margin="4,0,0,0">
|
|
<Path Data="M5,5 L15,15 M15,5 L5,15" Stroke="#e94560" StrokeThickness="2"
|
|
Width="14" Height="14" Stretch="Uniform"/>
|
|
</Button>
|
|
<Slider DockPanel.Dock="Left" Width="120" Minimum="1" Maximum="60"
|
|
Value="{Binding SelectedElement.TimerMinutes, Mode=TwoWay}"
|
|
VerticalAlignment="Center" TickFrequency="1" IsSnapToTickEnabled="True"/>
|
|
<TextBlock Text="{Binding SelectedElement.TimerMinutes}" Foreground="#a0a0b0" FontSize="11"
|
|
VerticalAlignment="Center" Margin="8,0,0,0" Width="20"/>
|
|
</DockPanel>
|
|
</StackPanel>
|
|
|
|
<!-- Web source config -->
|
|
<StackPanel Visibility="{Binding SelectedElement.IsWebSource, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
|
|
<TextBlock Text="URI" Foreground="#a0a0b0" FontSize="11" Margin="0,0,0,2"/>
|
|
<DockPanel>
|
|
<Button DockPanel.Dock="Right" Style="{StaticResource IconButton}"
|
|
Width="22" Height="22" Margin="4,0,0,0"
|
|
Click="WebAccept_Click">
|
|
<Path Data="M4,9 L8,14 L16,4" Stroke="#22c55e" StrokeThickness="2"
|
|
Width="14" Height="14" Stretch="Uniform"/>
|
|
</Button>
|
|
<Button DockPanel.Dock="Right" Style="{StaticResource IconButton}"
|
|
Width="22" Height="22" ToolTip="Revert"
|
|
Click="WebRevert_Click" Margin="4,0,0,0">
|
|
<Path Data="M5,5 L15,15 M15,5 L5,15" Stroke="#e94560" StrokeThickness="2"
|
|
Width="14" Height="14" Stretch="Uniform"/>
|
|
</Button>
|
|
<TextBox Text="{Binding SelectedElement.WebUri, UpdateSourceTrigger=PropertyChanged}"
|
|
Background="#0f3460" Foreground="#e0d0e0" BorderThickness="1"
|
|
BorderBrush="#2c3a5c" Padding="4,2" FontSize="12"
|
|
ToolTip="https://example.com"/>
|
|
</DockPanel>
|
|
</StackPanel>
|
|
|
|
<!-- Placeholder hint when no config available -->
|
|
<TextBlock Text="No configurable elements" Foreground="#555" FontSize="11"
|
|
HorizontalAlignment="Center" Margin="0,8,0,0"
|
|
Visibility="{Binding IsElementsPanelEmpty, Converter={StaticResource BoolToVis}}"/>
|
|
</StackPanel>
|
|
</ScrollViewer>
|
|
</Grid>
|
|
</Border>
|
|
|
|
<!-- CENTER: Preview Area -->
|
|
<Border Grid.Column="1" Background="#0a0a1a" CornerRadius="6" Margin="8,0"
|
|
BorderBrush="{Binding PreviewGlowBrush}" BorderThickness="{Binding PreviewGlowThickness}">
|
|
<Grid>
|
|
<Grid.RowDefinitions>
|
|
<RowDefinition Height="Auto"/>
|
|
<RowDefinition Height="*"/>
|
|
</Grid.RowDefinitions>
|
|
|
|
<TextBlock Grid.Row="0" Text="{Binding ActiveScene.Name}"
|
|
Foreground="#e0e0e0" FontSize="15" FontWeight="SemiBold"
|
|
Margin="14,12,14,0"/>
|
|
|
|
<Grid x:Name="PreviewGrid" Grid.Row="1"
|
|
PreviewMouseLeftButtonDown="Preview_MouseLeftButtonDown"
|
|
PreviewMouseMove="Preview_MouseMove"
|
|
PreviewMouseLeftButtonUp="Preview_MouseLeftButtonUp">
|
|
<Viewbox Stretch="Uniform">
|
|
<Grid x:Name="CanvasGrid" Width="1920" Height="1080" ClipToBounds="True" Background="Transparent">
|
|
<Grid.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"
|
|
Visibility="{Binding BackdropVisible, 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 ActiveScene.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 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>
|
|
<Grid.RenderTransform>
|
|
<ScaleTransform ScaleX="{Binding MirrorScale}"/>
|
|
</Grid.RenderTransform>
|
|
<Image Stretch="UniformToFill"
|
|
Source="{Binding DisplaySource}"
|
|
RenderOptions.BitmapScalingMode="HighQuality">
|
|
<Image.Style>
|
|
<Style TargetType="Image">
|
|
<Setter Property="Visibility" Value="Collapsed"/>
|
|
<Style.Triggers>
|
|
<DataTrigger Binding="{Binding IsImageSource}" Value="True">
|
|
<Setter Property="Visibility" Value="Visible"/>
|
|
</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>
|
|
|
|
<!-- Static OBS-style border: stroked rect (Traditional) / centered circle (Round). -->
|
|
<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"/>
|
|
<Separator/>
|
|
<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>
|
|
<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 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 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>
|
|
<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}}"/>
|
|
|
|
<Grid x:Name="BrandFlashLayer" Width="1920" Height="1080" IsHitTestVisible="False" Opacity="0">
|
|
<Grid.Style>
|
|
<Style TargetType="Grid">
|
|
<Setter Property="Opacity" Value="0"/>
|
|
<Style.Triggers>
|
|
<DataTrigger Binding="{Binding BrandFlashActive}" Value="True">
|
|
<DataTrigger.EnterActions>
|
|
<BeginStoryboard>
|
|
<Storyboard>
|
|
<DoubleAnimation Storyboard.TargetProperty="Opacity"
|
|
From="0" To="0.25" Duration="0:0:0.25"/>
|
|
</Storyboard>
|
|
</BeginStoryboard>
|
|
</DataTrigger.EnterActions>
|
|
<DataTrigger.ExitActions>
|
|
<BeginStoryboard>
|
|
<Storyboard>
|
|
<DoubleAnimation Storyboard.TargetProperty="Opacity"
|
|
From="0.25" To="0" Duration="0:0:0.25"/>
|
|
</Storyboard>
|
|
</BeginStoryboard>
|
|
</DataTrigger.ExitActions>
|
|
</DataTrigger>
|
|
</Style.Triggers>
|
|
</Style>
|
|
</Grid.Style>
|
|
<Viewbox Stretch="Uniform" MaxWidth="1600" MaxHeight="800"
|
|
HorizontalAlignment="Center" VerticalAlignment="Center">
|
|
<TextBlock Text="made with ytLlive!" FontWeight="Bold" Foreground="White"
|
|
FontSize="180" TextAlignment="Center"/>
|
|
</Viewbox>
|
|
</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>
|
|
</Viewbox>
|
|
<Border HorizontalAlignment="Right" VerticalAlignment="Top" Margin="0,8,8,0"
|
|
Background="#CC16213e" CornerRadius="4" Padding="8,3" IsHitTestVisible="False">
|
|
<TextBlock Text="{Binding ResolutionBadgeText}" Foreground="#e0e0e0" FontSize="12"/>
|
|
</Border>
|
|
<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 sources — 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>
|
|
|
|
<!-- Desktop/game audio bar: overlaid at the bottom of the
|
|
preview, always visible. Meter + mute + volume control
|
|
live here; desktop audio itself is automatic WASAPI
|
|
loopback. Volume + mute are stream-honest AND scale the
|
|
TRAX music in the headphones, so the creator hears the
|
|
control work. -->
|
|
<Border HorizontalAlignment="Center" VerticalAlignment="Bottom"
|
|
Background="#D9000000" CornerRadius="6" Padding="12,8"
|
|
Margin="0,0,0,12">
|
|
<StackPanel Orientation="Horizontal">
|
|
<!-- TRAX sits left of the "Desktop Audio" label
|
|
(the creator's pick): it rides the desktop
|
|
channel, so the music control lives with the
|
|
desktop audio controls. -->
|
|
<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>
|
|
</Grid>
|
|
</Grid>
|
|
</Border>
|
|
|
|
<!-- RIGHT PANEL: Chat -->
|
|
<Border Grid.Column="2" Background="#16213e" CornerRadius="6" Padding="12">
|
|
<Grid>
|
|
<Grid.RowDefinitions>
|
|
<RowDefinition Height="Auto"/>
|
|
<RowDefinition Height="*"/>
|
|
</Grid.RowDefinitions>
|
|
|
|
<TextBlock Grid.Row="0" Text="LIVE CHAT" Style="{StaticResource SectionHeader}"/>
|
|
|
|
<StackPanel Grid.Row="1" HorizontalAlignment="Center" VerticalAlignment="Center"
|
|
Visibility="{Binding ShowChatInactiveMessage, Converter={StaticResource BoolToVis}}">
|
|
<TextBlock Text="Chat is inactive" Foreground="#e0e0e0" FontSize="18"
|
|
FontWeight="SemiBold" HorizontalAlignment="Center"/>
|
|
<TextBlock Text="Chat becomes active once ytLlive is connected and streaming on YouTube."
|
|
Foreground="#a0a0b0" FontSize="13" Margin="0,8,0,0"
|
|
TextWrapping="Wrap" MaxWidth="260" TextAlignment="Center"
|
|
HorizontalAlignment="Center"/>
|
|
</StackPanel>
|
|
|
|
<ListBox Grid.Row="1" Background="Transparent" BorderThickness="0"
|
|
ItemsSource="{Binding ChatMessages}"
|
|
ItemContainerStyle="{StaticResource YtListBoxItem}"
|
|
Visibility="{Binding ShowChatInactiveMessage, Converter={StaticResource InverseBoolToVis}}"
|
|
Foreground="#e0e0e0" FontSize="12">
|
|
<ListBox.ItemTemplate>
|
|
<DataTemplate>
|
|
<StackPanel Margin="0,2">
|
|
<TextBlock>
|
|
<Run Text="{Binding AuthorName, Mode=OneWay}" FontWeight="Bold" Foreground="#e94560"/>
|
|
<Run Text=": "/>
|
|
<Run Text="{Binding Message, Mode=OneWay}" Foreground="#e0e0e0"/>
|
|
</TextBlock>
|
|
</StackPanel>
|
|
</DataTemplate>
|
|
</ListBox.ItemTemplate>
|
|
</ListBox>
|
|
</Grid>
|
|
</Border>
|
|
</Grid>
|
|
|
|
<!-- ═══ BOTTOM BAR: Stream Health + Audio ═══ -->
|
|
<Border Grid.Row="3" Background="#0f3460" Padding="16,6">
|
|
<Grid>
|
|
<Grid.RowDefinitions>
|
|
<RowDefinition Height="Auto"/>
|
|
<RowDefinition Height="Auto"/>
|
|
</Grid.RowDefinitions>
|
|
|
|
<!-- Line 1: Social controls on the LEFT (under scenes/sources),
|
|
sound meter + volume control CENTERED beneath the preview panel.
|
|
Audio is KISS: desktop/game audio is automatic (WASAPI loopback);
|
|
the mic meter is the creator's only footer audio control (the
|
|
game audio bar lives INSIDE the preview window, overlaid at its
|
|
bottom edge while a full-screen game is up, and carries the
|
|
TRAX button left of its "Desktop Audio" label). -->
|
|
<Grid Grid.Row="0">
|
|
<Grid.ColumnDefinitions>
|
|
<ColumnDefinition Width="220"/>
|
|
<ColumnDefinition Width="*"/>
|
|
<ColumnDefinition Width="300"/>
|
|
</Grid.ColumnDefinitions>
|
|
|
|
<!-- Social controls: centered under the scenes/sources listboxes -->
|
|
<StackPanel Grid.Column="0" Orientation="Horizontal"
|
|
HorizontalAlignment="Center" VerticalAlignment="Center">
|
|
<Button Command="{Binding OpenSocialDialogCommand}"
|
|
Style="{StaticResource YtButtonSecondary}" Padding="12,4"
|
|
FontSize="11" VerticalAlignment="Center"
|
|
ToolTip="Add or manage your social handles">
|
|
<StackPanel Orientation="Horizontal">
|
|
<Ellipse Width="8" Height="8" Fill="{Binding SocialBarDotBrush}"
|
|
Margin="0,0,6,0" VerticalAlignment="Center"/>
|
|
<TextBlock Text="Socials"/>
|
|
</StackPanel>
|
|
</Button>
|
|
</StackPanel>
|
|
|
|
<!-- Mic meter + volume: centered under the preview panel -->
|
|
<StackPanel Grid.Column="1" Orientation="Horizontal"
|
|
HorizontalAlignment="Center" VerticalAlignment="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>
|
|
<!-- Sound meter: muted track with a ruler scale, zone tints at
|
|
the yellow (60%) and red (80%) starts. READ-ONLY realtime
|
|
level display (fill = live level scaled by volume). -->
|
|
<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"/>
|
|
<!-- Mic source name, left-justified inside the bar; the
|
|
semi-transparent fill lets it (and the markings) show through. -->
|
|
<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>
|
|
<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="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 MicMuted, 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 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>
|
|
</Grid>
|
|
|
|
<!-- Line 2: everything else — stream stats on the left, quality +
|
|
gear on the right. -->
|
|
<Grid Grid.Row="1" Margin="0,6,0,0">
|
|
<Grid.ColumnDefinitions>
|
|
<ColumnDefinition Width="Auto"/>
|
|
<ColumnDefinition Width="*"/>
|
|
<ColumnDefinition Width="Auto"/>
|
|
</Grid.ColumnDefinitions>
|
|
|
|
<StackPanel Grid.Column="0" Orientation="Horizontal" VerticalAlignment="Center">
|
|
<TextBlock Text="Bitrate:" Style="{StaticResource YtLabel}" Margin="0,0,4,0"/>
|
|
<TextBlock Style="{StaticResource YtLabel}"
|
|
Foreground="White" Margin="0,0,20,0">
|
|
<Run Text="{Binding CurrentHealth.CurrentBitrate, Mode=OneWay}"/>
|
|
<Run Text=" Mbps"/>
|
|
</TextBlock>
|
|
|
|
<TextBlock Text="FPS:" Style="{StaticResource YtLabel}" Margin="0,0,4,0"/>
|
|
<TextBlock Style="{StaticResource YtLabel}"
|
|
Foreground="White" Margin="0,0,24,0">
|
|
<Run Text="{Binding CurrentHealth.FPS, Mode=OneWay, StringFormat={}{0:0}}"/>
|
|
</TextBlock>
|
|
|
|
<TextBlock Text="Dropped:" Style="{StaticResource YtLabel}" Margin="0,0,4,0"/>
|
|
<TextBlock Text="{Binding CurrentHealth.DroppedFrames}" Style="{StaticResource YtLabel}"
|
|
Foreground="White" Margin="0,0,20,0"/>
|
|
|
|
<TextBlock Text="Duration:" Style="{StaticResource YtLabel}" Margin="0,0,4,0"/>
|
|
<TextBlock Text="{Binding CurrentHealth.StreamDuration}" Style="{StaticResource YtLabel}"
|
|
Foreground="White"/>
|
|
|
|
<TextBlock Text="{Binding CurrentHealth.HealthMessage}" Style="{StaticResource YtLabel}"
|
|
Foreground="#e94560" Margin="20,0,0,0"/>
|
|
</StackPanel>
|
|
|
|
<StackPanel Grid.Column="2" Orientation="Horizontal" VerticalAlignment="Center">
|
|
<ComboBox Style="{StaticResource YtComboBox}" MinWidth="150"
|
|
ItemsSource="{Binding QualityOptions}"
|
|
SelectedItem="{Binding SelectedQuality}"
|
|
DisplayMemberPath="Label"
|
|
ToolTip="{Binding ResolutionHelp}"
|
|
IsEnabled="{Binding IsOffline}"/>
|
|
<Button ToolTip="Menu" Style="{StaticResource IconButton}" Click="GearButton_Click">
|
|
<Button.ContextMenu>
|
|
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
|
|
<MenuItem Header="Save Layout" Command="{Binding SaveLayoutCommand}"/>
|
|
<MenuItem Header="Save Layout As…" Command="{Binding SaveLayoutAsCommand}"/>
|
|
<MenuItem Header="Open Layout…" Command="{Binding OpenLayoutCommand}"/>
|
|
<Separator/>
|
|
<MenuItem Header="App Settings" Command="{Binding OpenSettingsCommand}"/>
|
|
<MenuItem Header="Report Bug" Command="{Binding OpenBugCommand}"/>
|
|
<MenuItem Header="Feature Request" Command="{Binding OpenFeatureCommand}"/>
|
|
<MenuItem Header="About" Command="{Binding OpenAboutCommand}"/>
|
|
</ContextMenu>
|
|
</Button.ContextMenu>
|
|
<Path Data="M19.14,12.94c0.04,-0.3 0.06,-0.61 0.06,-0.94c0,-0.32 -0.02,-0.64 -0.07,-0.94l2.03,-1.58c0.18,-0.14 0.23,-0.41 0.12,-0.61l-1.92,-3.32c-0.12,-0.22 -0.37,-0.29 -0.59,-0.22l-2.39,0.96c-0.5,-0.38 -1.03,-0.7 -1.62,-0.94L14.4,2.81c-0.04,-0.24 -0.24,-0.41 -0.48,-0.41h-3.84c-0.24,0 -0.43,0.17 -0.47,0.41L9.25,5.35C8.66,5.59 8.12,5.92 7.63,6.29L5.24,5.33c-0.22,-0.08 -0.47,0 -0.59,0.22L2.74,8.87C2.62,9.08 2.66,9.34 2.86,9.48l2.03,1.58C4.84,11.36 4.8,11.69 4.8,12s0.02,0.64 0.07,0.94l-2.03,1.58c-0.18,0.14 -0.23,0.41 -0.12,0.61l1.92,3.32c0.12,0.22 0.37,0.29 0.59,0.22l2.39,-0.96c0.5,0.38 1.03,0.7 1.62,0.94l0.36,2.54c0.05,0.24 0.24,0.41 0.48,0.41h3.84c0.24,0 0.44,-0.17 0.47,-0.41l0.36,-2.54c0.59,-0.24 1.13,-0.56 1.62,-0.94l2.39,0.96c0.22,0.08 0.47,0 0.59,-0.22l1.92,-3.32c0.12,-0.22 0.07,-0.47 -0.12,-0.61L19.14,12.94zM12,15.6c-1.98,0 -3.6,-1.62 -3.6,-3.6s1.62,-3.6 3.6,-3.6s3.6,1.62 3.6,3.6S13.98,15.6 12,15.6z"
|
|
Fill="#d0d0d0" Width="16" Height="16" Stretch="Uniform"/>
|
|
</Button>
|
|
</StackPanel>
|
|
</Grid>
|
|
</Grid>
|
|
</Border>
|
|
|
|
<!-- ═══ OVERLAY: Settings / Bug / Feature / About ═══ -->
|
|
<Grid Grid.RowSpan="3"
|
|
Visibility="{Binding IsAnyOverlayOpen, Converter={StaticResource BoolToVis}}">
|
|
<Button Background="#99000000" BorderThickness="0" Cursor="Hand"
|
|
Command="{Binding CloseOverlayCommand}">
|
|
<Button.Template>
|
|
<ControlTemplate TargetType="Button">
|
|
<Border Background="{TemplateBinding Background}"/>
|
|
</ControlTemplate>
|
|
</Button.Template>
|
|
</Button>
|
|
|
|
<Border Background="#0A0C1A" CornerRadius="8" Width="520" MaxHeight="560"
|
|
VerticalAlignment="Center" HorizontalAlignment="Center"
|
|
Padding="24" BorderBrush="#333" BorderThickness="1">
|
|
<Grid>
|
|
<Grid.RowDefinitions>
|
|
<RowDefinition Height="Auto"/>
|
|
<RowDefinition Height="Auto"/>
|
|
</Grid.RowDefinitions>
|
|
|
|
<!-- Header -->
|
|
<Grid Grid.Row="0">
|
|
<TextBlock Text="{Binding OverlayTitle}" FontSize="18" FontWeight="Bold"
|
|
Foreground="#e0e0e0" VerticalAlignment="Center"/>
|
|
<Button HorizontalAlignment="Right" ToolTip="Close" Style="{StaticResource IconButton}"
|
|
Command="{Binding CloseOverlayCommand}">
|
|
<Path Data="M19,6.41L17.59,5 12,10.59 6.41,5 5,6.41 10.59,12 5,17.59 6.41,19 12,13.41 17.59,19 19,17.59 13.41,12z"
|
|
Fill="#d0d0d0" Width="14" Height="14" Stretch="Uniform"/>
|
|
</Button>
|
|
</Grid>
|
|
|
|
<!-- App Settings -->
|
|
<StackPanel Grid.Row="1" Margin="0,16,0,0"
|
|
Visibility="{Binding IsSettingsOpen, Converter={StaticResource BoolToVis}}">
|
|
<TextBlock Text="Default Stream Title" Style="{StaticResource YtLabel}" Margin="0,0,0,4"/>
|
|
<TextBox Style="{StaticResource YtTextBox}" Margin="0,0,0,12"
|
|
Text="{Binding DefaultStreamTitle, UpdateSourceTrigger=PropertyChanged}"/>
|
|
|
|
<TextBlock Text="Default Stream Description" Style="{StaticResource YtLabel}" Margin="0,0,0,4"/>
|
|
<TextBox Style="{StaticResource YtTextBox}" Height="70" AcceptsReturn="True" TextWrapping="Wrap"
|
|
Margin="0,0,0,12"
|
|
Text="{Binding DefaultStreamDescription, UpdateSourceTrigger=PropertyChanged}"
|
|
VerticalScrollBarVisibility="Auto"/>
|
|
|
|
<TextBlock Text="These prefill the Start Stream dialog. Streams always start Private — change visibility on YouTube after going live."
|
|
Style="{StaticResource YtLabel}" Margin="0,16,0,0" TextWrapping="Wrap"/>
|
|
</StackPanel>
|
|
|
|
<!-- Report Bug -->
|
|
<StackPanel Grid.Row="1" Margin="0,16,0,0"
|
|
Visibility="{Binding IsBugOpen, Converter={StaticResource BoolToVis}}">
|
|
<TextBlock Text="What went wrong?" Style="{StaticResource YtLabel}" Margin="0,0,0,4"/>
|
|
<TextBox Style="{StaticResource YtTextBox}" Height="110" AcceptsReturn="True" TextWrapping="Wrap"
|
|
Margin="0,0,0,12"
|
|
Text="{Binding BugReportText, UpdateSourceTrigger=PropertyChanged}"
|
|
VerticalScrollBarVisibility="Auto"/>
|
|
|
|
<TextBlock Text="Email (optional)" Style="{StaticResource YtLabel}" Margin="0,0,0,4"/>
|
|
<TextBox Style="{StaticResource YtTextBox}" Margin="0,0,0,12"
|
|
Text="{Binding BugReportEmail, UpdateSourceTrigger=PropertyChanged}"/>
|
|
|
|
<TextBlock Text="Opens a pre-filled email to the ytLlive team." Style="{StaticResource YtLabel}"/>
|
|
<Button Content="Send Report" Command="{Binding SubmitBugCommand}"
|
|
Style="{StaticResource YtButton}" HorizontalAlignment="Right" Margin="0,16,0,0"/>
|
|
</StackPanel>
|
|
|
|
<!-- Feature Request -->
|
|
<StackPanel Grid.Row="1" Margin="0,16,0,0"
|
|
Visibility="{Binding IsFeatureOpen, Converter={StaticResource BoolToVis}}">
|
|
<TextBlock Text="What would you like to see?" Style="{StaticResource YtLabel}" Margin="0,0,0,4"/>
|
|
<TextBox Style="{StaticResource YtTextBox}" Height="110" AcceptsReturn="True" TextWrapping="Wrap"
|
|
Margin="0,0,0,12"
|
|
Text="{Binding FeatureRequestText, UpdateSourceTrigger=PropertyChanged}"
|
|
VerticalScrollBarVisibility="Auto"/>
|
|
|
|
<TextBlock Text="Email (optional)" Style="{StaticResource YtLabel}" Margin="0,0,0,4"/>
|
|
<TextBox Style="{StaticResource YtTextBox}" Margin="0,0,0,12"
|
|
Text="{Binding FeatureRequestEmail, UpdateSourceTrigger=PropertyChanged}"/>
|
|
|
|
<TextBlock Text="Opens a pre-filled email to the ytLlive team." Style="{StaticResource YtLabel}"/>
|
|
<Button Content="Send Request" Command="{Binding SubmitFeatureCommand}"
|
|
Style="{StaticResource YtButton}" HorizontalAlignment="Right" Margin="0,16,0,0"/>
|
|
</StackPanel>
|
|
|
|
<!-- About -->
|
|
<Grid Grid.Row="1" Margin="0,16,0,0"
|
|
Visibility="{Binding IsAboutOpen, Converter={StaticResource BoolToVis}}">
|
|
|
|
<!-- Hub view -->
|
|
<StackPanel Visibility="{Binding IsLicensesOpen, Converter={StaticResource InverseBoolToVis}}">
|
|
<Image Source="/ytLive;component/Assets/ytLlive-logo.png" Height="100" Stretch="Uniform"
|
|
HorizontalAlignment="Center" Margin="0,0,0,12"/>
|
|
<TextBlock Text="ytLlive" FontSize="22" FontWeight="Bold" Foreground="#e94560"
|
|
HorizontalAlignment="Center"/>
|
|
<TextBlock Text="{Binding AppVersionLabel}" Foreground="#a0a0b0" FontSize="12"
|
|
HorizontalAlignment="Center" Margin="0,4,0,0"/>
|
|
<TextBlock Text="Live streaming for YouTube — simple enough that even the most right-brained person can intuit it."
|
|
Foreground="#a0a0b0" FontSize="13" TextWrapping="Wrap" MaxWidth="380"
|
|
TextAlignment="Center" Margin="0,16,0,0"/>
|
|
<TextBlock Text="Free forever — live streams carry a brief "made with ytLlive!" flash. A one-time unlock removes it and adds alerts."
|
|
Foreground="#888" FontSize="12" TextWrapping="Wrap" MaxWidth="380"
|
|
TextAlignment="Center" Margin="0,12,0,0"/>
|
|
<TextBlock Text="Made by gramps · llama chile shop" Foreground="#a0a0b0" FontSize="12"
|
|
HorizontalAlignment="Center" Margin="0,16,0,8"/>
|
|
|
|
<!-- Creator hub links -->
|
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,8">
|
|
<Button Content="llama chile shop on YouTube" Command="{Binding OpenChannelCommand}"
|
|
Style="{StaticResource YtButtonSecondary}" Margin="0,0,8,0"/>
|
|
<Button Content="Mastodon" Command="{Binding OpenMastodonCommand}"
|
|
Style="{StaticResource YtButtonSecondary}"/>
|
|
</StackPanel>
|
|
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center">
|
|
<Button Content="Buy me a coffee" Command="{Binding OpenCoffeeCommand}"
|
|
Style="{StaticResource YtButtonSecondary}" Margin="0,0,8,0"/>
|
|
<Button Content="Unlock Premium" Command="{Binding OpenPremiumCommand}"
|
|
Style="{StaticResource YtButtonSecondary}"
|
|
IsEnabled="{Binding IsPremiumAvailable}"
|
|
ToolTip="Coming soon — subscription billing is on the way (TASK 23)"/>
|
|
</StackPanel>
|
|
|
|
<Button Content="Licenses & legal" Command="{Binding OpenLicensesCommand}"
|
|
Style="{StaticResource YtButtonSecondary}" HorizontalAlignment="Center"
|
|
Margin="0,16,0,0"/>
|
|
</StackPanel>
|
|
|
|
<!-- Licensing/legal panel -->
|
|
<StackPanel Visibility="{Binding IsLicensesOpen, Converter={StaticResource BoolToVis}}">
|
|
<TextBlock Text="Third-party notices & license texts" FontSize="15" FontWeight="Bold"
|
|
Foreground="#e0e0e0" Margin="0,0,0,8"/>
|
|
<Border Background="#10101e" CornerRadius="4" Padding="10" MaxHeight="360"
|
|
BorderBrush="#333" BorderThickness="1">
|
|
<ScrollViewer VerticalScrollBarVisibility="Auto">
|
|
<TextBlock Text="{Binding LicensesText}" Foreground="#b8b8c8" FontSize="11"
|
|
FontFamily="Consolas" TextWrapping="Wrap"/>
|
|
</ScrollViewer>
|
|
</Border>
|
|
<Button Content="← Back to About" Style="{StaticResource YtButtonSecondary}"
|
|
HorizontalAlignment="Left" Margin="0,12,0,0"
|
|
Click="BackToAbout_Click"/>
|
|
</StackPanel>
|
|
</Grid>
|
|
</Grid>
|
|
</Border>
|
|
</Grid>
|
|
</Grid>
|
|
</Window>
|