refactor(settings): a pull-out drawer, with Zoom as a category — and the menu moves to the wordmark

Creator directive, 2026-11-01. Settings stopped being a centred modal.

- **Drawer** (Controls/SettingsDrawer): hosted in PreviewPane's pull-out stack, 320px, the same
  animated-width pattern as Stream Settings / YPP / Socials / TEST, opened by the gear rail tab
  on a single left click. SettingsDrawerOpen is exclusive with the other four, both directions.
  This also removes the modal's MaxHeight-700 scroll problem for good: the drawer is the height
  of the preview.
- **Categories**: headings with controls indented beneath them. Zoom now owns *Zoom view shape*
  (Circle / Phone / Tablet) and *Zoom box follows*; Recordings, Brand logo and Scene transition
  are their own categories.
- **The app menu moved to the LlamaCasty wordmark** (right-click → Report Bug / Feature Request /
  About). Left-click still opens About. The gear tab no longer carries a menu at all, so clicking
  it does exactly one thing.

OverlayHost keeps only the Bug / Feature / About panels; its brand-logo handlers and the dev-panel
injection moved with the settings block. BrandLogoPreviewTests follows the control (same assertions,
new path). Release 0 warnings; 15 tests pass across brand logo, zoom, entitlement and preview
history.
This commit is contained in:
2026-11-01 09:16:27 -08:00
parent c418288215
commit a35ec0c5f8
14 changed files with 310 additions and 173 deletions
-100
View File
@@ -35,106 +35,6 @@
</Button>
</Grid>
<!-- App Settings: broadcast fields live in the preview's
Text pull-out now — this panel keeps app-level prefs only.
⛔ The ScrollViewer is load-bearing: the shell is MaxHeight 700 and the
panel has more sections than that (Recordings, Brand logo, Preview,
transitions, Zoom box, Developer), so without it the lower options were
clipped off-screen — which is how "Tablet (Horizontal Rectangle)" existed
in the XAML yet never appeared in the app (2026-11-01). -->
<ScrollViewer Grid.Row="1" VerticalScrollBarVisibility="Auto"
Visibility="{Binding IsSettingsOpen, Converter={StaticResource BoolToVis}}">
<StackPanel Margin="0,8,0,0">
<TextBlock Text="Recordings" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" FontWeight="SemiBold" Margin="0,8,0,2"/>
<TextBlock Text="Default location" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" Margin="10,0,0,2"/>
<DockPanel Margin="10,0,0,8">
<Button DockPanel.Dock="Right" Content="Browse…" Command="{Binding ChooseRecordFolderCommand}"
Style="{StaticResource YtButtonSecondary}" Padding="8,2" FontSize="11" Margin="6,0,0,0"/>
<Button DockPanel.Dock="Right" Content="Use Downloads" Command="{Binding ResetRecordFolderCommand}"
Style="{StaticResource YtButtonSecondary}" Padding="8,2" FontSize="11" Margin="6,0,0,0"/>
<TextBox Style="{StaticResource YtTextBoxCompact}" Text="{Binding RecordFolderDisplay, Mode=OneWay}"
IsReadOnly="True" Foreground="{StaticResource Brush.OnSurfaceVariant}" VerticalAlignment="Center"/>
</DockPanel>
<TextBlock Text="Brand logo" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" FontWeight="SemiBold" Margin="0,8,0,2"/>
<TextBlock Text="Image file (shown on every scene)" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" Margin="10,0,0,2"/>
<DockPanel Margin="10,0,0,8">
<Button DockPanel.Dock="Right" Content="Browse…" Command="{Binding BrowseBrandLogoCommand}"
Style="{StaticResource YtButtonSecondary}" Padding="8,2" FontSize="11" Margin="6,0,0,0"/>
<!-- A file NAME says nothing about the picture. The thumbnail shows what
is set; hovering enlarges it, a click collapses it again. -->
<Border x:Name="BrandLogoThumbFrame" DockPanel.Dock="Left" Width="28" Height="28"
Margin="0,0,6,0" CornerRadius="4" ClipToBounds="True"
Background="{StaticResource Brush.SurfaceContainerLowest}"
BorderBrush="{StaticResource Brush.Outline}" BorderThickness="1"
Cursor="Hand" ToolTip="Hover to enlarge — click to close"
Visibility="{Binding BrandLogoPreviewImage, Converter={StaticResource NotNullToVis}}">
<Image x:Name="BrandLogoThumbImage" Source="{Binding BrandLogoPreviewImage}"
Stretch="Uniform" MouseEnter="BrandLogoThumb_MouseEnter"
MouseLeave="BrandLogoThumb_MouseLeave"
MouseLeftButtonUp="BrandLogoThumb_Click"/>
</Border>
<TextBox Style="{StaticResource YtTextBoxCompact}" Text="{Binding BrandLogoFileDisplay, Mode=OneWay}"
IsReadOnly="True" Foreground="{StaticResource Brush.OnSurfaceVariant}" VerticalAlignment="Center"/>
</DockPanel>
<Popup x:Name="BrandLogoPreviewPopup" PlacementTarget="{Binding ElementName=BrandLogoThumbFrame}"
Placement="Right" HorizontalOffset="6" StaysOpen="True" AllowsTransparency="True"
IsHitTestVisible="True" MouseLeave="BrandLogoPreviewPopup_MouseLeave">
<Border Padding="6" CornerRadius="6"
Background="{StaticResource Brush.SurfaceContainerHighest}"
BorderBrush="{StaticResource Brush.Outline}" BorderThickness="1">
<StackPanel>
<Image Source="{Binding BrandLogoPreviewImage}" MaxWidth="420" MaxHeight="320"
Stretch="Uniform" RenderOptions.BitmapScalingMode="HighQuality"/>
<TextBlock Text="{Binding BrandLogoFileDisplay}" FontSize="11" Margin="0,6,0,0"
Foreground="{StaticResource Brush.OnSurfaceVariant}"/>
</StackPanel>
</Border>
</Popup>
<TextBlock Text="Preview" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" FontWeight="SemiBold" Margin="0,8,0,2"/>
<TextBlock Text="Zoom view shape" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" Margin="10,0,0,2"/>
<ComboBox Style="{StaticResource YtComboBox}" Margin="10,0,0,8" SelectedValue="{Binding ZoomShape}" SelectedValuePath="Tag">
<ComboBoxItem Content="Circle" Tag="{x:Static Models:ZoomShape.Circle}"/>
<ComboBoxItem Content="Phone (Vertical Rectangle)" Tag="{x:Static Models:ZoomShape.VerticalRectangle}"/>
<ComboBoxItem Content="Tablet (Horizontal Rectangle)" Tag="{x:Static Models:ZoomShape.HorizontalRectangle}"/>
</ComboBox>
<TextBlock Text="Scene transition" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" FontWeight="SemiBold" Margin="0,8,0,2"/>
<TextBlock Text="Type" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" Margin="10,0,0,2"/>
<ComboBox Style="{StaticResource YtComboBox}" Margin="10,0,0,8" SelectedValue="{Binding SelectedTransitionType}" SelectedValuePath="Tag">
<ComboBoxItem Content="Cut" Tag="{x:Static services:TransitionType.Cut}"/>
<ComboBoxItem Content="Fade" Tag="{x:Static services:TransitionType.Fade}"/>
<ComboBoxItem Content="Move" Tag="{x:Static services:TransitionType.Move}"/>
<ComboBoxItem Content="Wipe" Tag="{x:Static services:TransitionType.Wipe}"/>
</ComboBox>
<DockPanel Margin="10,0,0,6">
<TextBlock DockPanel.Dock="Right" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Margin="8,0,0,0" Width="56">
<Run Text="{Binding TransitionDurationMs, StringFormat={}{0:0}}"/><Run Text=" ms"/>
</TextBlock>
<Slider Minimum="100" Maximum="2000" TickFrequency="50" IsSnapToTickEnabled="True"
Value="{Binding TransitionDurationMs, Mode=TwoWay}" VerticalAlignment="Center"/>
</DockPanel>
<!-- Zoom box (creator ruling 2026-11-01): Anchor keeps the magnification on
the point that was clicked; Follow re-anchors it to the box as it is
dragged. Anchor is the default and the persisted norm. -->
<TextBlock Text="Zoom box" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" FontWeight="SemiBold" Margin="0,8,0,2"/>
<DockPanel Margin="10,0,0,8">
<ToggleButton DockPanel.Dock="Right" Style="{StaticResource PillToggle}"
IsChecked="{Binding ZoomFollowsBox, Mode=TwoWay}" VerticalAlignment="Center"
ToolTip="Off: the box magnifies the area you clicked. On: it magnifies whatever it hovers over."/>
<TextBlock Text="Follow the box" Foreground="{StaticResource Brush.OnSurface}" FontSize="11" VerticalAlignment="Center"/>
</DockPanel>
<!-- Development panel host. Empty in a Release build: the panel itself is
excluded from the Release compile, and OverlayHost.xaml.cs only fills
this inside #if DEBUG. Never put content here directly. -->
<StackPanel x:Name="DeveloperPanelHost"/>
</StackPanel>
</ScrollViewer>
<!-- Report Bug -->
<StackPanel Grid.Row="1" Margin="0,16,0,0"
Visibility="{Binding IsBugOpen, Converter={StaticResource BoolToVis}}">
-36
View File
@@ -9,44 +9,8 @@ public partial class OverlayHost : UserControl
public OverlayHost()
{
InitializeComponent();
#if DEBUG
// Development affordances are added at runtime, never declared in XAML: the panel is
// excluded from the Release compile (ytLive.csproj), so this line is the only
// reference to it and a Release build has none.
DeveloperPanelHost.Children.Add(new DeveloperPanel());
#endif
}
/// <summary>Is the enlarged brand-logo preview showing? Test seam — a Popup is a separate
/// window, so nothing else can observe it.</summary>
internal bool IsBrandLogoPreviewOpen => BrandLogoPreviewPopup.IsOpen;
/// <summary>Hover opens the enlargement. Nothing to show when no image is set, so the
/// thumbnail simply does nothing in that case.</summary>
private void BrandLogoThumb_MouseEnter(object sender, System.Windows.Input.MouseEventArgs e)
=> OpenBrandLogoPreview();
/// <summary>Leaving the thumbnail closes it — unless the pointer walked onto the popup
/// itself (which sits beside it), where it stays put so the creator can look closely.</summary>
private void BrandLogoThumb_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
{
if (BrandLogoPreviewPopup.Child?.IsMouseOver == true) return;
CloseBrandLogoPreview();
}
/// <summary>A click collapses the enlargement — the explicit "enough, thanks" gesture.</summary>
private void BrandLogoThumb_Click(object sender, System.Windows.Input.MouseButtonEventArgs e)
=> CloseBrandLogoPreview();
private void BrandLogoPreviewPopup_MouseLeave(object sender, System.Windows.Input.MouseEventArgs e)
=> CloseBrandLogoPreview();
/// <summary>The thumbnail is hidden when no image is set (see the XAML), so there is nothing
/// to open in that case — no empty frame can appear.</summary>
internal void OpenBrandLogoPreview() => BrandLogoPreviewPopup.IsOpen = true;
internal void CloseBrandLogoPreview() => BrandLogoPreviewPopup.IsOpen = false;
private void BackToAbout_Click(object sender, RoutedEventArgs e)
{
if (DataContext is MainViewModel vm)
+67 -17
View File
@@ -2,7 +2,7 @@
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:Helpers="clr-namespace:ytLive.Helpers"
xmlns:local="clr-namespace:ytLive">
xmlns:local="clr-namespace:ytLive" xmlns:controls="clr-namespace:ytLive.Controls">
<Border Background="{StaticResource Brush.SurfaceContainerLowest}" CornerRadius="6" Margin="8,0"
BorderBrush="{Binding PreviewGlowBrush}" BorderThickness="{Binding PreviewGlowThickness}">
<Grid>
@@ -881,26 +881,76 @@
</StackPanel>
</Grid>
</Border>
<!-- The Settings drawer: same animated-width pattern as the other
pull-outs, opened by the gear rail tab (creator ruling 2026-11-01).
Settings used to be a centred modal; it is a drawer now. -->
<Border Background="{StaticResource Brush.SurfaceContainer}" ClipToBounds="True">
<Border.Style>
<Style TargetType="Border">
<Setter Property="Width" Value="0"/>
<Style.Triggers>
<DataTrigger Binding="{Binding SettingsDrawerOpen}" 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="SETTINGS" Style="{StaticResource SectionHeader}" HorizontalAlignment="Left"/>
<Button HorizontalAlignment="Right" VerticalAlignment="Top"
Background="Transparent" BorderThickness="0" Cursor="Hand"
Command="{Binding OpenSettingsCommand}"
ToolTip="Close">
<Button.Template>
<ControlTemplate TargetType="Button">
<ContentPresenter/>
</ControlTemplate>
</Button.Template>
<TextBlock Text="✕" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="14" FontWeight="Bold"/>
</Button>
</Grid>
<controls:SettingsDrawer Grid.Row="1"/>
</Grid>
</Border>
<!-- The stacked white tabs -->
<StackPanel Orientation="Vertical" HorizontalAlignment="Right" VerticalAlignment="Stretch">
<!-- Settings: the first tab, orange, gear only (creator ruling 2026-11-01 —
moved off the top bar). Left-click opens the same app menu
(Settings / Bug / Feature / About); right-click goes straight
to App Settings, matching the old gear's behaviour exactly. -->
<!-- Settings: the first tab, orange, gear only (creator ruling
2026-11-01). A left click opens the Settings DRAWER immediately
— same behaviour as the other rail tabs. The app menu (Report
Bug / Feature Request / About) moved to a right-click on the
LlamaCasty wordmark in the top bar. -->
<Border Width="30" VerticalAlignment="Center" Background="{StaticResource Brush.Attention}"
CornerRadius="4,0,0,4" Padding="0,10" Cursor="Hand" Margin="0,4,0,0"
ToolTip="App menu — right-click for Settings"
MouseLeftButtonUp="SettingsTab_Click"
PreviewMouseRightButtonDown="SettingsTab_PreviewMouseRightButtonDown">
<Border.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="App Settings" Command="{Binding OpenSettingsCommand}"/>
<Separator/>
<MenuItem Header="Report Bug" Command="{Binding OpenBugCommand}"/>
<MenuItem Header="Feature Request" Command="{Binding OpenFeatureCommand}"/>
<MenuItem Header="About" Command="{Binding OpenAboutCommand}"/>
</ContextMenu>
</Border.ContextMenu>
ToolTip="Settings"
MouseLeftButtonUp="SettingsTab_Click">
<Path Data="M19.43,12.98c0.04,-0.32 0.07,-0.65 0.07,-0.98c0,-0.33 -0.03,-0.66 -0.07,-0.98l2.11,-1.65c0.19,-0.15 0.24,-0.42 0.12,-0.64l-2,-3.46c-0.12,-0.22 -0.38,-0.31 -0.61,-0.22l-2.49,1c-0.52,-0.4 -1.08,-0.73 -1.69,-0.98l-0.38,-2.65C14.46,2.18 14.25,2 14,2h-4c-0.25,0 -0.46,0.18 -0.49,0.42l-0.38,2.65c-0.61,0.25 -1.17,0.59 -1.69,0.98l-2.49,-1c-0.23,-0.09 -0.49,0 -0.61,0.22l-2,3.46c-0.13,0.22 -0.07,0.49 0.12,0.64l2.11,1.65c-0.04,0.32 -0.07,0.65 -0.07,0.98c0,0.33 0.03,0.66 0.07,0.98l-2.11,1.65c-0.19,0.15 -0.24,0.42 -0.12,0.64l2,3.46c0.12,0.22 0.38,0.31 0.61,0.22l2.49,-1c0.52,0.4 1.08,0.73 1.69,0.98l0.38,2.65c0.03,0.24 0.24,0.42 0.49,0.42h4c0.25,0 0.46,-0.18 0.49,-0.42l0.38,-2.65c0.61,-0.25 1.17,-0.59 1.69,-0.98l2.49,1c0.23,0.09 0.49,0 0.61,-0.22l2,-3.46c0.12,-0.22 0.07,-0.49 -0.12,-0.64L19.43,12.98zM12,15.5c-1.93,0 -3.5,-1.57 -3.5,-3.5c0,-1.93 1.57,-3.5 3.5,-3.5c1.93,0 3.5,1.57 3.5,3.5C15.5,13.93 13.93,15.5 12,15.5z"
Fill="{StaticResource Brush.Background}" Width="18" Height="18" Stretch="Uniform"
HorizontalAlignment="Center"/>
+3 -14
View File
@@ -730,22 +730,11 @@ public partial class PreviewPane : UserControl
}
}
/// <summary>Rail settings tab (creator ruling 2026-11-01): left-click opens the same app menu
/// the top-bar gear used to own, so Bug / Feature / About keep their only entry point.</summary>
/// <summary>Rail settings tab (creator ruling 2026-11-01): a left click opens the Settings
/// DRAWER immediately, exactly like the other rail tabs — no menu, no modal.</summary>
private void SettingsTab_Click(object sender, MouseButtonEventArgs e)
{
if (sender is not FrameworkElement { ContextMenu: { } menu } anchor) return;
menu.PlacementTarget = anchor;
menu.Placement = System.Windows.Controls.Primitives.PlacementMode.Left;
menu.IsOpen = true;
if (DataContext is MainViewModel vm) vm.ToggleSettingsDrawer();
e.Handled = true;
}
/// <summary>Right-click goes straight to App Settings — the same shortcut the old gear had.</summary>
private void SettingsTab_PreviewMouseRightButtonDown(object sender, MouseButtonEventArgs e)
{
e.Handled = true;
if (DataContext is MainViewModel { OpenSettingsCommand: { } open } && open.CanExecute(null))
open.Execute(null);
}
}
+104
View File
@@ -0,0 +1,104 @@
<UserControl x:Class="ytLive.Controls.SettingsDrawer"
xmlns="http://schemas.microsoft.com/winfx/2006/xaml/presentation"
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
xmlns:models="clr-namespace:ytLive.Models"
xmlns:services="clr-namespace:ytLive.Services">
<!-- App Settings, as a RIGHT-EDGE PULL-OUT (creator ruling 2026-11-01): the gear tab opens
this drawer immediately, in the same animated-width pattern as Stream Settings / YPP /
Socials / TEST. It used to be a centred modal overlay, which then needed its own scroll
viewer once the section list outgrew the shell — a drawer has the height of the preview
and that problem disappears.
Categories are headings; their controls are indented under them (Zoom is one). -->
<ScrollViewer VerticalScrollBarVisibility="Auto" HorizontalScrollBarVisibility="Disabled">
<StackPanel Margin="0,0,4,0">
<!-- ═══ RECORDINGS ═══ -->
<TextBlock Text="Recordings" Style="{StaticResource SectionHeader}" Margin="0,0,0,4"/>
<TextBlock Text="Default location" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" Margin="10,0,0,2"/>
<DockPanel Margin="10,0,0,10">
<Button DockPanel.Dock="Right" Content="Browse…" Command="{Binding ChooseRecordFolderCommand}"
Style="{StaticResource YtButtonSecondary}" Padding="8,2" FontSize="11" Margin="6,0,0,0"/>
<Button DockPanel.Dock="Right" Content="Use Downloads" Command="{Binding ResetRecordFolderCommand}"
Style="{StaticResource YtButtonSecondary}" Padding="8,2" FontSize="11" Margin="6,0,0,0"/>
<TextBox Style="{StaticResource YtTextBoxCompact}" Text="{Binding RecordFolderDisplay, Mode=OneWay}"
IsReadOnly="True" Foreground="{StaticResource Brush.OnSurfaceVariant}" VerticalAlignment="Center"/>
</DockPanel>
<!-- ═══ BRAND ═══ -->
<TextBlock Text="Brand logo" Style="{StaticResource SectionHeader}" Margin="0,6,0,4"/>
<TextBlock Text="Image file (shown on every scene)" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" Margin="10,0,0,2"/>
<DockPanel Margin="10,0,0,10">
<Button DockPanel.Dock="Right" Content="Browse…" Command="{Binding BrowseBrandLogoCommand}"
Style="{StaticResource YtButtonSecondary}" Padding="8,2" FontSize="11" Margin="6,0,0,0"/>
<!-- A file NAME says nothing about the picture. The thumbnail shows what is set;
hovering enlarges it, a click collapses it again. -->
<Border x:Name="BrandLogoThumbFrame" DockPanel.Dock="Left" Width="28" Height="28"
Margin="0,0,6,0" CornerRadius="4" ClipToBounds="True"
Background="{StaticResource Brush.SurfaceContainerLowest}"
BorderBrush="{StaticResource Brush.Outline}" BorderThickness="1"
Cursor="Hand" ToolTip="Hover to enlarge — click to close"
Visibility="{Binding BrandLogoPreviewImage, Converter={StaticResource NotNullToVis}}">
<Image x:Name="BrandLogoThumbImage" Source="{Binding BrandLogoPreviewImage}"
Stretch="Uniform" MouseEnter="BrandLogoThumb_MouseEnter"
MouseLeave="BrandLogoThumb_MouseLeave"
MouseLeftButtonUp="BrandLogoThumb_Click"/>
</Border>
<TextBox Style="{StaticResource YtTextBoxCompact}" Text="{Binding BrandLogoFileDisplay, Mode=OneWay}"
IsReadOnly="True" Foreground="{StaticResource Brush.OnSurfaceVariant}" VerticalAlignment="Center"/>
</DockPanel>
<Popup x:Name="BrandLogoPreviewPopup" PlacementTarget="{Binding ElementName=BrandLogoThumbFrame}"
Placement="Left" HorizontalOffset="-6" StaysOpen="True" AllowsTransparency="True"
IsHitTestVisible="True" MouseLeave="BrandLogoPreviewPopup_MouseLeave">
<Border Padding="6" CornerRadius="6"
Background="{StaticResource Brush.SurfaceContainerHighest}"
BorderBrush="{StaticResource Brush.Outline}" BorderThickness="1">
<StackPanel>
<Image Source="{Binding BrandLogoPreviewImage}" MaxWidth="420" MaxHeight="320"
Stretch="Uniform" RenderOptions.BitmapScalingMode="HighQuality"/>
<TextBlock Text="{Binding BrandLogoFileDisplay}" FontSize="11" Margin="0,6,0,0"
Foreground="{StaticResource Brush.OnSurfaceVariant}"/>
</StackPanel>
</Border>
</Popup>
<!-- ═══ ZOOM ═══ -->
<TextBlock Text="Zoom" Style="{StaticResource SectionHeader}" Margin="0,6,0,4"/>
<TextBlock Text="Zoom view shape" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" Margin="10,0,0,2"/>
<ComboBox Style="{StaticResource YtComboBox}" Margin="10,0,0,8" SelectedValue="{Binding ZoomShape}" SelectedValuePath="Tag">
<ComboBoxItem Content="Circle" Tag="{x:Static models:ZoomShape.Circle}"/>
<ComboBoxItem Content="Phone (Vertical Rectangle)" Tag="{x:Static models:ZoomShape.VerticalRectangle}"/>
<ComboBoxItem Content="Tablet (Horizontal Rectangle)" Tag="{x:Static models:ZoomShape.HorizontalRectangle}"/>
</ComboBox>
<DockPanel Margin="10,0,0,10">
<ToggleButton DockPanel.Dock="Right" Style="{StaticResource PillToggle}"
IsChecked="{Binding ZoomFollowsBox, Mode=TwoWay}" VerticalAlignment="Center"
ToolTip="Off: the box magnifies the area you clicked. On: it magnifies whatever it hovers over."/>
<TextBlock Text="Zoom box follows" Foreground="{StaticResource Brush.OnSurface}" FontSize="11" VerticalAlignment="Center"/>
</DockPanel>
<!-- ═══ SCENE TRANSITIONS ═══ -->
<TextBlock Text="Scene transition" Style="{StaticResource SectionHeader}" Margin="0,6,0,4"/>
<TextBlock Text="Type" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" Margin="10,0,0,2"/>
<ComboBox Style="{StaticResource YtComboBox}" Margin="10,0,0,8" SelectedValue="{Binding SelectedTransitionType}" SelectedValuePath="Tag">
<ComboBoxItem Content="Cut" Tag="{x:Static services:TransitionType.Cut}"/>
<ComboBoxItem Content="Fade" Tag="{x:Static services:TransitionType.Fade}"/>
<ComboBoxItem Content="Move" Tag="{x:Static services:TransitionType.Move}"/>
<ComboBoxItem Content="Wipe" Tag="{x:Static services:TransitionType.Wipe}"/>
</ComboBox>
<DockPanel Margin="10,0,0,10">
<TextBlock DockPanel.Dock="Right" Foreground="{StaticResource Brush.OnSurfaceVariant}" FontSize="11" VerticalAlignment="Center" Margin="8,0,0,0" Width="56">
<Run Text="{Binding TransitionDurationMs, StringFormat={}{0:0}}"/><Run Text=" ms"/>
</TextBlock>
<Slider Minimum="100" Maximum="2000" TickFrequency="50" IsSnapToTickEnabled="True"
Value="{Binding TransitionDurationMs, Mode=TwoWay}" VerticalAlignment="Center"/>
</DockPanel>
<!-- Development panel host. Empty in a Release build: the panel itself is excluded
from the Release compile, and the code-behind only fills this inside #if DEBUG. -->
<StackPanel x:Name="DeveloperPanelHost"/>
</StackPanel>
</ScrollViewer>
</UserControl>
+58
View File
@@ -0,0 +1,58 @@
using System.Windows;
using System.Windows.Controls;
using System.Windows.Input;
namespace ytLive.Controls;
/// <summary>
/// App Settings as a right-edge pull-out (creator ruling 2026-11-01). Hosted by
/// <see cref="PreviewPane"/>'s drawer stack and opened by the gear rail tab, exactly like Stream
/// Settings / YPP / Socials / TEST.
///
/// <para>Born from the modal overlay: that shell was MaxHeight 700, so once the section list grew
/// the bottom options were clipped and unreachable — including the zoom shape the creator was
/// looking for. A drawer is the height of the preview, so the list can grow without a second
/// scroll decision to get wrong.</para>
/// </summary>
public partial class SettingsDrawer : UserControl
{
public SettingsDrawer()
{
InitializeComponent();
#if DEBUG
// Development affordances are added at runtime, never declared in XAML: the panel is
// excluded from the Release compile (ytLive.csproj), so this line is the only reference.
DeveloperPanelHost.Children.Add(new DeveloperPanel());
#endif
}
/// <summary>Is the enlarged brand-logo preview showing? Test seam — a Popup is a separate
/// window, so nothing else can observe it.</summary>
internal bool IsBrandLogoPreviewOpen => BrandLogoPreviewPopup.IsOpen;
/// <summary>The thumbnail is hidden when no image is set (see the XAML), so there is nothing to
/// open in that case — no empty frame can appear.</summary>
internal void OpenBrandLogoPreview() => BrandLogoPreviewPopup.IsOpen = true;
internal void CloseBrandLogoPreview() => BrandLogoPreviewPopup.IsOpen = false;
/// <summary>Hover opens the enlargement. Nothing to show when no image is set, so the
/// thumbnail simply does nothing in that case.</summary>
private void BrandLogoThumb_MouseEnter(object sender, MouseEventArgs e)
=> BrandLogoPreviewPopup.IsOpen = true;
/// <summary>Leaving the thumbnail closes it — unless the pointer walked onto the popup
/// itself, where it stays put so the creator can look closely.</summary>
private void BrandLogoThumb_MouseLeave(object sender, MouseEventArgs e)
{
if (BrandLogoPreviewPopup.Child?.IsMouseOver == true) return;
BrandLogoPreviewPopup.IsOpen = false;
}
/// <summary>A click collapses the enlargement — the explicit "enough, thanks" gesture.</summary>
private void BrandLogoThumb_Click(object sender, MouseButtonEventArgs e)
=> BrandLogoPreviewPopup.IsOpen = false;
private void BrandLogoPreviewPopup_MouseLeave(object sender, MouseEventArgs e)
=> BrandLogoPreviewPopup.IsOpen = false;
}
+14 -3
View File
@@ -21,11 +21,22 @@
<ColumnDefinition Width="Auto"/>
</Grid.ColumnDefinitions>
<!-- LOGO / BRAND — clicking opens the in-app About overlay. -->
<!-- LOGO / BRAND — left-click opens the in-app About overlay; right-click opens the app
menu (Report Bug / Feature Request / About). App Settings is no longer in this
menu: the gear rail tab opens the Settings drawer directly (creator ruling
2026-11-01). -->
<Button Grid.Column="0" Background="Transparent" BorderThickness="0" Cursor="Hand"
HorizontalAlignment="Left"
VerticalAlignment="Center" ToolTip="About LlamaCasty"
Command="{Binding OpenAboutCommand}">
VerticalAlignment="Center" ToolTip="About LlamaCasty — right-click for the menu"
Command="{Binding OpenAboutCommand}"
PreviewMouseRightButtonDown="BrandWordmark_PreviewMouseRightButtonDown">
<Button.ContextMenu>
<ContextMenu DataContext="{Binding PlacementTarget.DataContext, RelativeSource={RelativeSource Self}}">
<MenuItem Header="Report Bug" Command="{Binding OpenBugCommand}"/>
<MenuItem Header="Feature Request" Command="{Binding OpenFeatureCommand}"/>
<MenuItem Header="About" Command="{Binding OpenAboutCommand}"/>
</ContextMenu>
</Button.ContextMenu>
<Button.Template>
<ControlTemplate TargetType="Button">
<ContentPresenter/>
+14
View File
@@ -21,6 +21,20 @@ public partial class TopBar : UserControl
AvatarInitial.Visibility = System.Windows.Visibility.Collapsed;
}
/// <summary>Right-click on the LlamaCasty wordmark opens the app menu (Report Bug / Feature
/// Request / About) — the menu that used to hang off the gear, which now opens the Settings
/// drawer directly (creator ruling 2026-11-01).</summary>
private void BrandWordmark_PreviewMouseRightButtonDown(object sender, System.Windows.Input.MouseButtonEventArgs e)
{
if (sender is Button { ContextMenu: { } menu } button)
{
menu.PlacementTarget = button;
menu.Placement = System.Windows.Controls.Primitives.PlacementMode.Bottom;
menu.IsOpen = true;
e.Handled = true;
}
}
public void RefreshAvatar()
{
_ = LoadAvatarImageAsync();