Browse Source

Improve cancellation

Ruben 9 months ago
parent
commit
357d1d30d4

+ 1 - 2
src/PicView.Avalonia/Gallery/GalleryFunctions.cs

@@ -164,8 +164,7 @@ public static class GalleryFunctions
                         await ToggleGallery(vm);
                         await ToggleGallery(vm);
                     }
                     }
 
 
-                    await vm.ImageIterator.IterateToIndex(vm.ImageIterator.ImagePaths.IndexOf(fileInfo.FullName))
-                        .ConfigureAwait(false);
+                    await NavigationHelper.Navigate(vm.ImageIterator.ImagePaths.IndexOf(fileInfo.FullName), vm).ConfigureAwait(false);
                 };
                 };
                 galleryListBox.Items.Insert(index, galleryItem);
                 galleryListBox.Items.Insert(index, galleryItem);
                 var isSvg = fileInfo.Extension.Equals(".svg", StringComparison.OrdinalIgnoreCase) ||
                 var isSvg = fileInfo.Extension.Equals(".svg", StringComparison.OrdinalIgnoreCase) ||

+ 2 - 1
src/PicView.Avalonia/Gallery/GalleryLoad.cs

@@ -1,6 +1,7 @@
 using Avalonia.Svg.Skia;
 using Avalonia.Svg.Skia;
 using Avalonia.Threading;
 using Avalonia.Threading;
 using PicView.Avalonia.ImageHandling;
 using PicView.Avalonia.ImageHandling;
+using PicView.Avalonia.Navigation;
 using PicView.Avalonia.UI;
 using PicView.Avalonia.UI;
 using PicView.Avalonia.ViewModels;
 using PicView.Avalonia.ViewModels;
 using PicView.Avalonia.Views.UC;
 using PicView.Avalonia.Views.UC;
@@ -108,7 +109,7 @@ public static class GalleryLoad
                         {
                         {
                             await GalleryFunctions.ToggleGallery(vm);
                             await GalleryFunctions.ToggleGallery(vm);
                         }
                         }
-                        await vm.ImageIterator.IterateToIndex(vm.ImageIterator.ImagePaths.IndexOf(fileInfos[i1].FullName)).ConfigureAwait(false);
+                        await NavigationHelper.Navigate(vm.ImageIterator.ImagePaths.IndexOf(fileInfos[i1].FullName), vm).ConfigureAwait(false);
                     };
                     };
                     galleryListBox.Items.Add(galleryItem);
                     galleryListBox.Items.Add(galleryItem);
                     if (i != vm.ImageIterator?.CurrentIndex)
                     if (i != vm.ImageIterator?.CurrentIndex)

+ 2 - 1
src/PicView.Avalonia/Gallery/GalleryNavigation.cs

@@ -1,5 +1,6 @@
 using Avalonia;
 using Avalonia;
 using Avalonia.Threading;
 using Avalonia.Threading;
+using PicView.Avalonia.Navigation;
 using PicView.Avalonia.UI;
 using PicView.Avalonia.UI;
 using PicView.Avalonia.ViewModels;
 using PicView.Avalonia.ViewModels;
 using PicView.Avalonia.Views.UC;
 using PicView.Avalonia.Views.UC;
@@ -178,7 +179,7 @@ public static class GalleryNavigation
         await GalleryFunctions.ToggleGallery(vm);
         await GalleryFunctions.ToggleGallery(vm);
         if (vm.SelectedGalleryItemIndex != vm.ImageIterator.CurrentIndex) 
         if (vm.SelectedGalleryItemIndex != vm.ImageIterator.CurrentIndex) 
         {
         {
-            await vm.ImageIterator.IterateToIndex(vm.SelectedGalleryItemIndex);
+            await NavigationHelper.Navigate(vm.SelectedGalleryItemIndex, vm).ConfigureAwait(false);
         }
         }
     }
     }
 }
 }

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

@@ -91,6 +91,6 @@ public static class ErrorHandling
         vm.ImageIterator.Clear();
         vm.ImageIterator.Clear();
         await vm.ImageIterator.ReloadFileList().ConfigureAwait(false);
         await vm.ImageIterator.ReloadFileList().ConfigureAwait(false);
         var index = vm.ImageIterator.ImagePaths.IndexOf(vm.FileInfo.FullName);
         var index = vm.ImageIterator.ImagePaths.IndexOf(vm.FileInfo.FullName);
-        await vm.ImageIterator.IterateToIndex(index).ConfigureAwait(false);
+        await NavigationHelper.Navigate(index, vm).ConfigureAwait(false);
     }
     }
 }
 }

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

@@ -147,7 +147,7 @@ public static class FileHistoryNavigation
                 return;
                 return;
             }
             }
 
 
-            await vm.ImageIterator.IterateToIndex(imagePaths.IndexOf(nextEntry)).ConfigureAwait(false);
+            await NavigationHelper.Navigate(imagePaths.IndexOf(nextEntry), vm).ConfigureAwait(false);
             return;
             return;
         }
         }
         await NavigationHelper.LoadPicFromStringAsync(nextEntry, vm);
         await NavigationHelper.LoadPicFromStringAsync(nextEntry, vm);

+ 33 - 18
src/PicView.Avalonia/Navigation/ImageIterator.cs

@@ -385,7 +385,7 @@ public sealed class ImageIterator : IDisposable
     public async Task QuickReload()
     public async Task QuickReload()
     {
     {
         RemoveCurrentItemFromPreLoader();
         RemoveCurrentItemFromPreLoader();
-        await IterateToIndex(CurrentIndex).ConfigureAwait(false);
+        await IterateToIndex(CurrentIndex, new CancellationTokenSource()).ConfigureAwait(false);
     }
     }
 
 
     public int GetIteration(int index, NavigateTo navigateTo, bool skip1 = false, bool skip10 = false, bool skip100 = false)
     public int GetIteration(int index, NavigateTo navigateTo, bool skip1 = false, bool skip10 = false, bool skip100 = false)
