瀏覽代碼

Add cancellation token support to navigation operations and refactor navigation logic for improved consistency.

- Updated `Navigate` and `Iterate` methods in `NavigationManager` to accept optional cancellation tokens.
- Modified `CheckCancellationAndStartIterateToIndex` in `ImageLoader` to support external cancellation.
- Updated `FunctionsMapper`, `HoverBar`, and `NavigationDialog` to integrate new cancellation token parameters.
- Refactored ReactiveCommands in `NavigationViewModel` for async navigation operations.
- Adjusted benchmark and mouse shortcut implementations to align with the refactored navigation methods.
Ruben 1 周之前
父節點
當前提交
c9984abc3b

+ 8 - 5
src/PicView.Avalonia/Functions/FunctionsMapper.cs

@@ -234,7 +234,7 @@ public static class FunctionsMapper
 
     /// <inheritdoc cref="NavigationManager.Iterate(bool, MainViewModel)" />
     public static async ValueTask Next() =>
-        await NavigationManager.Iterate(next: true, Vm).ConfigureAwait(false);
+        await NavigationManager.Iterate(true, Vm, CancellationToken.None).ConfigureAwait(false);
     
     /// <inheritdoc cref="NavigationManager.NavigateBetweenDirectories(bool, MainViewModel)" />
     public static async ValueTask NextFolder() =>
@@ -246,7 +246,7 @@ public static class FunctionsMapper
 
     /// <inheritdoc cref="NavigationManager.Iterate(bool, MainViewModel)" />
     public static async ValueTask Prev() =>
-        await NavigationManager.Iterate(next: false, Vm).ConfigureAwait(false);
+        await NavigationManager.Iterate(false, Vm, CancellationToken.None).ConfigureAwait(false);
     
     /// <inheritdoc cref="NavigationManager.NavigateBetweenDirectories(bool, MainViewModel)" />
     public static async ValueTask PrevFolder() =>
@@ -405,9 +405,12 @@ public static class FunctionsMapper
         await Task.Run(() => GalleryFunctions.OpenCloseBottomGallery(Vm));
     
     /// <inheritdoc cref="GalleryFunctions.CloseGallery(MainViewModel)" />
-    public static async ValueTask CloseGallery() =>
-        await Task.Run(() => GalleryFunctions.CloseGallery(Vm));
-    
+    public static ValueTask CloseGallery()
+    {
+        GalleryFunctions.CloseGallery(Vm);
+        return ValueTask.CompletedTask;
+    }
+
     /// <inheritdoc cref="GalleryNavigation.GalleryClick(MainViewModel)" />
     public static async ValueTask GalleryClick() =>
         await GalleryNavigation.GalleryClick(Vm).ConfigureAwait(false);

+ 1 - 1
src/PicView.Avalonia/Input/MouseShortcuts.cs

@@ -173,7 +173,7 @@ public static class MouseShortcuts
         }
 
         var next = reverse ? Settings.Zoom.HorizontalReverseScroll : !Settings.Zoom.HorizontalReverseScroll;
-        await NavigationManager.Navigate(next, mainViewModel).ConfigureAwait(false);
+        await NavigationManager.Navigate(next, mainViewModel, CancellationToken.None).ConfigureAwait(false);
     }
     
     public static async Task MainWindow_PointerPressed(PointerPressedEventArgs e)

+ 6 - 6
src/PicView.Avalonia/Navigation/ImageLoader.cs

@@ -454,12 +454,8 @@ public static class ImageLoader
             .NextIteration(NavigateTo.First, _cancellationTokenSource)
             .ConfigureAwait(false);
 
-    /// <summary>
-    ///     Checks if the previous iteration has been canceled and starts the iteration at the given index
-    /// </summary>
-    /// <param name="index">The index to iterate to.</param>
-    /// <param name="imageIterator">The ImageIterator instance.</param>
-    public static async ValueTask CheckCancellationAndStartIterateToIndex(int index, ImageIterator imageIterator)
+    public static async ValueTask CheckCancellationAndStartIterateToIndex(int index, ImageIterator imageIterator,
+        CancellationToken? cancellationToken)
     {
         if (_cancellationTokenSource is not null)
         {
@@ -467,6 +463,10 @@ public static class ImageLoader
         }
 
         _cancellationTokenSource = new CancellationTokenSource();
+        if (cancellationToken is not null)
+        {
+            CancellationTokenSource.CreateLinkedTokenSource(cancellationToken.Value, _cancellationTokenSource.Token);
+        }
         await imageIterator.NextIteration(index, _cancellationTokenSource).ConfigureAwait(false);
     }
 

