55using Avalonia . Input ;
66using Avalonia . Input . Platform ;
77using Avalonia . Interactivity ;
8+ using Avalonia . Layout ;
9+ using Avalonia . Media ;
810using Avalonia . Threading ;
911using UniGetUI . Avalonia . ViewModels . Pages ;
1012using UniGetUI . Avalonia . Views . Controls ;
13+ using UniGetUI . Core . SettingsEngine ;
1114using UniGetUI . Core . Tools ;
1215using UniGetUI . PackageEngine . Interfaces ;
1316using 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