@@ -442,7 +442,7 @@ public sealed class ImageIterator : IDisposable
         return next;
         return next;
     }
     }
 
 
-    public async Task NextIteration(NavigateTo navigateTo, CancellationToken cancellationToken = default)
+    public async Task NextIteration(NavigateTo navigateTo, CancellationTokenSource cts)
     {
     {
         var index = GetIteration(CurrentIndex, navigateTo, SettingsHelper.Settings.ImageScaling.ShowImageSideBySide);
         var index = GetIteration(CurrentIndex, navigateTo, SettingsHelper.Settings.ImageScaling.ShowImageSideBySide);
         if (index < 0)
         if (index < 0)
@@ -452,27 +452,27 @@ public sealed class ImageIterator : IDisposable
 
 
         if (!MainKeyboardShortcuts.IsKeyHeldDown)
         if (!MainKeyboardShortcuts.IsKeyHeldDown)
         {
         {
-            await IterateToIndex(index, cancellationToken).ConfigureAwait(false);
+            await IterateToIndex(index, cts).ConfigureAwait(false);
         }
         }
         else
         else
         {
         {
-            await TimerIteration(index).ConfigureAwait(false);
+            await TimerIteration(index, cts).ConfigureAwait(false);
         }
         }
     }
     }
     
     
-    public async Task Next10Iteration(bool forwards)
+    public async Task Next10Iteration(bool forwards, CancellationTokenSource cts)
     {
     {
         var index = GetIteration(CurrentIndex, forwards ? NavigateTo.Next : NavigateTo.Previous, false, true);
         var index = GetIteration(CurrentIndex, forwards ? NavigateTo.Next : NavigateTo.Previous, false, true);
-        await IterateToIndex(index).ConfigureAwait(false);
+        await IterateToIndex(index, cts).ConfigureAwait(false);
     }
     }
     
     
-    public async Task Next100Iteration(bool forwards)
+    public async Task Next100Iteration(bool forwards, CancellationTokenSource cts)
     {
     {
         var index = GetIteration(CurrentIndex, forwards ? NavigateTo.Next : NavigateTo.Previous, false, false, true);
         var index = GetIteration(CurrentIndex, forwards ? NavigateTo.Next : NavigateTo.Previous, false, false, true);
-        await IterateToIndex(index).ConfigureAwait(false);
+        await IterateToIndex(index, cts).ConfigureAwait(false);
     }
     }
 
 
-    public async Task IterateToIndex(int index, CancellationToken cancellationToken = default)
+    public async Task IterateToIndex(int index, CancellationTokenSource cts)
     {
     {
         if (index < 0 || index >= ImagePaths.Count)
         if (index < 0 || index >= ImagePaths.Count)
         {
         {
@@ -480,9 +480,20 @@ public sealed class ImageIterator : IDisposable
             return;
             return;
         }
         }
         
         
-        using var cts = CancellationTokenSource.CreateLinkedTokenSource(cancellationToken);
         // UI is more responsive when started in new thread
         // UI is more responsive when started in new thread
-        await Task.Run(async () =>
+        var isMainThread = Dispatcher.UIThread.CheckAccess();
+        if (isMainThread)
+        {
+            await Task.Run(async () => { await Iterate(); }, cts.Token).ConfigureAwait(false);
+        }
+        else
+        {
+            await Iterate().ConfigureAwait(false);
+        }
+
+        return;
+
+        async Task Iterate()
         {
         {
             try
             try
             {
             {
@@ -499,7 +510,7 @@ public sealed class ImageIterator : IDisposable
 
 
                     while (preloadValue.IsLoading)
                     while (preloadValue.IsLoading)
                     {
                     {
-                        await Task.Delay(20, cancellationToken).ConfigureAwait(false);
+                        await Task.Delay(20, cts.Token).ConfigureAwait(false);
                         if (CurrentIndex != index)
                         if (CurrentIndex != index)
                         {
                         {
                             // Skip loading if user went to next value
                             // Skip loading if user went to next value
@@ -536,13 +547,13 @@ public sealed class ImageIterator : IDisposable
                     {
                     {
                         _vm.SecondaryImageSource = nextPreloadValue.ImageModel?.Image;
                         _vm.SecondaryImageSource = nextPreloadValue.ImageModel?.Image;
                     }
                     }
-                    cancellationToken.ThrowIfCancellationRequested();
+                    cts.Token.ThrowIfCancellationRequested();
                     await UpdateImage.UpdateSource(_vm, index, ImagePaths, IsReversed, preloadValue, nextPreloadValue)
                     await UpdateImage.UpdateSource(_vm, index, ImagePaths, IsReversed, preloadValue, nextPreloadValue)
                         .ConfigureAwait(false);
                         .ConfigureAwait(false);
                 }
                 }
                 else
                 else
                 {
                 {
-                    cancellationToken.ThrowIfCancellationRequested();
+                    cts.Token.ThrowIfCancellationRequested();
                     await UpdateImage.UpdateSource(_vm, index, ImagePaths, IsReversed, preloadValue)
                     await UpdateImage.UpdateSource(_vm, index, ImagePaths, IsReversed, preloadValue)
                         .ConfigureAwait(false);
                         .ConfigureAwait(false);
                 }
                 }
@@ -557,7 +568,7 @@ public sealed class ImageIterator : IDisposable
                         });
                         });
                     }
                     }
 
 
-                    await PreLoader.PreLoadAsync(CurrentIndex, ImagePaths.Count, IsReversed, ImagePaths, cancellationToken)
+                    await PreLoader.PreLoadAsync(CurrentIndex, ImagePaths.Count, IsReversed, ImagePaths, cts.Token)
                         .ConfigureAwait(false);
                         .ConfigureAwait(false);
                 }
                 }
 
 
@@ -569,6 +580,10 @@ public sealed class ImageIterator : IDisposable
                     FileHistoryNavigation.Add(ImagePaths[index]);
                     FileHistoryNavigation.Add(ImagePaths[index]);
                 }
                 }
             }
             }