+ 29 - 18
src/PicView.Avalonia/Navigation/NavigationManager.cs

@@ -150,12 +150,13 @@ public static class NavigationManager
         !DialogManager.IsDialogOpen && vm is { MainWindow.IsEditableTitlebarOpen.CurrentValue: false, PicViewer.FileInfo.CurrentValue: not null };
 
     /// <summary>
-    ///     Navigates to the next or previous image based on the <paramref name="next" /> parameter.
+    /// Navigates to the next or previous image based on the specified direction, ensuring all conditions for navigation are met.
     /// </summary>
-    /// <param name="next">True to navigate to the next image, false for the previous image.</param>
-    /// <param name="vm">The main view model instance.</param>
-    /// <returns>A task representing the asynchronous operation.</returns>
-    public static async ValueTask Navigate(bool next, MainViewModel vm)
+    /// <param name="next">Indicates the navigation direction. True for next image and false for previous image.</param>
+    /// <param name="vm">The main view model instance used for managing the application's state and data.</param>
+    /// <param name="cancellationToken">Optional: A cancellation token to handle task cancellation during navigation operations.</param>
+    /// <returns>A ValueTask representing the asynchronous navigation operation.</returns>
+    public static async ValueTask Navigate(bool next, MainViewModel vm, CancellationToken? cancellationToken)
     {
         if (!CanNavigate(vm))
         {
@@ -211,7 +212,7 @@ public static class NavigationManager
         }
         else
         {
-            await ImageLoader.CheckCancellationAndStartIterateToIndex(nextIteration, ImageIterator)
+            await ImageLoader.CheckCancellationAndStartIterateToIndex(nextIteration, ImageIterator, cancellationToken)
                 .ConfigureAwait(false);
         }
     }
@@ -223,7 +224,8 @@ public static class NavigationManager
             var tiffPages = await Task.FromResult(TiffManager.LoadTiffPages(currentFileName)).ConfigureAwait(false);
             if (tiffPages.Count < 1)
             {
-                await ImageLoader.CheckCancellationAndStartIterateToIndex(nextIteration, ImageIterator)
+                await ImageLoader
+                    .CheckCancellationAndStartIterateToIndex(nextIteration, ImageIterator, CancellationToken.None)
                     .ConfigureAwait(false);
                 return;
             }
@@ -238,7 +240,8 @@ public static class NavigationManager
 
         if (TiffNavigationInfo is null)
         {
-            await ImageLoader.CheckCancellationAndStartIterateToIndex(nextIteration, ImageIterator)
+            await ImageLoader
+                .CheckCancellationAndStartIterateToIndex(nextIteration, ImageIterator, CancellationToken.None)
                 .ConfigureAwait(false);
         }
         else
@@ -273,7 +276,8 @@ public static class NavigationManager
 
         async ValueTask ExitTiffNavigationAndNavigate()
         {
-            await ImageLoader.CheckCancellationAndStartIterateToIndex(nextIteration, ImageIterator)
+            await ImageLoader
+                .CheckCancellationAndStartIterateToIndex(nextIteration, ImageIterator, CancellationToken.None)
                 .ConfigureAwait(false);
             TiffNavigationInfo?.Dispose();
             TiffNavigationInfo = null;
@@ -310,7 +314,8 @@ public static class NavigationManager
             return;
         }
 
-        await ImageLoader.CheckCancellationAndStartIterateToIndex(index, ImageIterator).ConfigureAwait(false);
+        await ImageLoader.CheckCancellationAndStartIterateToIndex(index, ImageIterator, CancellationToken.None)
+            .ConfigureAwait(false);
     }
 
     public static async ValueTask Navigate(FileInfo fileInfo, MainViewModel vm)
@@ -326,7 +331,8 @@ public static class NavigationManager
             return;
         }
 
