From de9037be75e3546d26e873554dcea48434ebea76 Mon Sep 17 00:00:00 2001 From: GabrielDuf Date: Mon, 20 Jul 2026 09:27:58 -0400 Subject: [PATCH 1/2] auto-size the operations panel to its rows, capped at 3 --- src/UniGetUI.Avalonia/Views/MainWindow.axaml | 3 +- .../Views/MainWindow.axaml.cs | 62 ++++++++++++++++--- 2 files changed, 57 insertions(+), 8 deletions(-) diff --git a/src/UniGetUI.Avalonia/Views/MainWindow.axaml b/src/UniGetUI.Avalonia/Views/MainWindow.axaml index 229868562d..df75b093b0 100644 --- a/src/UniGetUI.Avalonia/Views/MainWindow.axaml +++ b/src/UniGetUI.Avalonia/Views/MainWindow.axaml @@ -119,7 +119,8 @@ - BackButton.IsVisible = canGoBack; ViewModel.PropertyChanged += OnViewModelPropertyChanged; + ViewModel.Operations.CollectionChanged += OnOperationsCollectionChanged; + OperationsSplitter.DragCompleted += OnOperationsSplitterDragCompleted; UpdateOperationsPanelRow(); Resized += (_, _) => _ = SaveGeometryAsync(); @@ -292,7 +296,10 @@ private void OnViewModelPropertyChanged(object? sender, System.ComponentModel.Pr { if (e.PropertyName is nameof(MainWindowViewModel.OperationsPanelExpanded) or nameof(MainWindowViewModel.OperationsPanelVisible)) + { UpdateOperationsPanelRow(); + ScheduleOperationsAutoSize(); + } // Expanding the panel while an operation has failed jumps to that op (the failure // badge on the chevron is what drew the user here). @@ -314,10 +321,6 @@ private void ScrollToFirstFailedOperation() }, DispatcherPriority.Background); } - // Drive the operations-panel grid row: a resizable pixel height while the panel is - // open, Auto otherwise so it collapses to just the toolbar (or vanishes when empty). - // The GridSplitter writes the row height directly, so we read it back to preserve the - // user's chosen size across collapse/expand. private void UpdateOperationsPanelRow() { if (ContentRoot.RowDefinitions.Count < 3) @@ -326,18 +329,63 @@ private void UpdateOperationsPanelRow() RowDefinition row = ContentRoot.RowDefinitions[2]; if (ViewModel.OperationsPanelVisible && ViewModel.OperationsPanelExpanded) { - row.MinHeight = 80; - row.Height = new GridLength(_operationsPanelHeight, GridUnitType.Pixel); + row.MinHeight = 48; + double height = _userResizedPanel ? _operationsPanelHeight : ComputeOperationsAutoHeight(); + if (height <= 0) + height = _operationsPanelHeight; + row.Height = new GridLength(height, GridUnitType.Pixel); } else { - if (row.Height.IsAbsolute && row.Height.Value > 0) + if (_userResizedPanel && row.Height.IsAbsolute && row.Height.Value > 0) _operationsPanelHeight = row.Height.Value; row.MinHeight = 0; row.Height = GridLength.Auto; } } + private double ComputeOperationsAutoHeight() + { + int count = ViewModel.Operations.Count; + if (count == 0) + return 0; + + const double chrome = 42; + const double fallbackRow = 68; + + double rows = 0; + int visible = Math.Min(count, 3); + for (int i = 0; i < visible; i++) + rows += (OperationsList.ContainerFromIndex(i) as Control)?.Bounds.Height ?? fallbackRow; + + return chrome + rows; + } + + private void ScheduleOperationsAutoSize() + { + if (_userResizedPanel) + return; + Dispatcher.UIThread.Post(UpdateOperationsPanelRow, DispatcherPriority.Loaded); + } + + private void OnOperationsCollectionChanged(object? sender, NotifyCollectionChangedEventArgs e) + { + if (ViewModel.Operations.Count == 0) + _userResizedPanel = false; + ScheduleOperationsAutoSize(); + } + + private void OnOperationsSplitterDragCompleted(object? sender, VectorEventArgs e) + { + if (ContentRoot.RowDefinitions.Count >= 3) + { + RowDefinition row = ContentRoot.RowDefinitions[2]; + if (row.Height.IsAbsolute && row.Height.Value > 0) + _operationsPanelHeight = row.Height.Value; + } + _userResizedPanel = true; + } + // ─── Navigation rail / docked pane (responsive) ──────────────────────────── // Feed the live window width to the sidebar, which resolves the layout mode and drives // the NavRail / NavDock / overlay visibility bindings. From 13960c30574fb72ec3c8e085caf95579fbdf0229 Mon Sep 17 00:00:00 2001 From: GabrielDuf Date: Mon, 20 Jul 2026 13:49:11 -0400 Subject: [PATCH 2/2] re-fit panel when badges appear; ignore collapsed splitter drags --- .../ViewModels/MainWindowViewModel.cs | 4 +++ src/UniGetUI.Avalonia/Views/MainWindow.axaml | 2 +- .../Views/MainWindow.axaml.cs | 25 +++++++++++++++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs b/src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs index cfa40a472d..c6aba4a818 100644 --- a/src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs +++ b/src/UniGetUI.Avalonia/ViewModels/MainWindowViewModel.cs @@ -65,12 +65,16 @@ public partial class MainWindowViewModel : ViewModelBase [ObservableProperty] [NotifyPropertyChangedFor(nameof(ShowFailedOperationBadge))] + [NotifyPropertyChangedFor(nameof(OperationsSplitterVisible))] private bool _operationsPanelVisible; [ObservableProperty] [NotifyPropertyChangedFor(nameof(ShowFailedOperationBadge))] + [NotifyPropertyChangedFor(nameof(OperationsSplitterVisible))] private bool _operationsPanelExpanded = true; + public bool OperationsSplitterVisible => OperationsPanelVisible && OperationsPanelExpanded; + private readonly List _operationBatch = new(); private bool _batchSummaryShown; diff --git a/src/UniGetUI.Avalonia/Views/MainWindow.axaml b/src/UniGetUI.Avalonia/Views/MainWindow.axaml index df75b093b0..07e55a929f 100644 --- a/src/UniGetUI.Avalonia/Views/MainWindow.axaml +++ b/src/UniGetUI.Avalonia/Views/MainWindow.axaml @@ -123,7 +123,7 @@ Grid.Row="1" HorizontalAlignment="Stretch" VerticalAlignment="Stretch" - IsVisible="{Binding OperationsPanelVisible}" + IsVisible="{Binding OperationsSplitterVisible}" automation:AutomationProperties.AccessibilityView="Raw" Background="{DynamicResource AppSplitterBackground}" Opacity="0.8"/> diff --git a/src/UniGetUI.Avalonia/Views/MainWindow.axaml.cs b/src/UniGetUI.Avalonia/Views/MainWindow.axaml.cs index f8c3b3d6fb..0bc5667010 100644 --- a/src/UniGetUI.Avalonia/Views/MainWindow.axaml.cs +++ b/src/UniGetUI.Avalonia/Views/MainWindow.axaml.cs @@ -1,4 +1,5 @@ using System; +using System.Collections.Generic; using System.Collections.Specialized; using System.Runtime.InteropServices; using System.Threading; @@ -105,6 +106,7 @@ public partial class MainWindow : Window // Last user-chosen height (px) of the operations panel; restored when re-expanded. private double _operationsPanelHeight = 240; private bool _userResizedPanel; + private readonly HashSet _badgeSubscriptions = new(); public enum RuntimeNotificationLevel { @@ -136,6 +138,7 @@ public MainWindow() ViewModel.PropertyChanged += OnViewModelPropertyChanged; ViewModel.Operations.CollectionChanged += OnOperationsCollectionChanged; OperationsSplitter.DragCompleted += OnOperationsSplitterDragCompleted; + SyncBadgeSubscriptions(); UpdateOperationsPanelRow(); Resized += (_, _) => _ = SaveGeometryAsync(); @@ -372,11 +375,33 @@ private void OnOperationsCollectionChanged(object? sender, NotifyCollectionChang { if (ViewModel.Operations.Count == 0) _userResizedPanel = false; + SyncBadgeSubscriptions(); ScheduleOperationsAutoSize(); } + private void SyncBadgeSubscriptions() + { + _badgeSubscriptions.RemoveWhere(vm => + { + if (ViewModel.Operations.Contains(vm)) + return false; + vm.Badges.CollectionChanged -= OnOperationBadgesChanged; + return true; + }); + + foreach (OperationViewModel vm in ViewModel.Operations) + if (_badgeSubscriptions.Add(vm)) + vm.Badges.CollectionChanged += OnOperationBadgesChanged; + } + + private void OnOperationBadgesChanged(object? sender, NotifyCollectionChangedEventArgs e) + => ScheduleOperationsAutoSize(); + private void OnOperationsSplitterDragCompleted(object? sender, VectorEventArgs e) { + if (!ViewModel.OperationsPanelVisible || !ViewModel.OperationsPanelExpanded) + return; + if (ContentRoot.RowDefinitions.Count >= 3) { RowDefinition row = ContentRoot.RowDefinitions[2];