+            catch (OperationCanceledException)
+            {
+                // Ignore
+            }
             catch (Exception e)
             catch (Exception e)
             {
             {
 #if DEBUG
 #if DEBUG
@@ -606,12 +621,12 @@ public sealed class ImageIterator : IDisposable
                 _vm.ImageSource = null;
                 _vm.ImageSource = null;
                 _vm.SecondaryImageSource = null;
                 _vm.SecondaryImageSource = null;
             }
             }
-        }, cancellationToken);
+        }
     }
     }
 
 
     private static Timer? _timer;
     private static Timer? _timer;
 
 
-    internal async Task TimerIteration(int index)
+    internal async Task TimerIteration(int index, CancellationTokenSource cts)
     {
     {
         if (_timer is null)
         if (_timer is null)
         {
         {
@@ -633,7 +648,7 @@ public sealed class ImageIterator : IDisposable
 
 
         _timer.Interval = TimeSpan.FromSeconds(SettingsHelper.Settings.UIProperties.NavSpeed).TotalMilliseconds;
         _timer.Interval = TimeSpan.FromSeconds(SettingsHelper.Settings.UIProperties.NavSpeed).TotalMilliseconds;
         _timer.Start();
         _timer.Start();
-        await IterateToIndex(index).ConfigureAwait(false);
+        await IterateToIndex(index, cts).ConfigureAwait(false);
     }
     }
 
 
     public void UpdateFileListAndIndex(List<string> fileList, int index)
     public void UpdateFileListAndIndex(List<string> fileList, int index)

+ 163 - 55
src/PicView.Avalonia/Navigation/NavigationHelper.cs

@@ -27,7 +27,7 @@ public static class NavigationHelper
     private static CancellationTokenSource? _cancellationTokenSource;
     private static CancellationTokenSource? _cancellationTokenSource;
 
 
     #region Navigation
     #region Navigation
-    
+
     /// <summary>
     /// <summary>
     /// Determines whether navigation is possible based on the current state of the <see cref="MainViewModel"/>.
     /// Determines whether navigation is possible based on the current state of the <see cref="MainViewModel"/>.
     /// </summary>
     /// </summary>
@@ -39,7 +39,7 @@ public static class NavigationHelper
                vm.ImageIterator.ImagePaths.Count > 0 && !CropFunctions.IsCropping;
                vm.ImageIterator.ImagePaths.Count > 0 && !CropFunctions.IsCropping;
         // TODO: should probably turn this into CanExecute observable for ReactiveUI
         // TODO: should probably turn this into CanExecute observable for ReactiveUI
     }
     }
-    
+
     /// <summary>
     /// <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 <paramref name="next"/> parameter.
     /// </summary>
     /// </summary>
@@ -52,19 +52,88 @@ public static class NavigationHelper
         {
         {
             return;
             return;
         }
         }
-        
+
         if (GalleryFunctions.IsFullGalleryOpen)
         if (GalleryFunctions.IsFullGalleryOpen)
         {
         {
             await ScrollGallery(next);
             await ScrollGallery(next);
         }
         }
         else
         else
         {
         {
-            var navigateTo = next ? NavigateTo.Next : NavigateTo.Previous;
-            _cancellationTokenSource = new CancellationTokenSource();
-            await vm.ImageIterator.NextIteration(navigateTo, _cancellationTokenSource.Token).ConfigureAwait(false);
+            await Task.Run(async () =>
+            {
+                var navigateTo = next ? NavigateTo.Next : NavigateTo.Previous;
+
+                if (_cancellationTokenSource is not null)
+                {
+                    await _cancellationTokenSource.CancelAsync().ConfigureAwait(false);
+                }
+
+                _cancellationTokenSource = new CancellationTokenSource();
+                await vm.ImageIterator.NextIteration(navigateTo, _cancellationTokenSource).ConfigureAwait(false);
+            }).ConfigureAwait(false);
         }
         }
     }
     }
 
 
+    public static async Task Navigate(int index, MainViewModel vm)
+    {
+        if (!CanNavigate(vm))
+        {
+            return;
+        }
+
+        if (_cancellationTokenSource is not null)
+        {
+            await _cancellationTokenSource.CancelAsync().ConfigureAwait(false);
+        }
+
+        _cancellationTokenSource = new CancellationTokenSource();
+        await vm.ImageIterator.IterateToIndex(index, _cancellationTokenSource).ConfigureAwait(false);
+    }
+
+    public static async Task Next10(MainViewModel vm)
+    {
+        if (_cancellationTokenSource is not null)
+        {
+            await _cancellationTokenSource.CancelAsync().ConfigureAwait(false);
+        }
+
+        _cancellationTokenSource = new CancellationTokenSource();
+        await vm.ImageIterator.Next10Iteration(true, _cancellationTokenSource).ConfigureAwait(false);
+    }
+
+    public static async Task Next100(MainViewModel vm)
+    {
+        if (_cancellationTokenSource is not null)
+        {
+            await _cancellationTokenSource.CancelAsync().ConfigureAwait(false);
+        }
+
+        _cancellationTokenSource = new CancellationTokenSource();
+        await vm.ImageIterator.Next100Iteration(true, _cancellationTokenSource).ConfigureAwait(false);
+    }
+
+    public static async Task Prev10(MainViewModel vm)
+    {
+        if (_cancellationTokenSource is not null)
+        {
+            await _cancellationTokenSource.CancelAsync().ConfigureAwait(false);
+        }
+
+        _cancellationTokenSource = new CancellationTokenSource();
+        await vm.ImageIterator.Next10Iteration(false, _cancellationTokenSource).ConfigureAwait(false);
+    }
+
+    public static async Task Prev100(MainViewModel vm)
+    {
+        if (_cancellationTokenSource is not null)
+        {
+            await _cancellationTokenSource.CancelAsync().ConfigureAwait(false);
+        }
+
+        _cancellationTokenSource = new CancellationTokenSource();
+        await vm.ImageIterator.Next100Iteration(false, _cancellationTokenSource).ConfigureAwait(false);
+    }
+
     /// <summary>
     /// <summary>
     /// Navigates to the first or last image in the collection.
     /// Navigates to the first or last image in the collection.
     /// </summary>
     /// </summary>
