added scroll reset and tab persistance

This commit is contained in:
Ebbe Baß
2026-08-19 15:23:49 +02:00
parent 62a257b538
commit 52ca49ac85
4 changed files with 41 additions and 2 deletions
@@ -20,4 +20,13 @@ public sealed class AppSettings
/// costs noticeably more CPU and memory than showing static first frames.
/// </summary>
public bool PlayGifPreviews { get; set; }
/// <summary>
/// Which tab the picker reopens on. Persisted so the choice survives an app restart, not
/// just a hide/show. Defaults to the favourites view, since that is where the memes you
/// actually reach for most often live.
/// </summary>
public MemeSource LastSource { get; set; } = MemeSource.Local;
public bool LastShowingFavorites { get; set; } = true;
}
@@ -99,7 +99,16 @@ public partial class PickerViewModel : ObservableObject
_providers = providers.ToList();
Items.CollectionChanged += OnItemsChanged;
RefreshLocalItems();
// Restore the tab the picker was last left on. Assigned to the backing fields directly
// so restoring doesn't count as a user change and immediately re-save.
_activeSource = _settings.Current.LastSource;
_showingFavorites = _settings.Current.LastShowingFavorites;
// Seed the grid for the restored view. Remote sources are skipped here because that
// needs an async call - OnShown covers it when the window is actually opened.
if (_showingFavorites) RefreshFavoriteItems();
else if (IsLocalSource) RefreshLocalItems();
}
private void OnItemsChanged(object? sender, NotifyCollectionChangedEventArgs e) =>
@@ -116,6 +125,7 @@ public partial class PickerViewModel : ObservableObject
OnPropertyChanged(nameof(EmptyStateText));
OnPropertyChanged(nameof(ShowEmptyState));
SearchText = string.Empty;
PersistViewState();
_ = RefreshAsync();
}
@@ -127,11 +137,19 @@ public partial class PickerViewModel : ObservableObject
OnPropertyChanged(nameof(CanImport));
OnPropertyChanged(nameof(ShowEmptyState));
SearchText = string.Empty;
PersistViewState();
// Setting SearchText above only triggers a refresh if the value actually changed, so
// refresh explicitly here to cover switching tabs with an already-empty box.
_ = RefreshAsync();
}
private void PersistViewState()
{
_settings.Current.LastSource = ActiveSource;
_settings.Current.LastShowingFavorites = ShowingFavorites;
_ = _settings.SaveAsync();
}
[RelayCommand]
private void SelectSource(MemeSource source) => ActiveSource = source;
@@ -202,7 +202,8 @@
ToolTip="Add memes"/>
</Grid>
<ScrollViewer Grid.Row="3" VerticalScrollBarVisibility="Auto" Padding="0,0,4,0"
<ScrollViewer x:Name="ResultsScrollViewer"
Grid.Row="3" VerticalScrollBarVisibility="Auto" Padding="0,0,4,0"
ScrollChanged="ResultsScrollViewer_OnScrollChanged">
<Grid>
<TextBlock Text="{Binding EmptyStateText}"
@@ -29,6 +29,8 @@ public partial class PickerWindow : Window
_autoPaste.CaptureForegroundWindow();
PositionNearCursor();
_viewModel.OnShown();
// Always reopen at the top rather than wherever the last session was left scrolled to.
ResultsScrollViewer.ScrollToTop();
Show();
Activate();
SearchBox.Focus();
@@ -88,6 +90,15 @@ public partial class PickerWindow : Window
{
Activate();
}
// Switching tab/view or typing replaces the whole grid, so a retained scroll offset
// would leave the user part-way down a completely different set of results.
if (e.PropertyName is nameof(PickerViewModel.ActiveSource)
or nameof(PickerViewModel.ShowingFavorites)
or nameof(PickerViewModel.SearchText))
{
ResultsScrollViewer.ScrollToTop();
}
}
private void PickerWindow_OnDeactivated(object? sender, EventArgs e)