Right rail, top bar, bottom bar and chat border: Socials slide-out drawer, tab icons and colors, EDIT toggle, stream readouts moved to the top-left cell, webcam border restored, chat border controls, 30fps vertical tier

- Socials: the dialog is a right-rail drawer (same pattern as Stream Info/YPP/TEST); the live-screen button is removed
- Right rail: Stream Info (was Stream Settings) and YPP show the YouTube logo; Socials and TEST get icons; tab colors and label colors per spec
- Top bar: EDIT toggle beside REC (orange when on, blue when off); going live on YouTube turns it off
- Stream readouts (bitrate, FPS, dropped n (x.y%), Live hh:mm:ss, health) and the resolution dropdown moved to the top-left cell; bottom bar emptied at the same height
- Webcam border drawing restored (it was removed with the CRT bezel change)
- YouTube chat layer: border color, thickness, and opacity controls
- Vertical 1080p30 tier added

Not related to the fps gate yet; no gating is enforced.
This commit is contained in:
2026-10-26 18:24:09 -07:00
parent a8814b42d0
commit 7ae30d4cfc
13 changed files with 341 additions and 102 deletions
+3 -41
View File
@@ -3,46 +3,8 @@
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"> xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
<Border Background="#0f3460" Padding="16,4"> <Border Background="#0f3460" Padding="16,4">
<Grid> <Grid>
<Grid.ColumnDefinitions> <!-- Intentionally empty. The hidden label keeps the bar's height unchanged. -->
<ColumnDefinition Width="Auto"/> <TextBlock Style="{StaticResource YtLabel}" Text=" " Visibility="Hidden"/>
<ColumnDefinition Width="*"/>
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- Stream stats: centered under the preview pane -->
<StackPanel Grid.Column="1" Orientation="Horizontal" HorizontalAlignment="Center" 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>
<!-- Resolution dropdown: far right -->
<ComboBox Grid.Column="2" Style="{StaticResource YtComboBox}" MinWidth="150"
ItemsSource="{Binding QualityOptions}"
SelectedItem="{Binding SelectedQuality}"
DisplayMemberPath="Label"
ToolTip="{Binding ResolutionHelp}"
IsEnabled="{Binding IsOffline}"/>
</Grid> </Grid>
</Border> </Border>
</UserControl> </UserControl>
+32
View File
@@ -514,6 +514,38 @@
<StackPanel Visibility="{Binding SelectedElement.IsChatBox, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8"> <StackPanel Visibility="{Binding SelectedElement.IsChatBox, Converter={StaticResource BoolToVis}}" Margin="0,0,0,8">
<TextBlock Text="Chat Overlay" Foreground="#a0a0b0" FontSize="11" FontWeight="SemiBold" Margin="0,0,0,6"/> <TextBlock Text="Chat Overlay" Foreground="#a0a0b0" FontSize="11" FontWeight="SemiBold" Margin="0,0,0,6"/>
<!-- Border (same controls as the webcam's border) -->
<TextBlock Text="Border Color" Foreground="#a0a0b0" FontSize="11" FontWeight="SemiBold" Margin="0,0,0,2"/>
<DockPanel Margin="10,0,0,6">
<TextBox DockPanel.Dock="Left"
Text="{Binding SelectedElement.BorderColor, UpdateSourceTrigger=PropertyChanged}"
Style="{StaticResource YtTextBoxCompact}"
MaxLength="7" ToolTip="#RRGGBB" Width="60"
VerticalAlignment="Center"/>
<ComboBox ItemsSource="{Binding SelectedElement.BorderColorOptions}"
SelectedItem="{Binding SelectedElement.BorderColorEntry, Mode=TwoWay}"
ItemTemplateSelector="{StaticResource BorderColorTemplates}"
Style="{StaticResource YtComboBox}"
ToolTip="Border colour" MinWidth="100"
Margin="8,0,0,0" VerticalAlignment="Center"/>
</DockPanel>
<TextBlock Text="Border Thickness" Foreground="#a0a0b0" FontSize="11" FontWeight="SemiBold" Margin="0,0,0,2"/>
<DockPanel Margin="10,0,0,6">
<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>
<TextBlock Text="Border Opacity" Foreground="#a0a0b0" FontSize="11" FontWeight="SemiBold" Margin="0,0,0,2"/>
<DockPanel Margin="10,0,0,8">
<Slider DockPanel.Dock="Left" Width="120" Minimum="0" Maximum="1"
Value="{Binding SelectedElement.BorderOpacity, Mode=TwoWay}"
VerticalAlignment="Center"/>
<TextBlock Text="{Binding SelectedElement.BorderOpacity, StringFormat={}{0:P0}}" Foreground="#a0a0b0" FontSize="11"
VerticalAlignment="Center" Margin="8,0,0,0" Width="34"/>
</DockPanel>
<!-- Font Size --> <!-- Font Size -->
<DockPanel Margin="0,0,0,4"> <DockPanel Margin="0,0,0,4">
<TextBlock DockPanel.Dock="Left" Text="Font Size" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" Width="70"/> <TextBlock DockPanel.Dock="Left" Text="Font Size" Foreground="#a0a0b0" FontSize="11" VerticalAlignment="Center" Width="70"/>
+150 -23
View File
@@ -1,7 +1,8 @@
<UserControl x:Class="ytLive.Controls.PreviewPane" <UserControl x:Class="ytLive.Controls.PreviewPane"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Helpers="clr-namespace:ytLive.Helpers"> xmlns:Helpers="clr-namespace:ytLive.Helpers"
xmlns:local="clr-namespace:ytLive">
<Border Background="#0a0a1a" CornerRadius="6" Margin="8,0" <Border Background="#0a0a1a" CornerRadius="6" Margin="8,0"
BorderBrush="{Binding PreviewGlowBrush}" BorderThickness="{Binding PreviewGlowThickness}"> BorderBrush="{Binding PreviewGlowBrush}" BorderThickness="{Binding PreviewGlowThickness}">
<Grid> <Grid>
@@ -181,6 +182,50 @@
</Viewbox> </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> <Grid.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}"> <ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Change Webcam…" Click="WebcamMenu_ChangeWebcam" <MenuItem Header="Change Webcam…" Click="WebcamMenu_ChangeWebcam"
@@ -267,8 +312,8 @@
<Grid x:Name="SelectionOverlay" IsHitTestVisible="False" <Grid x:Name="SelectionOverlay" IsHitTestVisible="False"
Canvas.Left="{Binding SelectedElement.X}" Canvas.Top="{Binding SelectedElement.Y}" Canvas.Left="{Binding SelectedElement.X}" Canvas.Top="{Binding SelectedElement.Y}"
Width="{Binding SelectedElement.Width}" Height="{Binding SelectedElement.Height}"> Width="{Binding SelectedElement.Width}" Height="{Binding SelectedElement.Height}">
<Rectangle Stroke="#e94560" StrokeDashArray="4 3" StrokeThickness="3"/> <Rectangle Stroke="#ff8c3a" StrokeDashArray="4 3" StrokeThickness="5"/>
<Ellipse Width="26" Height="26" Fill="#e94560" Stroke="White" StrokeThickness="2" <Ellipse Width="26" Height="26" Fill="#ff8c3a" Stroke="White" StrokeThickness="2"
HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,-13,-13"/> HorizontalAlignment="Right" VerticalAlignment="Bottom" Margin="0,0,-13,-13"/>
</Grid> </Grid>
@@ -516,6 +561,63 @@
</StackPanel> </StackPanel>
</Grid> </Grid>
</Border> </Border>
<!-- The Socials drawer (same pattern as the other pull-outs) -->
<Border Background="#16213e" 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="#a0a0b0" 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) --> <!-- The YPP-progress drawer (below Stream Settings on the rail) -->
<Border Background="#16213e" ClipToBounds="True"> <Border Background="#16213e" ClipToBounds="True">
<Border.Style> <Border.Style>
@@ -750,47 +852,86 @@
</Border> </Border>
<!-- The stacked white tabs --> <!-- The stacked white tabs -->
<StackPanel Orientation="Vertical" HorizontalAlignment="Right" VerticalAlignment="Stretch"> <StackPanel Orientation="Vertical" HorizontalAlignment="Right" VerticalAlignment="Stretch">
<Border Width="30" VerticalAlignment="Center" Background="#ffffff" <Border Width="30" VerticalAlignment="Center" Background="#ff5451"
CornerRadius="4,0,0,4" Padding="0,10" Cursor="Hand" Margin="0,4,0,0" CornerRadius="4,0,0,4" Padding="0,10" Cursor="Hand" Margin="0,4,0,0"
ToolTip="Broadcast text fields"> ToolTip="Broadcast text fields">
<Border.InputBindings> <Border.InputBindings>
<MouseBinding Gesture="LeftClick" <MouseBinding Gesture="LeftClick"
Command="{Binding BroadcastForm.ToggleDrawerCommand}"/> Command="{Binding BroadcastForm.ToggleDrawerCommand}"/>
</Border.InputBindings> </Border.InputBindings>
<TextBlock Text="Stream Settings" Foreground="#e94560" FontWeight="Bold" FontSize="15" <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="#ff0000" Width="6" Height="7"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<TextBlock Text="Stream Info" Foreground="White" FontWeight="Bold" FontSize="15"
HorizontalAlignment="Center"> HorizontalAlignment="Center">
<TextBlock.LayoutTransform> <TextBlock.LayoutTransform>
<RotateTransform Angle="90"/> <RotateTransform Angle="90"/>
</TextBlock.LayoutTransform> </TextBlock.LayoutTransform>
</TextBlock> </TextBlock>
</StackPanel>
</Border> </Border>
<Border Width="30" VerticalAlignment="Center" Background="#ffffff" <Border Width="30" VerticalAlignment="Center" Background="#ff5451"
CornerRadius="4,0,0,4" Padding="0,10" Cursor="Hand" Margin="0,4,0,0" CornerRadius="4,0,0,4" Padding="0,10" Cursor="Hand" Margin="0,4,0,0"
ToolTip="YouTube Partner Program progress"> ToolTip="YouTube Partner Program progress">
<Border.InputBindings> <Border.InputBindings>
<MouseBinding Gesture="LeftClick" <MouseBinding Gesture="LeftClick"
Command="{Binding Ypp.ToggleDrawerCommand}"/> Command="{Binding Ypp.ToggleDrawerCommand}"/>
</Border.InputBindings> </Border.InputBindings>
<TextBlock Text="YPP" Foreground="#e94560" FontWeight="Bold" FontSize="15" <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="#ff0000" Width="6" Height="7"
HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<TextBlock Text="YPP" Foreground="White" FontWeight="Bold" FontSize="15"
HorizontalAlignment="Center"> HorizontalAlignment="Center">
<TextBlock.LayoutTransform> <TextBlock.LayoutTransform>
<RotateTransform Angle="90"/> <RotateTransform Angle="90"/>
</TextBlock.LayoutTransform> </TextBlock.LayoutTransform>
</TextBlock> </TextBlock>
</StackPanel>
</Border> </Border>
<Border Width="30" VerticalAlignment="Center" Background="#ffffff" <Border Width="30" VerticalAlignment="Center" Background="#3b82f6"
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="#0b1326" 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="#0b1326" FontWeight="Bold" FontSize="15"
HorizontalAlignment="Center">
<TextBlock.LayoutTransform>
<RotateTransform Angle="90"/>
</TextBlock.LayoutTransform>
</TextBlock>
</StackPanel>
</Border>
<Border Width="30" VerticalAlignment="Center" Background="#ff8c3a"
CornerRadius="4,0,0,4" Padding="0,10" Cursor="Hand" Margin="0,4,0,0" CornerRadius="4,0,0,4" Padding="0,10" Cursor="Hand" Margin="0,4,0,0"
ToolTip="Test stream — mock chat input and simulated events"> ToolTip="Test stream — mock chat input and simulated events">
<Border.InputBindings> <Border.InputBindings>
<MouseBinding Gesture="LeftClick" <MouseBinding Gesture="LeftClick"
Command="{Binding TestSession.ToggleDrawerCommand}"/> Command="{Binding TestSession.ToggleDrawerCommand}"/>
</Border.InputBindings> </Border.InputBindings>
<TextBlock Text="TEST" Foreground="#9c6f1c" FontWeight="Bold" FontSize="15" <StackPanel HorizontalAlignment="Center">
<Path Data="M8,2 H16 M9,2 V8 L4,18 H20 L15,8 V2 M7,14 H17"
Stroke="#0b1326" 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="#0b1326" FontWeight="Bold" FontSize="15"
HorizontalAlignment="Center"> HorizontalAlignment="Center">
<TextBlock.LayoutTransform> <TextBlock.LayoutTransform>
<RotateTransform Angle="90"/> <RotateTransform Angle="90"/>
</TextBlock.LayoutTransform> </TextBlock.LayoutTransform>
</TextBlock> </TextBlock>
</StackPanel>
</Border> </Border>
</StackPanel> </StackPanel>
</StackPanel> </StackPanel>
@@ -803,20 +944,6 @@
VerticalAlignment="Center" Margin="0,4"> VerticalAlignment="Center" Margin="0,4">
<!-- Line 1: Socials + TRAX --> <!-- Line 1: Socials + TRAX -->
<StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,4"> <StackPanel Orientation="Horizontal" HorizontalAlignment="Center" Margin="0,0,0,4">
<!-- Socials button: hover glows the social bar in the preview -->
<Button x:Name="SocialsButton"
Command="{Binding OpenSocialDialogCommand}"
Style="{StaticResource YtButtonSecondary}" Padding="12,4"
FontSize="11" VerticalAlignment="Center" Margin="0,0,12,0"
ToolTip="Add or manage your social handles"
MouseEnter="SocialsButton_MouseEnter"
MouseLeave="SocialsButton_MouseLeave">
<StackPanel Orientation="Horizontal">
<Ellipse Width="8" Height="8" Fill="{Binding SocialBarDotBrush}"
Margin="0,0,6,0" VerticalAlignment="Center"/>
<TextBlock Text="Socials"/>
</StackPanel>
</Button>
<!-- TRAX meter --> <!-- TRAX meter -->
<Border Background="#D9000000" CornerRadius="6" Padding="12,8"> <Border Background="#D9000000" CornerRadius="6" Padding="12,8">
<StackPanel Orientation="Horizontal"> <StackPanel Orientation="Horizontal">
+4 -6
View File
@@ -62,12 +62,10 @@
</StackPanel> </StackPanel>
</Border> </Border>
<TextBlock Text="{Binding HotkeyLabel, Mode=OneWay}" Foreground="White" FontSize="16" FontWeight="SemiBold" <Border HorizontalAlignment="Right" VerticalAlignment="Top" Margin="6" Padding="8,2"
HorizontalAlignment="Right" VerticalAlignment="Top" Margin="6"> CornerRadius="10" Background="#000000">
<TextBlock.Effect> <TextBlock Text="{Binding HotkeyLabel, Mode=OneWay}" Foreground="White" FontSize="16" FontWeight="SemiBold"/>
<DropShadowEffect BlurRadius="4" ShadowDepth="0" Opacity="0.9"/> </Border>
</TextBlock.Effect>
</TextBlock>
<TextBlock Text="{Binding Name, Mode=OneWay}" Foreground="White" FontSize="16" FontWeight="SemiBold" <TextBlock Text="{Binding Name, Mode=OneWay}" Foreground="White" FontSize="16" FontWeight="SemiBold"
HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="6" HorizontalAlignment="Left" VerticalAlignment="Bottom" Margin="6"
+10 -8
View File
@@ -58,16 +58,18 @@
<ToggleButton IsChecked="{Binding EditingActive, Mode=TwoWay}" Content="EDIT" <ToggleButton IsChecked="{Binding EditingActive, Mode=TwoWay}" Content="EDIT"
Cursor="Hand" Height="32" Padding="14,0" Margin="0,0,24,0" VerticalAlignment="Center" Cursor="Hand" Height="32" Padding="14,0" Margin="0,0,24,0" VerticalAlignment="Center"
FontWeight="Bold" FontSize="12" Foreground="#0b1326" BorderThickness="0"> FontWeight="Bold" FontSize="12" Foreground="#0b1326" BorderThickness="0">
<ToggleButton.Style> <ToggleButton.Template>
<Style TargetType="ToggleButton"> <ControlTemplate TargetType="ToggleButton">
<Setter Property="Background" Value="#38bdf8"/> <Border x:Name="EditBody" Background="#38bdf8" CornerRadius="4" Padding="{TemplateBinding Padding}">
<Style.Triggers> <ContentPresenter HorizontalAlignment="Center" VerticalAlignment="Center"/>
</Border>
<ControlTemplate.Triggers>
<Trigger Property="IsChecked" Value="True"> <Trigger Property="IsChecked" Value="True">
<Setter Property="Background" Value="#ff8c3a"/> <Setter TargetName="EditBody" Property="Background" Value="#ff8c3a"/>
</Trigger> </Trigger>
</Style.Triggers> </ControlTemplate.Triggers>
</Style> </ControlTemplate>
</ToggleButton.Style> </ToggleButton.Template>
</ToggleButton> </ToggleButton>
<!-- IDLE: the mode switch + the action(s) of the armed world. The three <!-- IDLE: the mode switch + the action(s) of the armed world. The three
controls share one height (build 77 feedback) so they read as one controls share one height (build 77 feedback) so they read as one
+23
View File
@@ -0,0 +1,23 @@
using System.Globalization;
using System.Windows.Data;
using System.Windows.Media;
namespace ytLive.Helpers;
public class DroppedFramesToBrushConverter : IValueConverter
{
private static readonly SolidColorBrush Attention = new(Color.FromRgb(0xff, 0x8c, 0x3a));
private static readonly SolidColorBrush Clean = new(Colors.White);
static DroppedFramesToBrushConverter()
{
Attention.Freeze();
Clean.Freeze();
}
public object Convert(object value, Type targetType, object parameter, CultureInfo culture)
=> value is int dropped && dropped > 0 ? Attention : Clean;
public object ConvertBack(object value, Type targetType, object parameter, CultureInfo culture)
=> throw new NotSupportedException();
}
+20
View File
@@ -0,0 +1,20 @@
using System.Globalization;
using System.Windows.Data;
namespace ytLive.Helpers;
public class DroppedPercentConverter : IMultiValueConverter
{
public object Convert(object[] values, Type targetType, object parameter, CultureInfo culture)
{
var dropped = values.Length > 0 && values[0] is int d ? d : 0;
var fps = values.Length > 1 && values[1] is double f ? f : 0;
var duration = values.Length > 2 && values[2] is TimeSpan t ? t : TimeSpan.Zero;
var total = fps * duration.TotalSeconds;
var percent = total > 0 ? dropped / total * 100 : 0;
return $"{dropped} ({percent.ToString("0.0", CultureInfo.InvariantCulture)}%)";
}
public object[] ConvertBack(object value, Type[] targetTypes, object parameter, CultureInfo culture)
=> throw new NotSupportedException();
}
+55
View File
@@ -31,6 +31,8 @@
<Window.Resources> <Window.Resources>
<ResourceDictionary> <ResourceDictionary>
<Helpers:DroppedFramesToBrushConverter x:Key="DroppedToBrush"/>
<Helpers:DroppedPercentConverter x:Key="DroppedPercent"/>
<!-- Red dot shown in the taskbar icon while live --> <!-- Red dot shown in the taskbar icon while live -->
<DrawingImage x:Key="LiveOverlay"> <DrawingImage x:Key="LiveOverlay">
@@ -78,6 +80,59 @@
<ColumnDefinition Width="3*"/> <ColumnDefinition Width="3*"/>
</Grid.ColumnDefinitions> </Grid.ColumnDefinitions>
<!-- STREAM READOUTS (top-left cell, above the layers) + resolution -->
<Border Grid.Row="0" Grid.Column="0" Background="#0d1117" CornerRadius="4" Padding="10,8" Margin="0,0,8,0">
<Grid>
<Grid.RowDefinitions>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/>
<RowDefinition Height="*"/>
</Grid.RowDefinitions>
<Grid.ColumnDefinitions>
<ColumnDefinition Width="Auto"/>
<ColumnDefinition Width="*"/>
</Grid.ColumnDefinitions>
<TextBlock Grid.Row="0" Grid.Column="0" Text="Bitrate" Foreground="#94a3b8" FontSize="12" Margin="0,0,12,2"/>
<TextBlock Grid.Row="0" Grid.Column="1" Foreground="#dae2fd" FontSize="13" HorizontalAlignment="Right" Margin="0,0,0,2">
<Run Text="{Binding CurrentHealth.CurrentBitrate, Mode=OneWay, StringFormat={}{0:0.0}}"/><Run Text=" Mbps"/>
</TextBlock>
<TextBlock Grid.Row="1" Grid.Column="0" Text="FPS" Foreground="#94a3b8" FontSize="12" Margin="0,0,12,2"/>
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding CurrentHealth.FPS, Mode=OneWay, StringFormat={}{0:0}}"
Foreground="#dae2fd" FontSize="13" HorizontalAlignment="Right" Margin="0,0,0,2"/>
<TextBlock Grid.Row="2" Grid.Column="0" Text="Dropped" Foreground="#94a3b8" FontSize="12" Margin="0,0,12,2"/>
<TextBlock Grid.Row="2" Grid.Column="1" FontSize="13" HorizontalAlignment="Right" Margin="0,0,0,2"
Foreground="{Binding CurrentHealth.DroppedFrames, Converter={StaticResource DroppedToBrush}}">
<TextBlock.Text>
<MultiBinding Converter="{StaticResource DroppedPercent}">
<Binding Path="CurrentHealth.DroppedFrames" Mode="OneWay"/>
<Binding Path="CurrentHealth.FPS" Mode="OneWay"/>
<Binding Path="CurrentHealth.StreamDuration" Mode="OneWay"/>
</MultiBinding>
</TextBlock.Text>
</TextBlock>
<TextBlock Grid.Row="3" Grid.Column="0" Text="Live:" Foreground="#94a3b8" FontSize="12" Margin="0,0,12,2"/>
<TextBlock Grid.Row="3" Grid.Column="1" Text="{Binding CurrentHealth.StreamDuration, Mode=OneWay, StringFormat={}{0:hh\:mm\:ss}}"
Foreground="#ff8c3a" FontSize="13" FontWeight="SemiBold" HorizontalAlignment="Right" Margin="0,0,0,2"/>
<TextBlock Grid.Row="4" Grid.ColumnSpan="2" Text="{Binding CurrentHealth.HealthMessage}"
Foreground="#ff8c3a" FontSize="12" TextWrapping="Wrap" Margin="0,0,0,6"/>
<ComboBox Grid.Row="5" Grid.ColumnSpan="2" VerticalAlignment="Bottom" Style="{StaticResource YtComboBox}"
ItemsSource="{Binding QualityOptions}"
SelectedItem="{Binding SelectedQuality}"
DisplayMemberPath="Label"
ToolTip="{Binding ResolutionHelp}"
IsEnabled="{Binding IsOffline}"/>
</Grid>
</Border>
<!-- ═══ THUMBNAIL STRIP (over the preview column; left column stays empty) ═══ --> <!-- ═══ THUMBNAIL STRIP (over the preview column; left column stays empty) ═══ -->
<controls:SceneThumbnailStrip x:Name="SceneStrip" Grid.Row="0" Grid.Column="1" Margin="8,0" Height="154"/> <controls:SceneThumbnailStrip x:Name="SceneStrip" Grid.Row="0" Grid.Column="1" Margin="8,0" Height="154"/>
+7 -11
View File
@@ -1,18 +1,14 @@
<Window x:Class="ytLive.SocialsDialog" <UserControl x:Class="ytLive.SocialsDialog"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation" xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
Title="Social Media Site Promotion" Height="520" Width="520"
Icon="/Assets/llamacasty-icon.png"
WindowStartupLocation="CenterOwner" ResizeMode="NoResize"
ShowInTaskbar="False" Background="#1a1a2e">
<Window.Resources> <UserControl.Resources>
<ResourceDictionary> <ResourceDictionary>
<BooleanToVisibilityConverter x:Key="BoolToVis"/> <BooleanToVisibilityConverter x:Key="BoolToVis"/>
</ResourceDictionary> </ResourceDictionary>
</Window.Resources> </UserControl.Resources>
<Grid Margin="24"> <Grid Margin="0">
<Grid.RowDefinitions> <Grid.RowDefinitions>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
<RowDefinition Height="Auto"/> <RowDefinition Height="Auto"/>
@@ -125,4 +121,4 @@
<Button Content="Save" Command="{Binding SaveCommand}" Style="{StaticResource YtButton}" MinWidth="110"/> <Button Content="Save" Command="{Binding SaveCommand}" Style="{StaticResource YtButton}" MinWidth="110"/>
</StackPanel> </StackPanel>
</Grid> </Grid>
</Window> </UserControl>
+4 -10
View File
@@ -1,24 +1,18 @@
using System.Windows; using System.Windows;
using System.Windows.Controls;
using System.Windows.Input; using System.Windows.Input;
using ytLive.Helpers; using ytLive.Helpers;
using ytLive.ViewModels; using ytLive.ViewModels;
namespace ytLive; namespace ytLive;
public partial class SocialsDialog : Window public partial class SocialsDialog : UserControl
{ {
private readonly SocialsDialogViewModel _viewModel; private SocialsDialogViewModel _viewModel => (SocialsDialogViewModel)DataContext;
public SocialsDialog(SocialsDialogViewModel viewModel) public SocialsDialog()
{ {
AppLog.Write("SocialsDialog ctor: before InitializeComponent");
InitializeComponent(); InitializeComponent();
AppLog.Write("SocialsDialog ctor: after InitializeComponent");
DataContext = viewModel;
_viewModel = viewModel;
viewModel.SaveRequested += () => DialogResult = true;
viewModel.CancelRequested += () => DialogResult = false;
Closing += (_, _) => _viewModel.AbortPending();
} }
private void SignInButton_Click(object sender, RoutedEventArgs e) private void SignInButton_Click(object sender, RoutedEventArgs e)
+29 -3
View File
@@ -119,20 +119,46 @@ public partial class MainViewModel
/// Opens the Social Media Site Promotion dialog (6 slots, sign-in gate, /// Opens the Social Media Site Promotion dialog (6 slots, sign-in gate,
/// validation). On save the working copy replaces <see cref="_socials"/>. /// validation). On save the working copy replaces <see cref="_socials"/>.
/// </summary> /// </summary>
/// <summary>The Socials pull-out (same drawer pattern as Stream Settings / YPP / TEST).</summary>
public bool SocialsDrawerOpen { get; private set; }
public SocialsDialogViewModel? SocialsDrawer { get; private set; }
private void OpenSocialDialog() private void OpenSocialDialog()
{ {
if (SocialsDrawerOpen)
{
CloseSocialsDrawer();
return;
}
var dialog = new SocialsDialogViewModel( var dialog = new SocialsDialogViewModel(
_socialValidator, _socialValidator,
SignInAsync, SignInAsync,
SignOutYouTubeAsync, SignOutYouTubeAsync,
_youtubeAuth.CurrentChannel, _youtubeAuth.CurrentChannel,
_socials); _socials);
var window = new ytLive.SocialsDialog(dialog) { Owner = Application.Current.MainWindow }; dialog.SaveRequested += () =>
if (window.ShowDialog() == true)
{ {
_socials = dialog.BuildSocialsConfig(); _socials = dialog.BuildSocialsConfig();
NotifySocialsChanged(); NotifySocialsChanged();
} CloseSocialsDrawer();
};
dialog.CancelRequested += CloseSocialsDrawer;
SocialsDrawer = dialog;
BroadcastForm.IsDrawerOpen = false;
Ypp.IsDrawerOpen = false;
TestSession.IsDrawerOpen = false;
SocialsDrawerOpen = true;
OnPropertyChanged(nameof(SocialsDrawer));
OnPropertyChanged(nameof(SocialsDrawerOpen));
}
internal void CloseSocialsDrawer()
{
if (!SocialsDrawerOpen) return;
SocialsDrawerOpen = false;
SocialsDrawer?.AbortPending();
OnPropertyChanged(nameof(SocialsDrawerOpen));
} }
private void NotifySocialsChanged() private void NotifySocialsChanged()
+1
View File
@@ -364,6 +364,7 @@ public partial class MainViewModel : ViewModelBase
new("1080p30", 30, 8.0, 1920, 1080), new("1080p30", 30, 8.0, 1920, 1080),
new("720p60", 60, 6.0, 1280, 720), new("720p60", 60, 6.0, 1280, 720),
new("720p30", 30, 6.0, 1280, 720), new("720p30", 30, 6.0, 1280, 720),
new("Vertical 1080p30", 30, 8.0, 1080, 1920),
new("Vertical 1080p60", 60, 8.0, 1080, 1920), new("Vertical 1080p60", 60, 8.0, 1080, 1920),
}; };
+3
View File
@@ -38,6 +38,7 @@ public partial class MainViewModel
{ {
Ypp.IsDrawerOpen = false; Ypp.IsDrawerOpen = false;
TestSession.IsDrawerOpen = false; TestSession.IsDrawerOpen = false;
CloseSocialsDrawer();
} }
}; };
Ypp.PropertyChanged += (_, e) => Ypp.PropertyChanged += (_, e) =>
@@ -46,6 +47,7 @@ public partial class MainViewModel
{ {
BroadcastForm.IsDrawerOpen = false; BroadcastForm.IsDrawerOpen = false;
TestSession.IsDrawerOpen = false; TestSession.IsDrawerOpen = false;
CloseSocialsDrawer();
} }
}; };
TestSession.PropertyChanged += (_, e) => TestSession.PropertyChanged += (_, e) =>
@@ -54,6 +56,7 @@ public partial class MainViewModel
{ {
BroadcastForm.IsDrawerOpen = false; BroadcastForm.IsDrawerOpen = false;
Ypp.IsDrawerOpen = false; Ypp.IsDrawerOpen = false;
CloseSocialsDrawer();
} }
}; };
} }