@@ -77,13 +146,21 @@ public static class NavigationHelper
         {
         {
             return;
             return;
         }
         }
+
         if (GalleryFunctions.IsFullGalleryOpen)
         if (GalleryFunctions.IsFullGalleryOpen)
         {
         {
             GalleryNavigation.NavigateGallery(last, vm);
             GalleryNavigation.NavigateGallery(last, vm);
         }
         }
         else
         else
         {
         {
-            await vm.ImageIterator.NextIteration(last ? NavigateTo.Last : NavigateTo.First).ConfigureAwait(false);
+            if (_cancellationTokenSource is not null)
+            {
+                await _cancellationTokenSource.CancelAsync().ConfigureAwait(false);
+            }
+
+            _cancellationTokenSource = new CancellationTokenSource();
+            await vm.ImageIterator.NextIteration(last ? NavigateTo.Last : NavigateTo.First, _cancellationTokenSource)
+                .ConfigureAwait(false);
             await ScrollToEndIfNecessary(last);
             await ScrollToEndIfNecessary(last);
         }
         }
     }
     }
@@ -124,7 +201,7 @@ public static class NavigationHelper
         {
         {
             return;
             return;
         }
         }
-        
+
         if (GalleryFunctions.IsFullGalleryOpen)
         if (GalleryFunctions.IsFullGalleryOpen)
         {
         {
             await ScrollGallery(next);
             await ScrollGallery(next);
@@ -135,7 +212,7 @@ public static class NavigationHelper
             await MoveCursorOnButtonClick(next, arrow, vm);
             await MoveCursorOnButtonClick(next, arrow, vm);
         }
         }
     }
     }
-    
+
     /// <summary>
     /// <summary>
     /// Navigates to the next or previous folder and loads the first image in that folder.
     /// Navigates to the next or previous folder and loads the first image in that folder.
     /// </summary>
     /// </summary>
@@ -148,11 +225,13 @@ public static class NavigationHelper
         {
         {
             return;
             return;
         }
         }
+
         SetTitleHelper.SetLoadingTitle(vm);
         SetTitleHelper.SetLoadingTitle(vm);
         if (_cancellationTokenSource is not null)
         if (_cancellationTokenSource is not null)
         {
         {
             await _cancellationTokenSource.CancelAsync();
             await _cancellationTokenSource.CancelAsync();
         }
         }
+
         var fileList = await GetNextFolderFileList(next, vm).ConfigureAwait(false);
         var fileList = await GetNextFolderFileList(next, vm).ConfigureAwait(false);
 
 
         if (fileList is null)
         if (fileList is null)
@@ -165,11 +244,11 @@ public static class NavigationHelper
             await PreviewPicAndLoadGallery(new FileInfo(fileList[0]), vm, fileList);
             await PreviewPicAndLoadGallery(new FileInfo(fileList[0]), vm, fileList);
         }
         }
     }
     }
-    
+
     #endregion
     #endregion
-    
+
     #region Load pictures from string, file or url
     #region Load pictures from string, file or url
-    
+
     /// <summary>
     /// <summary>
     /// Loads a picture from a given string source, which can be a file path, directory path, or URL.
     /// Loads a picture from a given string source, which can be a file path, directory path, or URL.
     /// </summary>
     /// </summary>
@@ -182,16 +261,18 @@ public static class NavigationHelper
         {
         {
             return;
             return;
         }
         }
-        
+
         UIHelper.CloseMenus(vm);
         UIHelper.CloseMenus(vm);
         vm.IsLoading = true;
         vm.IsLoading = true;
         SetTitleHelper.SetLoadingTitle(vm);
         SetTitleHelper.SetLoadingTitle(vm);
-        
+
         if (_cancellationTokenSource is not null)
         if (_cancellationTokenSource is not null)
         {
         {
-            await _cancellationTokenSource.CancelAsync();
+            await _cancellationTokenSource.CancelAsync().ConfigureAwait(false);
         }
         }
 
 
+        _cancellationTokenSource = new CancellationTokenSource();
+
         // Starting in new task makes it more responsive and works better
         // Starting in new task makes it more responsive and works better
         await Task.Run(async () =>
         await Task.Run(async () =>
         {
         {
@@ -205,11 +286,13 @@ public static class NavigationHelper
                     {
                     {
                         if (vm.ImageIterator.ImagePaths.Contains(check))
                         if (vm.ImageIterator.ImagePaths.Contains(check))
                         {
                         {
-                            await vm.ImageIterator.IterateToIndex(vm.ImageIterator.ImagePaths.IndexOf(check))
+                            await vm.ImageIterator.IterateToIndex(vm.ImageIterator.ImagePaths.IndexOf(check),
+                                    _cancellationTokenSource)
                                 .ConfigureAwait(false);
                                 .ConfigureAwait(false);
                             return;
                             return;
                         }
                         }
                     }
                     }
+
                     vm.CurrentView = vm.ImageViewer;
                     vm.CurrentView = vm.ImageViewer;
                     await LoadPicFromFile(check, vm).ConfigureAwait(false);
                     await LoadPicFromFile(check, vm).ConfigureAwait(false);
                     vm.IsLoading = false;
                     vm.IsLoading = false;
@@ -251,7 +334,7 @@ public static class NavigationHelper
             }
             }
         });
         });
     }
     }
-    
+
     /// <summary>
     /// <summary>
     /// Loads a picture from a given file.
     /// Loads a picture from a given file.
     /// </summary>
     /// </summary>
