From 1a9e8b8d362c1ad16ca4f0aa698b86b3aad4fc81 Mon Sep 17 00:00:00 2001 From: gramps Date: Fri, 30 Oct 2026 12:37:17 -0700 Subject: [PATCH] =?UTF-8?q?feat(audio=20dock):=20the=20shell=20=E2=80=94?= =?UTF-8?q?=20168px,=20auto-collapsing,=20replacing=20the=20empty=20bottom?= =?UTF-8?q?=20bar?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Unit 4 of the sound-bar refactor. `Controls/AudioDock` takes the bottom row that held the empty `BottomBar` spacer (deleted): four bay containers (MIC / GAME / TRAX / LIP-SYNC) above a 36px master strip, all token-styled. Height comes from `Models/AudioDockLayout`, not from a setting: 168px normally, 36px below a 900px window height. At the 1366×768 minimum a full dock leaves ~398px for the preview, so the collapse is mandatory, not cosmetic. The dock listens to its window's SizeChanged and re-applies. Nothing here pretends to be a meter yet — the bays and the master readouts fill in over the next units, each with its own test. Guard: AudioDockLayoutTests pins both sides of the threshold (768 collapsed, 1040 expanded), the boundary itself, and the two heights as the design's numbers. --- Controls/AudioDock.xaml | 58 ++++++++++++++++++++++++++++ Controls/AudioDock.xaml.cs | 55 ++++++++++++++++++++++++++ Controls/BottomBar.xaml | 10 ----- Controls/BottomBar.xaml.cs | 11 ------ MainWindow.xaml | 2 +- Models/AudioDockLayout.cs | 23 +++++++++++ ytLive.Tests/AudioDockLayoutTests.cs | 32 +++++++++++++++ 7 files changed, 169 insertions(+), 22 deletions(-) create mode 100644 Controls/AudioDock.xaml create mode 100644 Controls/AudioDock.xaml.cs delete mode 100644 Controls/BottomBar.xaml delete mode 100644 Controls/BottomBar.xaml.cs create mode 100644 Models/AudioDockLayout.cs create mode 100644 ytLive.Tests/AudioDockLayoutTests.cs diff --git a/Controls/AudioDock.xaml b/Controls/AudioDock.xaml new file mode 100644 index 0000000..561d92b --- /dev/null +++ b/Controls/AudioDock.xaml @@ -0,0 +1,58 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/Controls/AudioDock.xaml.cs b/Controls/AudioDock.xaml.cs new file mode 100644 index 0000000..d998688 --- /dev/null +++ b/Controls/AudioDock.xaml.cs @@ -0,0 +1,55 @@ +using System.Windows; +using System.Windows.Controls; +using ytLive.Models; + +namespace ytLive.Controls; + +/// The sound bars (creator ruling 2026-10-30). This unit owns the SHELL: the piece of the +/// window it occupies and the collapse behaviour. The bays and the master readouts fill in over +/// the following units, so nothing here pretends to be a working meter. +/// +/// Collapse is driven by the window height, not a user setting: at 1366×768 a full dock +/// squeezes the preview, and the creator should not have to manage that. +public partial class AudioDock : UserControl +{ + private Window? _window; + + public AudioDock() + { + InitializeComponent(); + Loaded += OnLoaded; + Unloaded += OnUnloaded; + } + + /// Is the dock showing only the telemetry strip? + internal bool IsCollapsed { get; private set; } + + /// Test seam: the same code the window's SizeChanged runs, without a window. + internal void ApplyWindowHeight(double windowHeight) + { + var collapsed = AudioDockLayout.IsCollapsed(windowHeight); + IsCollapsed = collapsed; + Height = AudioDockLayout.HeightFor(windowHeight); + Bays.Visibility = collapsed ? Visibility.Collapsed : Visibility.Visible; + } + + private void OnLoaded(object sender, RoutedEventArgs e) + { + _window = Window.GetWindow(this); + if (_window == null) + { + ApplyWindowHeight(AudioDockLayout.CollapseBelowHeight); // no host: show the dock + return; + } + _window.SizeChanged += OnWindowSizeChanged; + ApplyWindowHeight(_window.ActualHeight > 0 ? _window.ActualHeight : _window.Height); + } + + private void OnUnloaded(object sender, RoutedEventArgs e) + { + if (_window != null) _window.SizeChanged -= OnWindowSizeChanged; + _window = null; + } + + private void OnWindowSizeChanged(object sender, SizeChangedEventArgs e) => ApplyWindowHeight(e.NewSize.Height); +} diff --git a/Controls/BottomBar.xaml b/Controls/BottomBar.xaml deleted file mode 100644 index 6a233db..0000000 --- a/Controls/BottomBar.xaml +++ /dev/null @@ -1,10 +0,0 @@ - - - - - - - - diff --git a/Controls/BottomBar.xaml.cs b/Controls/BottomBar.xaml.cs deleted file mode 100644 index 36e5aa6..0000000 --- a/Controls/BottomBar.xaml.cs +++ /dev/null @@ -1,11 +0,0 @@ -using System.Windows.Controls; - -namespace ytLive.Controls; - -public partial class BottomBar : UserControl -{ - public BottomBar() - { - InitializeComponent(); - } -} \ No newline at end of file diff --git a/MainWindow.xaml b/MainWindow.xaml index a5959a7..137b8bd 100644 --- a/MainWindow.xaml +++ b/MainWindow.xaml @@ -148,7 +148,7 @@ - + diff --git a/Models/AudioDockLayout.cs b/Models/AudioDockLayout.cs new file mode 100644 index 0000000..3e71d9a --- /dev/null +++ b/Models/AudioDockLayout.cs @@ -0,0 +1,23 @@ +namespace ytLive.Models; + +/// How tall the audio dock should be for a given window height. +/// +/// Creator ruling (2026-10-30): the dock is 168px on a normal window and collapses to a 36px +/// telemetry strip when the window is short, because at 1366×768 a full dock leaves ~398px for the +/// preview and the composition starts letterboxing into a postage stamp. +/// +/// Pure, so the switch is testable without a window. +public static class AudioDockLayout +{ + /// Below this window height the dock collapses. The app's minimum is 768; the + /// default window is 1040, so a normal launch shows the full dock. + public const double CollapseBelowHeight = 900; + + public const double ExpandedHeight = 168; + public const double CollapsedHeight = 36; + + public static bool IsCollapsed(double windowHeight) => windowHeight < CollapseBelowHeight; + + public static double HeightFor(double windowHeight) + => IsCollapsed(windowHeight) ? CollapsedHeight : ExpandedHeight; +} diff --git a/ytLive.Tests/AudioDockLayoutTests.cs b/ytLive.Tests/AudioDockLayoutTests.cs new file mode 100644 index 0000000..144d611 --- /dev/null +++ b/ytLive.Tests/AudioDockLayoutTests.cs @@ -0,0 +1,32 @@ +using Xunit; +using ytLive.Models; + +namespace ytLive.Tests; + +/// The dock's height is the whole reason it is allowed to take 168px: on a short window it +/// must get out of the way instead of squeezing the preview. The threshold is the creator's +/// minimum (768) and default (1040) — both sides are pinned here so a future "tidy" cannot leave a +/// 768px window with a full dock. +/// +public class AudioDockLayoutTests +{ + [Fact] + public void The_dock_collapses_on_short_windows_and_expands_on_normal_ones() + { + // Minimum window: collapsed, so the preview keeps its room. + Assert.True(AudioDockLayout.IsCollapsed(768)); + Assert.Equal(AudioDockLayout.CollapsedHeight, AudioDockLayout.HeightFor(768)); + + // Default window: the full dock. + Assert.False(AudioDockLayout.IsCollapsed(1040)); + Assert.Equal(AudioDockLayout.ExpandedHeight, AudioDockLayout.HeightFor(1040)); + + // The boundary itself belongs to the expanded side (>= is the friendlier read). + Assert.False(AudioDockLayout.IsCollapsed(AudioDockLayout.CollapseBelowHeight)); + Assert.True(AudioDockLayout.IsCollapsed(AudioDockLayout.CollapseBelowHeight - 1)); + + // The two heights are the design's numbers, not whatever looked fine. + Assert.Equal(168, AudioDockLayout.ExpandedHeight); + Assert.Equal(36, AudioDockLayout.CollapsedHeight); + } +}