-        await ImageLoader.CheckCancellationAndStartIterateToIndex(index, ImageIterator).ConfigureAwait(false);
+        await ImageLoader.CheckCancellationAndStartIterateToIndex(index, ImageIterator, CancellationToken.None)
+            .ConfigureAwait(false);
     }
     
     public static async ValueTask NavigateIncrements(bool next, bool is10, bool is100) =>
@@ -343,7 +349,8 @@ public static class NavigationManager
         var direction = next ? NavigateTo.Next : NavigateTo.Previous;
         var index = ImageIterator.GetIteration(currentIndex, direction, false, is10, is100);
 
-        await ImageLoader.CheckCancellationAndStartIterateToIndex(index, ImageIterator).ConfigureAwait(false);
+        await ImageLoader.CheckCancellationAndStartIterateToIndex(index, ImageIterator, CancellationToken.None)
+            .ConfigureAwait(false);
     }
 
     public static async ValueTask LoadLastFileAsync(MainViewModel vm)
@@ -405,12 +412,14 @@ public static class NavigationManager
         await NavigateFirstOrLast(last, UIHelper.GetMainView.DataContext as MainViewModel);
 
     /// <summary>
-    ///     Iterates to the next or previous image based on the <paramref name="next" /> parameter.
+    /// Iterates through the gallery or navigates between images depending on the current state.
+    /// If the full gallery is open, it navigates through the gallery. Otherwise, it navigates between images.
     /// </summary>
-    /// <param name="next">True to iterate to the next image, false for the previous image.</param>
+    /// <param name="next">Indicates the direction of iteration. If true, iterates to the next item; otherwise, iterates to the previous item.</param>
     /// <param name="vm">The main view model instance.</param>
-    /// <returns>A task representing the asynchronous operation.</returns>
-    public static async ValueTask Iterate(bool next, MainViewModel vm)
+    /// <param name="cancellationToken">The token to monitor for cancellation requests.</param>
+    /// <returns>A task that represents the asynchronous operation.</returns>
+    public static async ValueTask Iterate(bool next, MainViewModel vm, CancellationToken? cancellationToken)
     {
         if (GalleryFunctions.IsFullGalleryOpen)
         {
@@ -418,10 +427,12 @@ public static class NavigationManager
         }
         else
         {
-            await Navigate(next, vm);
+            await Navigate(next, vm, cancellationToken);
         }
     }
-    public static async ValueTask Iterate(bool next) => await Iterate(next, UIHelper.GetMainView.DataContext as MainViewModel).ConfigureAwait(false);
+
+    public static async ValueTask Iterate(bool next, CancellationToken cancellationToken) =>
+        await Iterate(next, UIHelper.GetMainView.DataContext as MainViewModel, cancellationToken).ConfigureAwait(false);
 
     /// <summary>
     ///     Navigates to the next or previous folder and loads the first image in that folder.

+ 1 - 1
src/PicView.Avalonia/Navigation/Slideshow.cs

@@ -86,7 +86,7 @@ public static class Slideshow
                 // https://docs.avaloniaui.net/docs/guides/graphics-and-animation/page-transitions/how-to-create-a-custom-page-transition
                 // https://docs.avaloniaui.net/docs/guides/graphics-and-animation/page-transitions/page-slide-transition
                 // https://docs.avaloniaui.net/docs/reference/controls/transitioningcontentcontrol
-                await NavigationManager.Navigate(true, vm).ConfigureAwait(false);
+                await NavigationManager.Navigate(true, vm, CancellationToken.None).ConfigureAwait(false);
             };
         }
         else if (_timer.Enabled)

+ 8 - 4
src/PicView.Avalonia/ViewModels/NavigationViewModel.cs

@@ -14,16 +14,20 @@ public class NavigationViewModel : IDisposable
     });
     
     // Next
-    public ReactiveCommand NextCommand { get; } = new(Next); // Don't want to await, since it can make it feel slow
-    private static void Next(Unit unit) => _ = NavigationManager.Iterate(next: true);
+    public ReactiveCommand NextCommand { get; } = new(Next);
+
+    private static async ValueTask Next(Unit unit, CancellationToken token) =>
+        await NavigationManager.Iterate(true, token);
     public ReactiveCommand NextFolderCommand { get; } = new(async (_, _) =>
     {
         await NavigationManager.NavigateBetweenDirectories(next: true).ConfigureAwait(false);
     });
     
     // Prev