@@ -265,21 +348,25 @@ public static class NavigationHelper
         {
         {
             return;
             return;
         }
         }
+
         fileInfo ??= new FileInfo(fileName);
         fileInfo ??= new FileInfo(fileName);
         if (!fileInfo.Exists)
         if (!fileInfo.Exists)
         {
         {
             return;
             return;
         }
         }
+
         if (SettingsHelper.Settings.UIProperties.IsTaskbarProgressEnabled)
         if (SettingsHelper.Settings.UIProperties.IsTaskbarProgressEnabled)
         {
         {
             vm.PlatformService.StopTaskbarProgress();
             vm.PlatformService.StopTaskbarProgress();
         }
         }
-        
+
         if (_cancellationTokenSource is not null)
         if (_cancellationTokenSource is not null)
         {
         {
-            await _cancellationTokenSource.CancelAsync();
+            await _cancellationTokenSource.CancelAsync().ConfigureAwait(false);
         }
         }
 
 
+        _cancellationTokenSource = new CancellationTokenSource();
+
         if (vm.ImageIterator is not null)
         if (vm.ImageIterator is not null)
         {
         {
             if (fileInfo.DirectoryName == vm.ImageIterator.InitialFileInfo.DirectoryName)
             if (fileInfo.DirectoryName == vm.ImageIterator.InitialFileInfo.DirectoryName)
@@ -300,7 +387,7 @@ public static class NavigationHelper
                 var index = vm.ImageIterator.ImagePaths.IndexOf(fileName);
                 var index = vm.ImageIterator.ImagePaths.IndexOf(fileName);
                 if (index != -1)
                 if (index != -1)
                 {
                 {
-                    await vm.ImageIterator.IterateToIndex(index);
+                    await vm.ImageIterator.IterateToIndex(index, _cancellationTokenSource).ConfigureAwait(false);
                 }
                 }
                 else
                 else
                 {
                 {
@@ -333,8 +420,9 @@ public static class NavigationHelper
         {
         {
             await _cancellationTokenSource.CancelAsync();
             await _cancellationTokenSource.CancelAsync();
         }
         }
-        
-        var extraction = await ArchiveExtraction.ExtractArchiveAsync(path, vm.PlatformService.ExtractWithLocalSoftwareAsync).ConfigureAwait(false);
+
+        var extraction = await ArchiveExtraction
+            .ExtractArchiveAsync(path, vm.PlatformService.ExtractWithLocalSoftwareAsync).ConfigureAwait(false);
         if (!extraction)
         if (!extraction)
         {
         {
             await ErrorHandling.ReloadAsync(vm);
             await ErrorHandling.ReloadAsync(vm);
@@ -354,6 +442,7 @@ public static class NavigationHelper
             {
             {
                 await LoadPicFromDirectoryAsync(ArchiveExtraction.TempZipDirectory, vm).ConfigureAwait(false);
                 await LoadPicFromDirectoryAsync(ArchiveExtraction.TempZipDirectory, vm).ConfigureAwait(false);
             }
             }
+
             MainKeyboardShortcuts.ClearKeyDownModifiers(); // Fix possible modifier key state issue
             MainKeyboardShortcuts.ClearKeyDownModifiers(); // Fix possible modifier key state issue
         }
         }
         else
         else
@@ -361,7 +450,7 @@ public static class NavigationHelper
             await ErrorHandling.ReloadAsync(vm);
             await ErrorHandling.ReloadAsync(vm);
         }
         }
     }
     }
-    
+
     /// <summary>
     /// <summary>
     /// Loads a picture from a given URL.
     /// Loads a picture from a given URL.
     /// </summary>
     /// </summary>
@@ -374,7 +463,7 @@ public static class NavigationHelper
         {
         {
             await _cancellationTokenSource.CancelAsync();
             await _cancellationTokenSource.CancelAsync();
         }
         }
-        
+
         string destination;
         string destination;
 
 
         try
         try
@@ -383,12 +472,13 @@ public static class NavigationHelper
 
 
             var httpDownload = HttpNavigation.GetDownloadClient(url);
             var httpDownload = HttpNavigation.GetDownloadClient(url);
             using var client = httpDownload.Client;
             using var client = httpDownload.Client;
-            client.ProgressChanged += (totalFileSize, totalBytesDownloaded, progressPercentage) => 
+            client.ProgressChanged += (totalFileSize, totalBytesDownloaded, progressPercentage) =>
             {
             {
                 if (totalFileSize is null || totalBytesDownloaded is null || progressPercentage is null)
                 if (totalFileSize is null || totalBytesDownloaded is null || progressPercentage is null)
                 {
                 {
                     return;
                     return;
                 }
                 }
+
                 var displayProgress = HttpNavigation.GetProgressDisplay(totalFileSize, totalBytesDownloaded,
                 var displayProgress = HttpNavigation.GetProgressDisplay(totalFileSize, totalBytesDownloaded,
                     progressPercentage);
                     progressPercentage);
                 vm.Title = displayProgress;
                 vm.Title = displayProgress;
@@ -412,14 +502,14 @@ public static class NavigationHelper
 
 
             return;
             return;
         }
         }
-        
+
         var fileInfo = new FileInfo(destination);
         var fileInfo = new FileInfo(destination);
         if (!fileInfo.Exists)
         if (!fileInfo.Exists)
         {
         {
             await ErrorHandling.ReloadAsync(vm);
             await ErrorHandling.ReloadAsync(vm);
             return;
             return;
         }
         }
-        
+
         var imageModel = await GetImageModel.GetImageModelAsync(fileInfo).ConfigureAwait(false);
         var imageModel = await GetImageModel.GetImageModelAsync(fileInfo).ConfigureAwait(false);
         UpdateImage.SetSingleImage(imageModel.Image, imageModel.ImageType, url, vm);
         UpdateImage.SetSingleImage(imageModel.Image, imageModel.ImageType, url, vm);
         vm.FileInfo = fileInfo;
         vm.FileInfo = fileInfo;
