The tier table cannot know a creator's connection, so it should not pretend to. The Mbps box under the picker is theirs: the tier chooses geometry and frame rate, the box chooses bits. Stored per tier (`Video.Rate.<tier>`), so tuning 1080p60 does not touch 720p30, and none means "the shipped default" rather than zero. Clamped at the tier's YouTube minimum (`RateLadder`), because a target under it cannot hold the resolution; no ceiling otherwise, because a wrong guess is exactly what live step-down exists to catch, and a creator who types 25 on a fibre line should be allowed to find out. The tooltip carries YouTube's number for the tier. `MainViewModel.EffectiveQuality` is the single substitution point, and the encoder options, the step-down policy and the clip buffer all read it — so a rated tier can never stream at the shipped default because one call site was missed. One ordering trap found by the test: the constructor resolves the default tier before the layout store is open, so a read at that point must not cache, or the shipped default shadows the creator's stored rate for the whole run. 670 pass, 0 warnings.
194 lines
11 KiB
XML
194 lines
11 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"
|
|
xmlns:controls="clr-namespace:ytLive.Controls"
|
|
xmlns:notifications="clr-namespace:Notification.Wpf.Controls;assembly=Notification.Wpf"
|
|
mc:Ignorable="d"
|
|
Title="{Binding WindowTitle}" Height="1040" Width="1920"
|
|
MinWidth="1366" MinHeight="768"
|
|
Icon="/LlamaCasty;component/Assets/llamacasty-icon.png"
|
|
Background="{StaticResource Brush.Background}"
|
|
FontFamily="{StaticResource FontFamily.Sans}"
|
|
WindowStartupLocation="Manual"
|
|
Closing="MainWindow_Closing"
|
|
Loaded="MainWindow_Loaded"
|
|
Activated="MainWindow_Activated"
|
|
Deactivated="MainWindow_Deactivated"
|
|
PreviewKeyDown="MainWindow_PreviewKeyDown"
|
|
PreviewMouseLeftButtonDown="Window_PreviewMouseLeftButtonDown"
|
|
ContextMenuOpening="Window_ContextMenuOpening">
|
|
|
|
<!-- Preview undo/redo (creator directive 2026-11-01). Window-level so the keys work wherever
|
|
focus sits; the commands themselves refuse unless EditingActive, because undo with the
|
|
preview showing program output would rewrite a scene nobody is looking at. -->
|
|
<Window.InputBindings>
|
|
<KeyBinding Gesture="Ctrl+Z" Command="{Binding UndoPreviewCommand}"/>
|
|
<KeyBinding Gesture="Ctrl+Shift+Z" Command="{Binding RedoPreviewCommand}"/>
|
|
</Window.InputBindings>
|
|
|
|
<Window.DataContext>
|
|
<vm:MainViewModel/>
|
|
</Window.DataContext>
|
|
|
|
<Window.TaskbarItemInfo>
|
|
<TaskbarItemInfo x:Name="TaskbarInfo"/>
|
|
</Window.TaskbarItemInfo>
|
|
|
|
<Window.Resources>
|
|
<ResourceDictionary>
|
|
<Helpers:DroppedFramesToBrushConverter x:Key="DroppedToBrush"/>
|
|
<Helpers:DroppedPercentConverter x:Key="DroppedPercent"/>
|
|
|
|
<!-- Red dot shown in the taskbar icon while live -->
|
|
<DrawingImage x:Key="LiveOverlay">
|
|
<DrawingImage.Drawing>
|
|
<DrawingGroup>
|
|
<GeometryDrawing Brush="{StaticResource Brush.Tertiary}">
|
|
<GeometryDrawing.Geometry>
|
|
<EllipseGeometry Center="8,8" RadiusX="8" RadiusY="8"/>
|
|
</GeometryDrawing.Geometry>
|
|
</GeometryDrawing>
|
|
</DrawingGroup>
|
|
</DrawingImage.Drawing>
|
|
</DrawingImage>
|
|
|
|
<!-- EyeButton/EyeIconStyle MOVED to Themes/Controls.xaml (2026-09-01): Controls/
|
|
LeftPanel.xaml consumes them via StaticResource, and window-scope resources
|
|
are invisible to a UserControl at parse time — the "known test failure" that
|
|
was actually an app-crash regression from the LeftPanel extraction. -->
|
|
</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">
|
|
<controls:TopBar x:Name="TopBar"/>
|
|
</Border>
|
|
|
|
<!-- ═══ MIDDLE: Main Content ═══ -->
|
|
<controls:HealthBanner Grid.Row="1"/>
|
|
|
|
<Grid Grid.Row="2" Margin="8">
|
|
<Grid.RowDefinitions>
|
|
<RowDefinition Height="Auto"/>
|
|
<RowDefinition Height="*"/>
|
|
<!-- the dock lives INSIDE the work area so its bottom edge lines up with the
|
|
layers panel (creator, 2026-10-30) -->
|
|
<RowDefinition Height="Auto"/>
|
|
</Grid.RowDefinitions>
|
|
<Grid.ColumnDefinitions>
|
|
<ColumnDefinition Width="360"/>
|
|
<ColumnDefinition Width="3*"/>
|
|
</Grid.ColumnDefinitions>
|
|
|
|
<!-- STREAM READOUTS (top-left cell, above the layers) + resolution -->
|
|
<Border Grid.Row="0" Grid.Column="0" Background="{StaticResource Brush.SurfaceContainerLowest}" 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="{StaticResource Brush.OnSurfaceVariant}" FontSize="12" Margin="0,0,12,2"/>
|
|
<TextBlock Grid.Row="0" Grid.Column="1" Foreground="{StaticResource Brush.OnSurface}" FontSize="13" FontFamily="{StaticResource FontFamily.Mono}" 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="{StaticResource Brush.OnSurfaceVariant}" FontSize="12" Margin="0,0,12,2"/>
|
|
<TextBlock Grid.Row="1" Grid.Column="1" Text="{Binding CurrentHealth.FPS, Mode=OneWay, StringFormat={}{0:0}}"
|
|
Foreground="{StaticResource Brush.OnSurface}" FontSize="13" FontFamily="{StaticResource FontFamily.Mono}" HorizontalAlignment="Right" Margin="0,0,0,2"/>
|
|
|
|
<TextBlock Grid.Row="2" Grid.Column="0" Text="Dropped" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="12" Margin="0,0,12,2"/>
|
|
<TextBlock Grid.Row="2" Grid.Column="1" FontSize="13" FontFamily="{StaticResource FontFamily.Mono}" 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="{StaticResource Brush.OnSurfaceVariant}" 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="{StaticResource Brush.Attention}" FontSize="13" FontFamily="{StaticResource FontFamily.Mono}" FontWeight="SemiBold" HorizontalAlignment="Right" Margin="0,0,0,2"/>
|
|
|
|
<TextBlock Grid.Row="4" Grid.ColumnSpan="2" Text="{Binding CurrentHealth.HealthMessage}"
|
|
Foreground="{StaticResource Brush.Attention}" FontSize="12" TextWrapping="Wrap" Margin="0,0,0,6"/>
|
|
|
|
<StackPanel Grid.Row="5" Grid.ColumnSpan="2" VerticalAlignment="Bottom">
|
|
<ComboBox x:Name="QualityPicker" Style="{StaticResource YtComboBox}"
|
|
ItemsSource="{Binding QualityOptions}"
|
|
SelectedItem="{Binding SelectedQuality}"
|
|
ItemTemplate="{StaticResource YtQualityComboBoxItemTemplate}"
|
|
ItemContainerStyle="{StaticResource YtQualityComboBoxItem}"
|
|
ToolTip="{Binding ResolutionHelp}"
|
|
IsEnabled="{Binding CanChangeQuality}"/>
|
|
<!-- The creator's own rate: the tier sets the geometry, this sets the bits.
|
|
Guidance lives in the tooltip (YouTube's numbers, from RateLadder). -->
|
|
<Grid Margin="0,6,0,0">
|
|
<Grid.ColumnDefinitions>
|
|
<ColumnDefinition Width="*"/>
|
|
<ColumnDefinition Width="Auto"/>
|
|
</Grid.ColumnDefinitions>
|
|
<TextBox x:Name="RatedMbpsBox" Grid.Column="0" Style="{StaticResource YtTextBoxCompact}"
|
|
Text="{Binding RatedMbps, UpdateSourceTrigger=LostFocus, StringFormat={}{0:0.#}}"
|
|
ToolTip="{Binding RateHint}"
|
|
IsEnabled="{Binding CanChangeQuality}"/>
|
|
<TextBlock Grid.Column="1" Text="Mbps" Margin="6,0,0,0" VerticalAlignment="Center"
|
|
Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="12"/>
|
|
</Grid>
|
|
</StackPanel>
|
|
</Grid>
|
|
</Border>
|
|
|
|
<!-- ═══ 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"/>
|
|
|
|
<!-- LEFT PANEL: one panel, two states.
|
|
Offline (edit mode): LAYERS + PROPERTIES
|
|
Live/Recording: YouTube Live Chat -->
|
|
<controls:LeftPanel x:Name="LeftPanel" Grid.Row="1" Grid.Column="0" Grid.RowSpan="2"/>
|
|
|
|
|
|
<!-- CENTER: Preview Area -->
|
|
<controls:PreviewPane x:Name="PreviewPane" Grid.Row="1" Grid.Column="1"/>
|
|
|
|
<!-- The dock starts where the layers panel ends and shares its bottom edge. -->
|
|
<controls:AudioDock x:Name="AudioDock" Grid.Row="2" Grid.Column="1"/>
|
|
</Grid>
|
|
|
|
|
|
|
|
<!-- ═══ OVERLAY: Settings / Bug / Feature / About ═══ -->
|
|
<controls:OverlayHost x:Name="AboutOverlay" Grid.RowSpan="3"/>
|
|
|
|
<!-- ═══ TOASTS: non-blocking notifications (user-positioned, bottom-right by default) ═══ -->
|
|
<notifications:NotificationArea x:Name="ToastArea"
|
|
Grid.RowSpan="4"
|
|
Position="{Binding ToastPosition}" MaxItems="4"
|
|
Margin="{Binding ToastAreaMargin}"/>
|
|
</Grid>
|
|
</Window>
|