Skip to content

Commit f9f28fc

Browse files
GabrielDufclaude
andcommitted
Avalonia: Responsive filter pane overlay mode + per-page state persistence
When the content area is < 1000px wide, the filter pane switches to overlay mode: it floats on top of the package list with a semi-transparent backdrop that dismisses it on tap, matching WinUI's SplitView overlay behaviour. Per-page filter pane open/closed state and pixel width are now persisted to settings (HideToggleFilters / SidepanelWidths) and restored on next launch, matching the WinUI implementation. Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent 29930b1 commit f9f28fc

3 files changed

Lines changed: 86 additions & 14 deletions

File tree

src/UniGetUI.Avalonia/ViewModels/SoftwarePages/PackagesPageViewModel.cs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,7 +92,11 @@ public bool IsExpanded
9292
public partial class PackagesPageViewModel : ViewModelBase
9393
{
9494
public double FilterPaneColumnWidth => IsFilterPaneOpen ? 220.0 : 0.0;
95-
partial void OnIsFilterPaneOpenChanged(bool _) => OnPropertyChanged(nameof(FilterPaneColumnWidth));
95+
partial void OnIsFilterPaneOpenChanged(bool value)
96+
{
97+
OnPropertyChanged(nameof(FilterPaneColumnWidth));
98+
Settings.SetDictionaryItem(Settings.K.HideToggleFilters, PageName, !value);
99+
}
96100

97101
// ─── Static config (set once in constructor) ──────────────────────────────
98102
public readonly string PageName;
@@ -205,6 +209,10 @@ public PackagesPageViewModel(PackagesPageData data)
205209
? (PackageViewMode)savedMode
206210
: PackageViewMode.List;
207211

212+
// Restore per-page filter pane open/closed state (default: open).
213+
// Use backing field to avoid writing to settings during construction.
214+
_isFilterPaneOpen = !Settings.GetDictionaryItem<string, bool>(Settings.K.HideToggleFilters, PageName);
215+
208216
_localPackagesNode.PackageName = CoreTools.Translate("Local");
209217

210218
if (Loader.IsLoading)

src/UniGetUI.Avalonia/Views/SoftwarePages/AbstractPackagesPage.axaml

Lines changed: 11 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -430,12 +430,22 @@
430430
</StackPanel>
431431
</ScrollViewer>
432432
<!-- ===== DRAG SPLITTER ===== -->
433-
<GridSplitter Grid.Column="1"
433+
<GridSplitter x:Name="FilterSplitter"
434+
Grid.Column="1"
434435
IsVisible="{Binding IsFilterPaneOpen}"
435436
HorizontalAlignment="Stretch"
436437
ResizeDirection="Columns"
437438
Background="{DynamicResource AppWindowBackground}"/>
438439

440+
<!-- ===== OVERLAY BACKDROP (overlay mode only, dismisses pane on click) ===== -->
441+
<Border x:Name="FilterOverlayBackdrop"
442+
Grid.Column="0"
443+
Grid.ColumnSpan="3"
444+
ZIndex="9"
445+
Background="#60000000"
446+
IsVisible="False"
447+
IsHitTestVisible="True"/>
448+
439449
<!-- ===== PACKAGE LIST ===== -->
440450
<Grid x:Name="PackagesListGrid" Grid.Column="2" Margin="0">
441451

src/UniGetUI.Avalonia/Views/SoftwarePages/AbstractPackagesPage.axaml.cs

Lines changed: 66 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,12 @@
55
using Avalonia.Input;
66
using Avalonia.Input.Platform;
77
using Avalonia.Interactivity;
8+
using Avalonia.Layout;
9+
using Avalonia.Media;
810
using Avalonia.Threading;
911
using UniGetUI.Avalonia.ViewModels.Pages;
1012
using UniGetUI.Avalonia.Views.Controls;
13+
using UniGetUI.Core.SettingsEngine;
1114
using UniGetUI.Core.Tools;
1215
using UniGetUI.PackageEngine.Interfaces;
1316
using UniGetUI.PackageEngine.PackageClasses;
@@ -20,6 +23,7 @@ public abstract partial class AbstractPackagesPage : UserControl,
2023
public PackagesPageViewModel ViewModel => (PackagesPageViewModel)DataContext!;
2124
private ContextMenu? _contextMenu;
2225
private double _savedFilterPaneWidth = 220;
26+
private bool _isOverlayMode;
2327

2428
protected AbstractPackagesPage(PackagesPageData data)
2529
{
@@ -73,23 +77,32 @@ or nameof(PackagesPageViewModel.SortAscending))
7377
// redirect focus + the typed character to the global search box.
7478
PackageList.TextInput += PackageList_TextInput;
7579

76-
// Close the filter pane when the splitter is dragged below the minimum width.
77-
// Observe ColumnDefinition.Width so the snap fires during the drag, not just
78-
// on pointer-release (pointer capture prevents PointerReleased on the splitter).
80+
// Snap-close when splitter is dragged below the minimum (inline mode only).
81+
// Using ColumnDefinition.WidthProperty fires every drag step, not just on release.
7982
FilteringPanel.ColumnDefinitions[0]
8083
.GetObservable(ColumnDefinition.WidthProperty)
8184
.Subscribe(width =>
8285
{
83-
if (!ViewModel.IsFilterPaneOpen) return;
86+
if (_isOverlayMode || !ViewModel.IsFilterPaneOpen) return;
8487
if (width.IsAbsolute && width.Value >= 100)
85-
_savedFilterPaneWidth = width.Value; // remember last good drag width
88+
{
89+
_savedFilterPaneWidth = width.Value;
90+
Settings.SetDictionaryItem(Settings.K.SidepanelWidths, ViewModel.PageName, (int)width.Value);
91+
}
8692
else if (width.IsAbsolute && width.Value < 100)
8793
{
88-
_savedFilterPaneWidth = 220; // reset to default on snap-close
94+
_savedFilterPaneWidth = 220;
8995
ViewModel.IsFilterPaneOpen = false;
9096
}
9197
});
9298

99+
// Responsive: switch between inline and overlay modes based on content width.
100+
FilteringPanel.GetObservable(BoundsProperty)
101+
.Subscribe(bounds => OnFilteringPanelWidthChanged(bounds.Width));
102+
103+
// Overlay backdrop dismisses the filter pane when tapped.
104+
FilterOverlayBackdrop.PointerPressed += (_, _) => ViewModel.IsFilterPaneOpen = false;
105+
93106
// Wire context menu (built by subclass)
94107
_contextMenu = GenerateContextMenu();
95108
if (_contextMenu is not null)
@@ -102,7 +115,11 @@ or nameof(PackagesPageViewModel.SortAscending))
102115
};
103116
}
104117