@@ -428,7 +518,7 @@ public static class NavigationHelper
 
 
         vm.IsLoading = false;
         vm.IsLoading = false;
     }
     }
-    
+
     /// <summary>
     /// <summary>
     /// Loads a picture from a Base64-encoded string.
     /// Loads a picture from a Base64-encoded string.
     /// </summary>
     /// </summary>
@@ -460,13 +550,14 @@ public static class NavigationHelper
                     PixelHeight = bitmap?.PixelSize.Height ?? 0,
                     PixelHeight = bitmap?.PixelSize.Height ?? 0,
                     ImageType = ImageType.Bitmap
                     ImageType = ImageType.Bitmap
                 };
                 };
-                UpdateImage.SetSingleImage(imageModel.Image, imageModel.ImageType, TranslationHelper.Translation.Base64Image, vm);
+                UpdateImage.SetSingleImage(imageModel.Image, imageModel.ImageType,
+                    TranslationHelper.Translation.Base64Image, vm);
             }
             }
             catch (Exception e)
             catch (Exception e)
             {
             {
-                #if DEBUG
+#if DEBUG
                 Console.WriteLine("LoadPicFromBase64Async exception = \n" + e.Message);
                 Console.WriteLine("LoadPicFromBase64Async exception = \n" + e.Message);
-                #endif
+#endif
                 if (vm.FileInfo is not null && vm.FileInfo.Exists)
                 if (vm.FileInfo is not null && vm.FileInfo.Exists)
                 {
                 {
                     await LoadPicFromFile(vm.FileInfo.FullName, vm, vm.FileInfo);
                     await LoadPicFromFile(vm.FileInfo.FullName, vm, vm.FileInfo);
@@ -491,17 +582,19 @@ public static class NavigationHelper
     {
     {
         vm.IsLoading = true;
         vm.IsLoading = true;
         SetTitleHelper.SetLoadingTitle(vm);
         SetTitleHelper.SetLoadingTitle(vm);
-        
+
         if (_cancellationTokenSource is not null)
         if (_cancellationTokenSource is not null)
         {
         {
-            await _cancellationTokenSource.CancelAsync();
+            await _cancellationTokenSource.CancelAsync().ConfigureAwait(false);
         }
         }
-        
+
+        _cancellationTokenSource = new CancellationTokenSource();
+
         if (SettingsHelper.Settings.UIProperties.IsTaskbarProgressEnabled)
         if (SettingsHelper.Settings.UIProperties.IsTaskbarProgressEnabled)
         {
         {
             vm.PlatformService.StopTaskbarProgress();
             vm.PlatformService.StopTaskbarProgress();
         }
         }
-        
+
         fileInfo ??= new FileInfo(file);
         fileInfo ??= new FileInfo(file);
         vm.ImageIterator?.Dispose();
         vm.ImageIterator?.Dispose();
         var fileList = await Task.FromResult(vm.PlatformService.GetFiles(fileInfo)).ConfigureAwait(false);
         var fileList = await Task.FromResult(vm.PlatformService.GetFiles(fileInfo)).ConfigureAwait(false);
@@ -517,6 +610,7 @@ public static class NavigationHelper
                     await ErrorHandling.ReloadAsync(vm).ConfigureAwait(false);
                     await ErrorHandling.ReloadAsync(vm).ConfigureAwait(false);
                     return;
                     return;
                 }
                 }
+
                 SettingsHelper.Settings.Sorting.IncludeSubDirectories = false;
                 SettingsHelper.Settings.Sorting.IncludeSubDirectories = false;
             }
             }
             else
             else
@@ -525,15 +619,16 @@ public static class NavigationHelper
                 return;
                 return;
             }
             }
         }
         }
+
         vm.ImageIterator = new ImageIterator(fileInfo, fileList, 0, vm);
         vm.ImageIterator = new ImageIterator(fileInfo, fileList, 0, vm);
-        await vm.ImageIterator.IterateToIndex(0);
+        await vm.ImageIterator.IterateToIndex(0, _cancellationTokenSource).ConfigureAwait(false);
         await CheckAndReloadGallery(fileInfo, vm);
         await CheckAndReloadGallery(fileInfo, vm);
     }
     }
-    
+
     #endregion
     #endregion
 
 
     #region Private helpers
     #region Private helpers
-    
+
     /// <summary>
     /// <summary>
     /// Gets the list of files in the next or previous folder.
     /// Gets the list of files in the next or previous folder.
     /// </summary>
     /// </summary>
@@ -550,12 +645,16 @@ public static class NavigationHelper
             var directories = Directory.GetDirectories(parentFolder, "*", SearchOption.TopDirectoryOnly);
             var directories = Directory.GetDirectories(parentFolder, "*", SearchOption.TopDirectoryOnly);
             var directoryIndex = Array.IndexOf(directories, currentFolder);
             var directoryIndex = Array.IndexOf(directories, currentFolder);
             if (SettingsHelper.Settings.UIProperties.Looping)
             if (SettingsHelper.Settings.UIProperties.Looping)
+            {
                 directoryIndex = (directoryIndex + indexChange + directories.Length) % directories.Length;
                 directoryIndex = (directoryIndex + indexChange + directories.Length) % directories.Length;
+            }
             else
             else
             {
             {
                 directoryIndex += indexChange;
                 directoryIndex += indexChange;
                 if (directoryIndex < 0 || directoryIndex >= directories.Length)
                 if (directoryIndex < 0 || directoryIndex >= directories.Length)
+                {
                     return null;
                     return null;
+                }
             }
             }
 
 
             for (var i = directoryIndex; i < directories.Length; i++)
             for (var i = directoryIndex; i < directories.Length; i++)