-    public ReactiveCommand PreviousCommand { get; } = new(Prev); // Don't want to await, since it can make it feel slow
-    private static void Prev(Unit unit) => _ = NavigationManager.Iterate(next: false);
+    public ReactiveCommand PreviousCommand { get; } = new(Prev);
+
+    private static async ValueTask Prev(Unit unit, CancellationToken token) =>
+        await NavigationManager.Iterate(false, token);
 
     public ReactiveCommand PreviousFolderCommand { get; } = new(async (_, _) =>
     {

+ 5 - 4
src/PicView.Avalonia/Views/UC/HoverBar.axaml.cs

@@ -4,6 +4,7 @@ using Avalonia.Input;
 using Avalonia.Interactivity;
 using Avalonia.LogicalTree;
 using PicView.Avalonia.Functions;
+using PicView.Avalonia.Navigation;
 using PicView.Avalonia.UI;
 using PicView.Avalonia.ViewModels;
 using PicView.Avalonia.Views.UC.PopUps;
@@ -32,17 +33,17 @@ public partial class HoverBar : UserControl
 
         Observable.FromEventHandler<RoutedEventArgs>(h => NextButton.Click += h,
                 h => NextButton.Click -= h)
-            .SubscribeAwait(async (_, _) =>
+            .SubscribeAwait(async (_, c) =>
             {
                 vm.HoverbarViewModel.IsHoverNavigationButtonNextClicked = true;
-                await FunctionsMapper.Next();
+                await NavigationManager.Navigate(true, vm, c);
             });
         Observable.FromEventHandler<RoutedEventArgs>(h => PreviousButton.Click += h,
                 h => PreviousButton.Click -= h)
-            .SubscribeAwait(async (_, _) =>
+            .SubscribeAwait(async (_, c) =>
             {
                 vm.HoverbarViewModel.IsHoverNavigationButtonPreviousClicked = true;
-                await FunctionsMapper.Prev();
+                await NavigationManager.Navigate(false, vm, c);
             });
     }
 

+ 5 - 4
src/PicView.Avalonia/Views/UC/PopUps/NavigationDialog.axaml.cs

@@ -2,6 +2,7 @@
 using Avalonia.LogicalTree;
 using PicView.Avalonia.CustomControls;
 using PicView.Avalonia.Functions;
+using PicView.Avalonia.Navigation;
 using PicView.Avalonia.UI;
 using PicView.Avalonia.ViewModels;
 using R3;
@@ -25,19 +26,19 @@ public partial class NavigationDialog : AnimatedPopUp
 
         Observable.FromEventHandler<RoutedEventArgs>(h => NextButton.Click += h,
                 h => NextButton.Click -= h)
-            .SubscribeAwait(async (_, _) =>
+            .SubscribeAwait(async (s, c) =>
             {
                 _ = AnimatedClosing();
-                await FunctionsMapper.Next();
+                await NavigationManager.Navigate(true, UIHelper.GetMainView.DataContext as MainViewModel, c);
             })
             .AddTo(_subscriptions);
         
         Observable.FromEventHandler<RoutedEventArgs>(h => PrevButton.Click += h,
                 h => PrevButton.Click -= h)
-            .SubscribeAwait(async (_, _) =>
+            .SubscribeAwait(async (s, c) =>
             {
                 _ = AnimatedClosing();
-                await FunctionsMapper.Prev();
+                await NavigationManager.Navigate(false, UIHelper.GetMainView.DataContext as MainViewModel, c);
             })
             .AddTo(_subscriptions);
         

+ 3 - 3
src/PicView.Benchmarks/Program.cs

@@ -1,9 +1,9 @@
 using BenchmarkDotNet.Running;
-using PicView.Benchmarks.StringBenchmarks;
+using PicView.Benchmarks.ImageBenchmarks;
 
 // BenchmarkRunner.Run<EvictingDictionaryBenchmark>();
 // BenchmarkRunner.Run<ImageBenchmarks>();
-//BenchmarkRunner.Run<PreloadingBenchmark>();
+BenchmarkRunner.Run<PreloadingBenchmark>();
 // BenchmarkRunner.Run<TranslationBenchmarks>();
 //BenchmarkRunner.Run<LanguageBenchmark>();
-BenchmarkRunner.Run<FileSizeBenchmark>();
+//BenchmarkRunner.Run<FileSizeBenchmark>();