105-
// Apply the initial filter-pane state (AXAML defaults to 220px open)
118+
// Restore per-page filter pane width from settings.
119+
var savedWidth = Settings.GetDictionaryItem<string, int>(Settings.K.SidepanelWidths, ViewModel.PageName);
120+
if (savedWidth >= 100) _savedFilterPaneWidth = savedWidth;
121+
122+
// Apply the initial filter-pane state (AXAML defaults to 220px open).
106123
UpdateFilterPaneColumn(ViewModel.IsFilterPaneOpen);
107124
}
108125

@@ -280,18 +297,55 @@ private void PackageList_TextInput(object? sender, TextInputEventArgs e)
280297
}
281298

282299
// ─── Filter pane column width management ─────────────────────────────────
300+
301+
private void OnFilteringPanelWidthChanged(double width)
302+
{
303+
if (width <= 0) return; // layout not complete yet
304+
bool shouldBeOverlay = width < 1000;
305+
if (shouldBeOverlay == _isOverlayMode) return;
306+
307+
_isOverlayMode = shouldBeOverlay;
308+
309+
if (_isOverlayMode && ViewModel.IsFilterPaneOpen)
310+
ViewModel.IsFilterPaneOpen = false; // collapse pane when entering overlay
311+
else
312+
UpdateFilterPaneColumn(ViewModel.IsFilterPaneOpen);
313+
}
314+
283315
private void UpdateFilterPaneColumn(bool open)
284316
{
285317
if (FilteringPanel.ColumnDefinitions.Count < 2) return;
286-
if (open)
318+
319+
if (_isOverlayMode)
287320
{
288-
FilteringPanel.ColumnDefinitions[0].Width = new GridLength(_savedFilterPaneWidth);
289-
FilteringPanel.ColumnDefinitions[1].Width = new GridLength(4);
321+
// Package list fills full width; filter pane and splitter take no space.
322+
FilteringPanel.ColumnDefinitions[0].Width = new GridLength(0);
323+
FilteringPanel.ColumnDefinitions[1].Width = new GridLength(0);
324+
325+
// Float the filter pane on top of the content when open.
326+
Grid.SetColumnSpan(SidePanel, 3);
327+
SidePanel.ZIndex = 10;
328+
SidePanel.Width = _savedFilterPaneWidth;
329+
SidePanel.HorizontalAlignment = HorizontalAlignment.Left;
330+
331+
// Semi-transparent backdrop covers the package list behind the pane.
332+
FilterOverlayBackdrop.IsVisible = open;
290333
}
291334
else
292335
{
293-
FilteringPanel.ColumnDefinitions[0].Width = new GridLength(0);
294-
FilteringPanel.ColumnDefinitions[1].Width = new GridLength(0);
336+
// Inline mode: pane sits beside the package list.
337+
Grid.SetColumnSpan(SidePanel, 1);
338+
SidePanel.ZIndex = 0;
339+
SidePanel.Width = double.NaN;
340+
SidePanel.HorizontalAlignment = HorizontalAlignment.Stretch;
341+
FilterOverlayBackdrop.IsVisible = false;
342+
343+
FilteringPanel.ColumnDefinitions[0].Width = open
344+
? new GridLength(_savedFilterPaneWidth)
345+
: new GridLength(0);
346+
FilteringPanel.ColumnDefinitions[1].Width = open
347+
? new GridLength(4)
348+
: new GridLength(0);
295349
}
296350
}
297351

0 commit comments

Comments
 (0)