@@ -563,8 +662,11 @@ public static class NavigationHelper
                 var fileInfo = new FileInfo(directories[i]);
                 var fileInfo = new FileInfo(directories[i]);
                 var fileList = vm.PlatformService.GetFiles(fileInfo);
                 var fileList = vm.PlatformService.GetFiles(fileInfo);
                 if (fileList is { Count: > 0 })
                 if (fileList is { Count: > 0 })
+                {
                     return fileList;
                     return fileList;
+                }
             }
             }
+
             return null;
             return null;
         }).ConfigureAwait(false);
         }).ConfigureAwait(false);
     }
     }
@@ -583,22 +685,24 @@ public static class NavigationHelper
         vm.ImageType = imageModel.ImageType;
         vm.ImageType = imageModel.ImageType;
         await Dispatcher.UIThread.InvokeAsync(() =>
         await Dispatcher.UIThread.InvokeAsync(() =>
         {
         {
-            WindowResizing.SetSize(imageModel.PixelWidth, imageModel.PixelHeight, 0,0, imageModel.Rotation, vm);
+            WindowResizing.SetSize(imageModel.PixelWidth, imageModel.PixelHeight, 0, 0, imageModel.Rotation, vm);
         });
         });
-        
+
         if (files is null)
         if (files is null)
         {
         {
             vm.ImageIterator = new ImageIterator(fileInfo, vm);
             vm.ImageIterator = new ImageIterator(fileInfo, vm);
-            await vm.ImageIterator.IterateToIndex(vm.ImageIterator.ImagePaths.IndexOf(fileInfo.FullName));
+            await vm.ImageIterator.IterateToIndex(vm.ImageIterator.ImagePaths.IndexOf(fileInfo.FullName),
+                _cancellationTokenSource);
         }
         }
         else
         else
         {
         {
-            vm.ImageIterator = new ImageIterator(fileInfo, files, currentIndex: 0, vm);
-            await vm.ImageIterator.IterateToIndex(0);
+            vm.ImageIterator = new ImageIterator(fileInfo, files, 0, vm);
+            await vm.ImageIterator.IterateToIndex(0, _cancellationTokenSource);
         }
         }
+
         await CheckAndReloadGallery(fileInfo, vm);
         await CheckAndReloadGallery(fileInfo, vm);
     }
     }
-    
+
     /// <summary>
     /// <summary>
     /// Checks and reloads the gallery if necessary based on the provided file info.
     /// Checks and reloads the gallery if necessary based on the provided file info.
     /// </summary>
     /// </summary>
@@ -610,7 +714,7 @@ public static class NavigationHelper
         if (SettingsHelper.Settings.Gallery.IsBottomGalleryShown || GalleryFunctions.IsFullGalleryOpen)
         if (SettingsHelper.Settings.Gallery.IsBottomGalleryShown || GalleryFunctions.IsFullGalleryOpen)
         {
         {
             GalleryFunctions.Clear();
             GalleryFunctions.Clear();
-            
+
             // Check if the bottom gallery should be shown
             // Check if the bottom gallery should be shown
             if (!GalleryFunctions.IsFullGalleryOpen)
             if (!GalleryFunctions.IsFullGalleryOpen)
             {
             {
@@ -620,10 +724,11 @@ public static class NavigationHelper
                     vm.GalleryMode = GalleryMode.ClosedToBottom;
                     vm.GalleryMode = GalleryMode.ClosedToBottom;
                 }
                 }
             }
             }
+
             await GalleryLoad.ReloadGalleryAsync(vm, fileInfo.DirectoryName);
             await GalleryLoad.ReloadGalleryAsync(vm, fileInfo.DirectoryName);
         }
         }
     }
     }
-    
+
     /// <summary>
     /// <summary>
     /// Scrolls the gallery to the next or previous page.
     /// Scrolls the gallery to the next or previous page.
     /// </summary>
     /// </summary>
@@ -634,12 +739,16 @@ public static class NavigationHelper
         await Dispatcher.UIThread.InvokeAsync(() =>
         await Dispatcher.UIThread.InvokeAsync(() =>
         {
         {
             if (next)
             if (next)
+            {
                 UIHelper.GetGalleryView.GalleryListBox.PageRight();
                 UIHelper.GetGalleryView.GalleryListBox.PageRight();
+            }
             else
             else
+            {
                 UIHelper.GetGalleryView.GalleryListBox.PageLeft();
                 UIHelper.GetGalleryView.GalleryListBox.PageLeft();
+            }
         });
         });
     }
     }
-    
+
     /// <summary>
     /// <summary>
     /// Scrolls to the end of the gallery if the <paramref name="last"/> parameter is true.
     /// Scrolls to the end of the gallery if the <paramref name="last"/> parameter is true.
     /// </summary>
     /// </summary>
@@ -649,13 +758,10 @@ public static class NavigationHelper
     {
     {
         if (last && SettingsHelper.Settings.Gallery.IsBottomGalleryShown)
         if (last && SettingsHelper.Settings.Gallery.IsBottomGalleryShown)
         {
         {
-            await Dispatcher.UIThread.InvokeAsync(() =>
-            {
-                UIHelper.GetGalleryView.GalleryListBox.ScrollToEnd();
-            });
+            await Dispatcher.UIThread.InvokeAsync(() => { UIHelper.GetGalleryView.GalleryListBox.ScrollToEnd(); });
         }
         }
     }
     }
-    
+
     /// <summary>
     /// <summary>
     /// Moves the cursor on the navigation button.
     /// Moves the cursor on the navigation button.
     /// </summary>
     /// </summary>
@@ -685,7 +791,9 @@ public static class NavigationHelper
     {
     {
         return arrow
         return arrow
             ? next ? "ClickArrowRight" : "ClickArrowLeft"
             ? next ? "ClickArrowRight" : "ClickArrowLeft"
-            : next ? "NextButton" : "PreviousButton";
+            : next
+                ? "NextButton"
+                : "PreviousButton";
     }
     }
 
 
     /// <summary>
     /// <summary>
