From 3261293d579c076c92b5e4c8e4fc3f32f250283e Mon Sep 17 00:00:00 2001 From: gramps Date: Thu, 5 Nov 2026 17:14:40 -0800 Subject: [PATCH] fix(ui): the gear and socials drawers retract on an outside click Reported 2026-11-05: those two tabs stayed open when clicking elsewhere, unlike Stream Settings, YPP and TEST. All five drawers share one host (TextPullOutHost), but the window's click-outside guard only listed the other three, so the gear and socials panels had no way to dismiss except their own tab. The guard now closes them too, and a test pins both directions: an outside click retracts the drawer, and a click inside it does not. --- MainWindow.xaml.cs | 4 ++ ViewModels/MainViewModel.Settings.cs | 5 +- ytLive.Tests/DrawerOutsideClickTests.cs | 75 +++++++++++++++++++++++++ 3 files changed, 82 insertions(+), 2 deletions(-) create mode 100644 ytLive.Tests/DrawerOutsideClickTests.cs diff --git a/MainWindow.xaml.cs b/MainWindow.xaml.cs index 54ae2d4..62c5a67 100644 --- a/MainWindow.xaml.cs +++ b/MainWindow.xaml.cs @@ -201,6 +201,10 @@ public partial class MainWindow : Window if (form.IsDrawerOpen) form.CloseDrawerCommand.Execute(null); if (ypp.IsDrawerOpen) ypp.CloseDrawerCommand.Execute(null); if (test.IsDrawerOpen) test.CloseDrawerCommand.Execute(null); + // The Settings (gear) and Socials drawers live in the same host but were left out of this + // guard, so clicking anywhere else left them open (reported 2026-11-05). + _viewModel.CloseSettingsDrawerIfOpen(); + _viewModel.CloseSocialsDrawer(); } if (_previewPane.IsClickInsidePreview(original)) return; if (_leftPanel.IsClickInside(original)) return; diff --git a/ViewModels/MainViewModel.Settings.cs b/ViewModels/MainViewModel.Settings.cs index eed7027..2b68d26 100644 --- a/ViewModels/MainViewModel.Settings.cs +++ b/ViewModels/MainViewModel.Settings.cs @@ -34,8 +34,9 @@ public partial class MainViewModel SettingsDrawerOpen = true; } - /// Called by the other drawers when they open, so settings yields to them. - private void CloseSettingsDrawerIfOpen() + /// Called by the other drawers when they open, so settings yields to them — and by the + /// window's click-outside guard, so the gear tab retracts like every other drawer (2026-11-05). + internal void CloseSettingsDrawerIfOpen() { if (_settingsDrawerOpen) SettingsDrawerOpen = false; } diff --git a/ytLive.Tests/DrawerOutsideClickTests.cs b/ytLive.Tests/DrawerOutsideClickTests.cs new file mode 100644 index 0000000..a4bba72 --- /dev/null +++ b/ytLive.Tests/DrawerOutsideClickTests.cs @@ -0,0 +1,75 @@ +using System; +using System.IO; +using System.Windows; +using System.Windows.Input; +using Microsoft.Data.Sqlite; +using Xunit; +using ytLive.ViewModels; + +namespace ytLive.Tests; + +/// +/// 2026-11-05 report: "the gear-icon tab and the socials tab do not retract when I click off the div, +/// like the other tabs." Both drawers live in the same host as Stream Settings / YPP / TEST, but the +/// window's click-outside guard only knew about the other three. +/// +[Collection("RealApp")] +public sealed class DrawerOutsideClickTests +{ + private readonly RealAppHost _app; + + public DrawerOutsideClickTests(RealAppHost app) => _app = app; + + [Fact] + public void ClickingOutside_RetractsTheSettingsAndSocialsDrawers() + { + _app.Run(() => + { + var tempDb = Path.Combine(Path.GetTempPath(), $"ytLlive-drawers-{Guid.NewGuid():N}.db"); + MainViewModel.LayoutPathOverride = tempDb; + var window = new MainWindow(); + var vm = (MainViewModel)window.DataContext; + try + { + window.Show(); + window.UpdateLayout(); + + // Open the gear drawer, then click somewhere that is not the drawer host. + vm.ToggleSettingsDrawer(); + Assert.True(vm.SettingsDrawerOpen); + + RaiseOutsideClick(window); + Assert.False(vm.SettingsDrawerOpen); + + // …and clicking INSIDE the drawer must not close it, or the panel would be unusable. + vm.ToggleSettingsDrawer(); + Assert.True(vm.SettingsDrawerOpen); + var pane = (System.Windows.Controls.UserControl)window.FindName("PreviewPane")!; + var host = (FrameworkElement)pane.FindName("TextPullOutHost")!; + RaiseClickOn(host); + Assert.True(vm.SettingsDrawerOpen); + } + finally + { + window.Close(); + MainViewModel.LayoutPathOverride = null; + SqliteConnection.ClearAllPools(); + try { File.Delete(tempDb); } catch { /* best-effort cleanup */ } + } + }); + } + + /// A left-button-down that originates on the window itself — outside the preview, the left + /// panel and the drawer host, which is exactly the case the guard handles. + private static void RaiseOutsideClick(Window window) => RaiseClickOn(window); + + private static void RaiseClickOn(UIElement target) + { + var args = new MouseButtonEventArgs(Mouse.PrimaryDevice, Environment.TickCount, MouseButton.Left) + { + RoutedEvent = UIElement.PreviewMouseLeftButtonDownEvent, + Source = target, + }; + target.RaiseEvent(args); + } +}