@@ -709,10 +817,10 @@ public static class NavigationHelper
     /// <returns>The point to click on the button.</returns>
     /// <returns>The point to click on the button.</returns>
     private static Point GetClickPoint(bool next, bool arrow)
     private static Point GetClickPoint(bool next, bool arrow)
     {
     {
-        return arrow ? next ? new Point(65, 95) : new Point(15, 95)
+        return arrow
+            ? next ? new Point(65, 95) : new Point(15, 95)
             : new Point(50, 10);
             : new Point(50, 10);
     }
     }
 
 
-
     #endregion
     #endregion
 }
 }

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

@@ -7,7 +7,6 @@ using PicView.Avalonia.ViewModels;
 using PicView.Avalonia.WindowBehavior;
 using PicView.Avalonia.WindowBehavior;
 using PicView.Core.Config;
 using PicView.Core.Config;
 using PicView.Core.Gallery;
 using PicView.Core.Gallery;
-using PicView.Core.Navigation;
 using Timer = System.Timers.Timer;
 using Timer = System.Timers.Timer;
 
 
 namespace PicView.Avalonia.Navigation;
 namespace PicView.Avalonia.Navigation;
@@ -82,7 +81,8 @@ public static class Slideshow
                 // E.g. https://codepen.io/arrive/pen/EOGyzK
                 // E.g. https://codepen.io/arrive/pen/EOGyzK
                 // 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/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/guides/graphics-and-animation/page-transitions/page-slide-transition
-                await vm.ImageIterator.NextIteration(NavigateTo.Next);
+                // https://docs.avaloniaui.net/docs/reference/controls/transitioningcontentcontrol
+                await NavigationHelper.Navigate(true, vm).ConfigureAwait(false);
             };
             };
         }
         }
         else if (_timer.Enabled)
         else if (_timer.Enabled)
@@ -116,6 +116,6 @@ public static class Slideshow
             vm.GalleryMode = GalleryMode.BottomToClosed;
             vm.GalleryMode = GalleryMode.BottomToClosed;
         }
         }
         
         
-        await vm.ImageIterator.NextIteration(NavigateTo.Next);
+        await NavigationHelper.Navigate(true, vm).ConfigureAwait(false);
     }
     }
 }
 }

+ 4 - 4
src/PicView.Avalonia/UI/FunctionsHelper.cs

@@ -238,22 +238,22 @@ public static class FunctionsHelper
     
     
     public static async Task Next10()
     public static async Task Next10()
     {
     {
-        await Vm?.ImageIterator.Next10Iteration(true);
+        await NavigationHelper.Next10(Vm).ConfigureAwait(false);
     }
     }
     
     
     public static async Task Next100()
     public static async Task Next100()
     {
     {
-        await Vm?.ImageIterator.Next100Iteration(true);
+        await NavigationHelper.Next100(Vm).ConfigureAwait(false);
     }
     }
     
     
     public static async Task Prev10()
     public static async Task Prev10()
     {
     {
-        await Vm?.ImageIterator.Next10Iteration(false);
+        await NavigationHelper.Prev10(Vm).ConfigureAwait(false);
     }
     }
     
     
     public static async Task Prev100()
     public static async Task Prev100()
     {
     {
-        await Vm?.ImageIterator.Next100Iteration(false);
+        await NavigationHelper.Prev100(Vm).ConfigureAwait(false);
     }
     }
     
     
 
 

+ 1 - 1
src/PicView.Avalonia/Views/ExifView.axaml.cs

@@ -177,7 +177,7 @@ public partial class ExifView : UserControl
                 if (success)
                 if (success)
                 {
                 {
                     vm.ImageIterator?.RemoveCurrentItemFromPreLoader();
                     vm.ImageIterator?.RemoveCurrentItemFromPreLoader();
-                    await vm.ImageIterator?.IterateToIndex(vm.ImageIterator.CurrentIndex);
+                    await NavigationHelper.Navigate(vm.ImageIterator.CurrentIndex, vm).ConfigureAwait(false);
                 }
                 }
             }
             }
         }
         }

+ 1 - 2
src/PicView.Avalonia/Views/UC/EditableTitlebar.axaml.cs

@@ -9,7 +9,6 @@ using PicView.Avalonia.ViewModels;
 using PicView.Core.FileHandling;
 using PicView.Core.FileHandling;
 using PicView.Core.ImageDecoding;
 using PicView.Core.ImageDecoding;
 using PicView.Core.Localization;
 using PicView.Core.Localization;
-using PicView.Core.Navigation;
 
 
 namespace PicView.Avalonia.Views.UC;
 namespace PicView.Avalonia.Views.UC;
 
 
@@ -134,7 +133,7 @@ public partial class EditableTitlebar : UserControl
         if (Path.GetDirectoryName(oldPath) != Path.GetDirectoryName(newPath))
         if (Path.GetDirectoryName(oldPath) != Path.GetDirectoryName(newPath))
         {
         {
             vm.ImageIterator?.RemoveCurrentItemFromPreLoader();
             vm.ImageIterator?.RemoveCurrentItemFromPreLoader();
-            await vm.ImageIterator?.NextIteration(NavigateTo.Next);
+            await NavigationHelper.Navigate(true, vm);
             FileHelper.RenameFile(oldPath, newPath);
             FileHelper.RenameFile(oldPath, newPath);
             return;
             return;
         }
         }

+ 1 - 1
src/PicView.Avalonia/Views/UC/Menus/ImageMenu.axaml.cs

@@ -76,7 +76,7 @@ public partial class ImageMenu  : AnimatedMenu
                 number--;
                 number--;
             }
             }
 
 
-            await vm.ImageIterator.IterateToIndex(number).ConfigureAwait(false);
+            await NavigationHelper.Navigate(number, vm).ConfigureAwait(false);
         }
         }
     }
     }
 }
 }