Browse Source

Use ZString instead StringBuilder, refactor, misc.

Ruben 1 year ago
parent
commit
41cb36aad5

+ 592 - 580
src/PicView.Avalonia/Navigation/ImageIterator.cs

@@ -13,542 +13,516 @@ using PicView.Core.Gallery;
 using PicView.Core.Navigation;
 using Timer = System.Timers.Timer;
 
-namespace PicView.Avalonia.Navigation
+namespace PicView.Avalonia.Navigation;
+
+public sealed class ImageIterator : IDisposable
 {
-    public sealed class ImageIterator : IDisposable
-    {
-        #region Properties
+    #region Properties
 
-        private bool _disposed;
+    private bool _disposed;
 
-        public List<string> ImagePaths { get; private set; }
+    public List<string> ImagePaths { get; private set; }
 
-        public bool IsRenamingInProgress { get; set; }
+    public bool IsRenamingInProgress { get; set; }
 
-        public int CurrentIndex { get; private set; }
+    public int CurrentIndex { get; private set; }
 
-        public FileInfo InitialFileInfo { get; private set; } = null!;
-        public bool IsReversed { get; private set; }
-        private PreLoader PreLoader { get; } = new();
+    public FileInfo InitialFileInfo { get; private set; } = null!;
+    public bool IsReversed { get; private set; }
+    private PreLoader PreLoader { get; } = new();
 
-        private static FileSystemWatcher? _watcher;
-        private static bool _isRunning;
-        private readonly MainViewModel? _vm;
-        private readonly Lock _lock = new();
+    private static FileSystemWatcher? _watcher;
+    private static bool _isRunning;
+    private readonly MainViewModel? _vm;
+    private readonly Lock _lock = new();
 
-        #endregion
+    #endregion
 
-        #region Constructors
+    #region Constructors
 
-        public ImageIterator(FileInfo fileInfo, MainViewModel vm)
-        {
-            ArgumentNullException.ThrowIfNull(fileInfo);
-            _vm = vm;
-            ImagePaths = vm.PlatformService.GetFiles(fileInfo);
-            CurrentIndex = Directory.Exists(fileInfo.FullName) ? 0 : ImagePaths.IndexOf(fileInfo.FullName);
-            InitiateFileSystemWatcher(fileInfo);
-        }
+    public ImageIterator(FileInfo fileInfo, MainViewModel vm)
+    {
+        ArgumentNullException.ThrowIfNull(fileInfo);
+        _vm = vm;
+        ImagePaths = vm.PlatformService.GetFiles(fileInfo);
+        CurrentIndex = Directory.Exists(fileInfo.FullName) ? 0 : ImagePaths.IndexOf(fileInfo.FullName);
+        InitiateFileSystemWatcher(fileInfo);
+    }
 
-        public ImageIterator(FileInfo fileInfo, List<string> imagePaths, int currentIndex, MainViewModel vm)
-        {
-            ArgumentNullException.ThrowIfNull(fileInfo);
-            _vm = vm;
-            ImagePaths = imagePaths;
-            CurrentIndex = currentIndex;
-            InitiateFileSystemWatcher(fileInfo);
-        }
+    public ImageIterator(FileInfo fileInfo, List<string> imagePaths, int currentIndex, MainViewModel vm)
+    {
+        ArgumentNullException.ThrowIfNull(fileInfo);
+        _vm = vm;
+        ImagePaths = imagePaths;
+        CurrentIndex = currentIndex;
+        InitiateFileSystemWatcher(fileInfo);
+    }
 
-        #endregion
+    #endregion
 
-        #region File Watcher
+    #region File Watcher
 
-        private void InitiateFileSystemWatcher(FileInfo fileInfo)
+    private void InitiateFileSystemWatcher(FileInfo fileInfo)
+    {
+        InitialFileInfo = fileInfo;
+        if (_watcher is not null)
         {
-            InitialFileInfo = fileInfo;
-            if (_watcher is not null)
-            {
-                _watcher.Dispose();
-                _watcher = null;
-            }
+            _watcher.Dispose();
+            _watcher = null;
+        }
 
-            _watcher = new FileSystemWatcher();
+        _watcher = new FileSystemWatcher();
 #if DEBUG
-            Debug.Assert(fileInfo.DirectoryName != null, "fileInfo.DirectoryName != null");
+        Debug.Assert(fileInfo.DirectoryName != null, "fileInfo.DirectoryName != null");
 #endif
-            _watcher.Path = fileInfo.DirectoryName;
-            _watcher.EnableRaisingEvents = true;
-            _watcher.Filter = "*.*";
-            _watcher.IncludeSubdirectories = SettingsHelper.Settings.Sorting.IncludeSubDirectories;
-            _watcher.Created += async (_, e) => await OnFileAdded(e);
-            _watcher.Deleted += async (_, e) => await OnFileDeleted(e);
-            _watcher.Renamed += async (_, e) => await OnFileRenamed(e);
-        }
+        _watcher.Path = fileInfo.DirectoryName;
+        _watcher.EnableRaisingEvents = true;
+        _watcher.Filter = "*.*";
+        _watcher.IncludeSubdirectories = SettingsHelper.Settings.Sorting.IncludeSubDirectories;
+        _watcher.Created += async (_, e) => await OnFileAdded(e);
+        _watcher.Deleted += async (_, e) => await OnFileDeleted(e);
+        _watcher.Renamed += async (_, e) => await OnFileRenamed(e);
+    }
 
-        private async Task OnFileAdded(FileSystemEventArgs e)
+    private async Task OnFileAdded(FileSystemEventArgs e)
+    {
+        if (IsRenamingInProgress)
         {
-            if (IsRenamingInProgress)
-            {
-                return;
-            }
+            return;
+        }
 
-            if (ImagePaths.Contains(e.FullPath))
-            {
-                return;
-            }
+        if (ImagePaths.Contains(e.FullPath))
+        {
+            return;
+        }
 
-            if (e.FullPath.IsSupported() == false)
-            {
-                return;
-            }
+        if (e.FullPath.IsSupported() == false)
+        {
+            return;
+        }
 
-            var fileInfo = new FileInfo(e.FullPath);
-            if (fileInfo.Exists == false)
-            {
-                return;
-            }
+        var fileInfo = new FileInfo(e.FullPath);
+        if (fileInfo.Exists == false)
+        {
+            return;
+        }
 
-            var retries = 0;
-            while (_isRunning && retries < 10)
-            {
-                await Task.Delay(200);
-                retries++;
-            }
+        var retries = 0;
+        while (_isRunning && retries < 10)
+        {
+            await Task.Delay(200);
+            retries++;
+        }
 
-            _isRunning = true;
+        _isRunning = true;
 
-            var newList = await Task.FromResult(_vm.PlatformService.GetFiles(fileInfo));
-            if (newList.Count == 0)
-            {
-                return;
-            }
+        var newList = await Task.FromResult(_vm.PlatformService.GetFiles(fileInfo));
+        if (newList.Count == 0)
+        {
+            return;
+        }
 
-            if (newList.Count == ImagePaths.Count)
-            {
-                return;
-            }
+        if (newList.Count == ImagePaths.Count)
+        {
+            return;
+        }
 
-            if (fileInfo.Exists == false)
-            {
-                return;
-            }
+        if (fileInfo.Exists == false)
+        {
+            return;
+        }
 
-            ImagePaths = newList;
+        ImagePaths = newList;
 
-            _isRunning = false;
+        _isRunning = false;
 
-            var index = ImagePaths.IndexOf(e.FullPath);
-            if (index < 0)
-            {
-                return;
-            }
+        var index = ImagePaths.IndexOf(e.FullPath);
+        if (index < 0)
+        {
+            return;
+        }
 
-            var nextIndex = index + 1;
-            if (index >= ImagePaths.Count)
-            {
-                nextIndex = 0;
-            }
+        var nextIndex = index + 1;
+        if (index >= ImagePaths.Count)
+        {
+            nextIndex = 0;
+        }
 
-            var prevIndex = index - 1;
-            if (prevIndex < 0)
-            {
-                prevIndex = ImagePaths.Count - 1;
-            }
+        var prevIndex = index - 1;
+        if (prevIndex < 0)
+        {
+            prevIndex = ImagePaths.Count - 1;
+        }
 
-            var cleared = false;
-            if (PreLoader.Contains(index, ImagePaths) || PreLoader.Contains(nextIndex, ImagePaths) ||
-                PreLoader.Contains(prevIndex, ImagePaths))
-            {
-                PreLoader.Clear();
-                cleared = true;
-            }
+        var cleared = false;
+        if (PreLoader.Contains(index, ImagePaths) || PreLoader.Contains(nextIndex, ImagePaths) ||
+            PreLoader.Contains(prevIndex, ImagePaths))
+        {
+            PreLoader.Clear();
+            cleared = true;
+        }
 
-            SetTitleHelper.SetTitle(_vm);
+        SetTitleHelper.SetTitle(_vm);
 
-            var isGalleryItemAdded = await GalleryFunctions.AddGalleryItem(index, fileInfo, _vm);
-            if (isGalleryItemAdded)
+        var isGalleryItemAdded = await GalleryFunctions.AddGalleryItem(index, fileInfo, _vm);
+        if (isGalleryItemAdded)
+        {
+            if (SettingsHelper.Settings.Gallery.IsBottomGalleryShown && ImagePaths.Count > 1)
             {
-                if (SettingsHelper.Settings.Gallery.IsBottomGalleryShown && ImagePaths.Count > 1)
+                if (_vm.GalleryMode is GalleryMode.BottomToClosed or GalleryMode.FullToClosed)
                 {
-                    if (_vm.GalleryMode is GalleryMode.BottomToClosed or GalleryMode.FullToClosed)
-                    {
-                        _vm.GalleryMode = GalleryMode.ClosedToBottom;
-                    }
+                    _vm.GalleryMode = GalleryMode.ClosedToBottom;
                 }
-
-                var indexOf = ImagePaths.IndexOf(_vm.FileInfo.FullName);
-                _vm.SelectedGalleryItemIndex = indexOf; // Fixes deselection bug
-                CurrentIndex = indexOf;
-                GalleryNavigation.CenterScrollToSelectedItem(_vm);
             }
 
-
-            if (cleared)
-            {
-                await Preload();
-            }
+            var indexOf = ImagePaths.IndexOf(_vm.FileInfo.FullName);
+            _vm.SelectedGalleryItemIndex = indexOf; // Fixes deselection bug
+            CurrentIndex = indexOf;
+            GalleryNavigation.CenterScrollToSelectedItem(_vm);
         }
 
-        private async Task OnFileDeleted(FileSystemEventArgs e)
-        {
-            if (IsRenamingInProgress)
-            {
-                return;
-            }
 
-            if (e.FullPath.IsSupported() == false)
-            {
-                return;
-            }
+        if (cleared)
+        {
+            await Preload();
+        }
+    }
 
-            if (ImagePaths.Contains(e.FullPath) == false)
-            {
-                return;
-            }
+    private async Task OnFileDeleted(FileSystemEventArgs e)
+    {
+        if (IsRenamingInProgress)
+        {
+            return;
+        }
 
-            if (_isRunning)
-            {
-                return;
-            }
+        if (e.FullPath.IsSupported() == false)
+        {
+            return;
+        }
 
-            _isRunning = true;
-            var index = ImagePaths.IndexOf(e.FullPath);
-            if (index < 0)
-            {
-                return;
-            }
+        if (ImagePaths.Contains(e.FullPath) == false)
+        {
+            return;
+        }
 
-            var nextIndex = index + 1;
-            if (index >= ImagePaths.Count)
-            {
-                nextIndex = 0;
-            }
+        if (_isRunning)
+        {
+            return;
+        }
 
-            var prevIndex = index - 1;
-            if (prevIndex < 0)
-            {
-                prevIndex = ImagePaths.Count - 1;
-            }
+        _isRunning = true;
+        var index = ImagePaths.IndexOf(e.FullPath);
+        if (index < 0)
+        {
+            return;
+        }
 
-            var cleared = false;
-            if (PreLoader.Contains(index, ImagePaths) || PreLoader.Contains(nextIndex, ImagePaths) ||
-                PreLoader.Contains(prevIndex, ImagePaths))
-            {
-                PreLoader.Clear();
-                cleared = true;
-            }
-            else
-            {
-                PreLoader.Remove(index, ImagePaths);
-            }
+        var nextIndex = index + 1;
+        if (index >= ImagePaths.Count)
+        {
+            nextIndex = 0;
+        }
 
-            var sameFile = CurrentIndex == index;
-            if (!ImagePaths.Remove(e.FullPath))
-            {
-                return;
-            }
+        var prevIndex = index - 1;
+        if (prevIndex < 0)
+        {
+            prevIndex = ImagePaths.Count - 1;
+        }
 
-            if (sameFile)
-            {
-                if (ImagePaths.Count <= 0)
-                {
-                    ErrorHandling.ShowStartUpMenu(_vm);
-                    return;
-                }
+        var cleared = false;
+        if (PreLoader.Contains(index, ImagePaths) || PreLoader.Contains(nextIndex, ImagePaths) ||
+            PreLoader.Contains(prevIndex, ImagePaths))
+        {
+            PreLoader.Clear();
+            cleared = true;
+        }
+        else
+        {
+            PreLoader.Remove(index, ImagePaths);
+        }
 
-                await NavigationHelper.Iterate(false, _vm);
-            }
-            else
-            {
-                SetTitleHelper.SetTitle(_vm);
-            }
+        var sameFile = CurrentIndex == index;
+        if (!ImagePaths.Remove(e.FullPath))
+        {
+            return;
+        }
 
-            var removed = GalleryFunctions.RemoveGalleryItem(index, _vm);
-            if (removed)
+        if (sameFile)
+        {
+            if (ImagePaths.Count <= 0)
             {
-                if (SettingsHelper.Settings.Gallery.IsBottomGalleryShown)
-                {
-                    if (ImagePaths.Count == 1)
-                    {
-                        _vm.GalleryMode = GalleryMode.BottomToClosed;
-                    }
-                }
-
-                var indexOf = ImagePaths.IndexOf(_vm.FileInfo.FullName);
-                _vm.SelectedGalleryItemIndex = indexOf; // Fixes deselection bug
-                CurrentIndex = indexOf;
-                GalleryNavigation.CenterScrollToSelectedItem(_vm);
+                ErrorHandling.ShowStartUpMenu(_vm);
+                return;
             }
 
-
-            FileHistoryNavigation.Remove(e.FullPath);
-            _isRunning = false;
-
+            await NavigationHelper.Iterate(false, _vm);
+        }
+        else
+        {
             SetTitleHelper.SetTitle(_vm);
-            if (cleared)
-            {
-                await Preload();
-            }
         }
 
-        private async Task OnFileRenamed(RenamedEventArgs e)
+        var removed = GalleryFunctions.RemoveGalleryItem(index, _vm);
+        if (removed)
         {
-            if (IsRenamingInProgress)
-            {
-                return;
-            }
-
-            if (e.FullPath.IsSupported() == false)
+            if (SettingsHelper.Settings.Gallery.IsBottomGalleryShown)
             {
-                if (ImagePaths.Contains(e.OldFullPath))
+                if (ImagePaths.Count == 1)
                 {
-                    ImagePaths.Remove(e.OldFullPath);
+                    _vm.GalleryMode = GalleryMode.BottomToClosed;
                 }
-
-                return;
             }
 
-            if (_isRunning)
-            {
-                return;
-            }
-
-            _isRunning = true;
-
-            var oldIndex = ImagePaths.IndexOf(e.OldFullPath);
-
-            var fileInfo = new FileInfo(e.FullPath);
-            if (fileInfo.Exists == false)
-            {
-                return;
-            }
+            var indexOf = ImagePaths.IndexOf(_vm.FileInfo.FullName);
+            _vm.SelectedGalleryItemIndex = indexOf; // Fixes deselection bug
+            CurrentIndex = indexOf;
+            GalleryNavigation.CenterScrollToSelectedItem(_vm);
+        }
 
-            var newList = FileListHelper.RetrieveFiles(fileInfo).ToList();
-            if (newList.Count == 0)
-            {
-                return;
-            }
 
-            if (fileInfo.Exists == false)
-            {
-                return;
-            }
+        FileHistoryNavigation.Remove(e.FullPath);
+        _isRunning = false;
 
-            ImagePaths = newList;
+        SetTitleHelper.SetTitle(_vm);
+        if (cleared)
+        {
+            await Preload();
+        }
+    }
 
-            var index = ImagePaths.IndexOf(e.FullPath);
-            if (index < 0)
-            {
-                return;
-            }
+    private async Task OnFileRenamed(RenamedEventArgs e)
+    {
+        if (IsRenamingInProgress)
+        {
+            return;
+        }
 
-            if (fileInfo.Exists == false)
+        if (e.FullPath.IsSupported() == false)
+        {
+            if (ImagePaths.Contains(e.OldFullPath))
             {
-                return;
+                ImagePaths.Remove(e.OldFullPath);
             }
 
-            SetTitleHelper.SetTitle(_vm);
-
-            await PreLoader.RefreshFileInfo(oldIndex, ImagePaths);
+            return;
+        }
 
-            _isRunning = false;
-            FileHistoryNavigation.Rename(e.OldFullPath, e.FullPath);
-            GalleryFunctions.RemoveGalleryItem(oldIndex, _vm);
-            await GalleryFunctions.AddGalleryItem(index, fileInfo, _vm);
-            await GalleryFunctions.SortGalleryItems(ImagePaths, _vm);
+        if (_isRunning)
+        {
+            return;
         }
 
-        #endregion
+        _isRunning = true;
 
-        #region Preloader
+        var oldIndex = ImagePaths.IndexOf(e.OldFullPath);
 
-        public void Clear()
+        var fileInfo = new FileInfo(e.FullPath);
+        if (fileInfo.Exists == false)
         {
-            PreLoader.Clear();
+            return;
         }
 
-        public async Task Preload()
+        var newList = FileListHelper.RetrieveFiles(fileInfo).ToList();
+        if (newList.Count == 0)
         {
-            await PreLoader.PreLoadAsync(CurrentIndex, ImagePaths.Count, IsReversed, ImagePaths).ConfigureAwait(false);
+            return;
         }
 
-        public async Task AddAsync(int index, ImageModel imageModel)
+        if (fileInfo.Exists == false)
         {
-            await PreLoader.AddAsync(index, ImagePaths, imageModel).ConfigureAwait(false);
+            return;
         }
 
-        public PreLoader.PreLoadValue? GetPreLoadValue(int index)
-        {
-            return PreLoader.Get(index, ImagePaths);
-        }
+        ImagePaths = newList;
 
-        public PreLoader.PreLoadValue? GetCurrentPreLoadValue()
+        var index = ImagePaths.IndexOf(e.FullPath);
+        if (index < 0)
         {
-            return PreLoader.Get(CurrentIndex, ImagePaths);
+            return;
         }
 
-        public async Task<PreLoader.PreLoadValue?> GetCurrentPreLoadValueAsync()
+        if (fileInfo.Exists == false)
         {
-            return await PreLoader.GetAsync(CurrentIndex, ImagePaths);
+            return;
         }
 
-        public PreLoader.PreLoadValue? GetNextPreLoadValue()
-        {
-            var nextIndex = GetIteration(CurrentIndex, NavigateTo.Next);
-            return PreLoader.Get(nextIndex, ImagePaths);
-        }
+        SetTitleHelper.SetTitle(_vm);
 
-        public async Task<PreLoader.PreLoadValue?>? GetNextPreLoadValueAsync()
-        {
-            var nextIndex = GetIteration(CurrentIndex, NavigateTo.Next);
-            return await PreLoader.GetAsync(nextIndex, ImagePaths);
-        }
+        await PreLoader.RefreshFileInfo(oldIndex, ImagePaths);
 
-        public void RemoveItemFromPreLoader(int index)
-        {
-            PreLoader.Remove(index, ImagePaths);
-        }
+        _isRunning = false;
+        FileHistoryNavigation.Rename(e.OldFullPath, e.FullPath);
+        GalleryFunctions.RemoveGalleryItem(oldIndex, _vm);
+        await GalleryFunctions.AddGalleryItem(index, fileInfo, _vm);
+        await GalleryFunctions.SortGalleryItems(ImagePaths, _vm);
+    }
 
-        public void RemoveCurrentItemFromPreLoader()
-        {
-            PreLoader.Remove(CurrentIndex, ImagePaths);
-        }
+    #endregion
 
-        #endregion
+    #region Preloader
 
-        #region Navigation
+    public void Clear()
+    {
+        PreLoader.Clear();
+    }
 
-        public async Task ReloadFileList()
-        {
-            ImagePaths = await Task.FromResult(_vm.PlatformService.GetFiles(InitialFileInfo)).ConfigureAwait(false);
-            CurrentIndex = ImagePaths.IndexOf(InitialFileInfo.FullName);
-            InitiateFileSystemWatcher(InitialFileInfo);
-        }
+    public async Task Preload()
+    {
+        await PreLoader.PreLoadAsync(CurrentIndex, ImagePaths.Count, IsReversed, ImagePaths).ConfigureAwait(false);
+    }
 
-        public int GetIteration(int index, NavigateTo navigateTo, bool skip1 = false)
-        {
-            int next;
-            var skipAmount = skip1 ? 2 : 1;
+    public async Task AddAsync(int index, ImageModel imageModel)
+    {
+        await PreLoader.AddAsync(index, ImagePaths, imageModel).ConfigureAwait(false);
+    }
 
-            switch (navigateTo)
-            {
-                case NavigateTo.Next:
-                case NavigateTo.Previous:
-                    var indexChange = navigateTo == NavigateTo.Next ? skipAmount : -skipAmount;
-                    IsReversed = navigateTo == NavigateTo.Previous;
-            
-                    if (SettingsHelper.Settings.UIProperties.Looping)
-                    {
-                        next = (index + indexChange + ImagePaths.Count) % ImagePaths.Count;
-                    }
-                    else
-                    {
-                        var newIndex = index + indexChange;
-                
-                        // Ensure the new index doesn't go out of bounds
-                        if (newIndex < 0)
-                        {
-                            return 0;
-                        }
+    public PreLoader.PreLoadValue? GetPreLoadValue(int index)
+    {
+        return PreLoader.Get(index, ImagePaths);
+    }
 
-                        if (newIndex >= ImagePaths.Count)
-                        {
-                            return ImagePaths.Count - 1;
-                        }
+    public PreLoader.PreLoadValue? GetCurrentPreLoadValue()
+    {
+        return PreLoader.Get(CurrentIndex, ImagePaths);
+    }
 
-                        next = newIndex;
-                    }
-                    break;
+    public async Task<PreLoader.PreLoadValue?> GetCurrentPreLoadValueAsync()
+    {
+        return await PreLoader.GetAsync(CurrentIndex, ImagePaths);
+    }
 
-                case NavigateTo.First:
-                case NavigateTo.Last:
-                    if (ImagePaths.Count > PreLoader.MaxCount)
-                    {
-                        PreLoader.Clear();
-                    }
+    public PreLoader.PreLoadValue? GetNextPreLoadValue()
+    {
+        var nextIndex = GetIteration(CurrentIndex, NavigateTo.Next);
+        return PreLoader.Get(nextIndex, ImagePaths);
+    }
 
-                    next = navigateTo == NavigateTo.First ? 0 : ImagePaths.Count - 1;
-                    break;
+    public async Task<PreLoader.PreLoadValue?>? GetNextPreLoadValueAsync()
+    {
+        var nextIndex = GetIteration(CurrentIndex, NavigateTo.Next);
+        return await PreLoader.GetAsync(nextIndex, ImagePaths);
+    }
 
-                default: 
-                    return -1;
-            }
+    public void RemoveItemFromPreLoader(int index)
+    {
+        PreLoader.Remove(index, ImagePaths);
+    }
 
-            return next;
-        }
+    public void RemoveCurrentItemFromPreLoader()
+    {
+        PreLoader.Remove(CurrentIndex, ImagePaths);
+    }
 
-        public async Task NextIteration(NavigateTo navigateTo)
-        {
-            var index = GetIteration(CurrentIndex, navigateTo, SettingsHelper.Settings.ImageScaling.ShowImageSideBySide);
-            if (index < 0)
-            {
-                return;
-            }
+    #endregion
 
-            if (!MainKeyboardShortcuts.IsKeyHeldDown)
-            {
-                await IterateToIndex(index);
-            }
-            else
-            {
-                await TimerIteration(index);
-            }
-        }
+    #region Navigation
+
+    public async Task ReloadFileList()
+    {
+        ImagePaths = await Task.FromResult(_vm.PlatformService.GetFiles(InitialFileInfo)).ConfigureAwait(false);
+        CurrentIndex = ImagePaths.IndexOf(_vm.FileInfo.FullName);
+
+        InitiateFileSystemWatcher(InitialFileInfo);
+    }
+
+    public int GetIteration(int index, NavigateTo navigateTo, bool skip1 = false)
+    {
+        int next;
+        var skipAmount = skip1 ? 2 : 1;
 
-        public async Task IterateToIndex(int index)
+        switch (navigateTo)
         {
-            if (index < 0 || index >= ImagePaths.Count)
-            {
-                ErrorHandling.ShowStartUpMenu(_vm);
-                return;
-            }
+            case NavigateTo.Next:
+            case NavigateTo.Previous:
+                var indexChange = navigateTo == NavigateTo.Next ? skipAmount : -skipAmount;
+                IsReversed = navigateTo == NavigateTo.Previous;
 
-            try
-            {
-                lock (_lock)
+                if (SettingsHelper.Settings.UIProperties.Looping)
                 {
-                    CurrentIndex = index;
+                    next = (index + indexChange + ImagePaths.Count) % ImagePaths.Count;
                 }
-
-                // ReSharper disable once MethodHasAsyncOverload
-                var preloadValue = PreLoader.Get(index, ImagePaths);
-                if (preloadValue is not null)
+                else
                 {
-                    if (preloadValue.IsLoading)
+                    var newIndex = index + indexChange;
+
+                    // Ensure the new index doesn't go out of bounds
+                    if (newIndex < 0)
                     {
-                        TryShowPreview(preloadValue);
+                        return 0;
                     }
 
-                    while (preloadValue.IsLoading)
+                    if (newIndex >= ImagePaths.Count)
                     {
-                        await Task.Delay(20);
-                        lock (_lock)
-                        {
-                            if (CurrentIndex != index)
-                            {
-                                // Skip loading if user went to next value
-                                return;
-                            }
-                        }
+                        return ImagePaths.Count - 1;
                     }
+
+                    next = newIndex;
                 }
-                else
+
+                break;
+
+            case NavigateTo.First:
+            case NavigateTo.Last:
+                if (ImagePaths.Count > PreLoader.MaxCount)
                 {
-                    TryShowPreview(preloadValue);
-                    preloadValue = await PreLoader.GetAsync(CurrentIndex, ImagePaths).ConfigureAwait(false);
+                    PreLoader.Clear();
                 }
 
-                lock (_lock)
+                next = navigateTo == NavigateTo.First ? 0 : ImagePaths.Count - 1;
+                break;
+
+            default:
+                return -1;
+        }
+
+        return next;
+    }
+
+    public async Task NextIteration(NavigateTo navigateTo)
+    {
+        var index = GetIteration(CurrentIndex, navigateTo, SettingsHelper.Settings.ImageScaling.ShowImageSideBySide);
+        if (index < 0)
+        {
+            return;
+        }
+
+        if (!MainKeyboardShortcuts.IsKeyHeldDown)
+        {
+            await IterateToIndex(index);
+        }
+        else
+        {
+            await TimerIteration(index);
+        }
+    }
+
+    public async Task IterateToIndex(int index)
+    {
+        if (index < 0 || index >= ImagePaths.Count)
+        {
+            ErrorHandling.ShowStartUpMenu(_vm);
+            return;
+        }
+
+        try
+        {
+            lock (_lock)
+            {
+                CurrentIndex = index;
+            }
+
+            // ReSharper disable once MethodHasAsyncOverload
+            var preloadValue = PreLoader.Get(index, ImagePaths);
+            if (preloadValue is not null)
+            {
+                if (preloadValue.IsLoading)
                 {
-                    if (CurrentIndex != index)
-                    {
-                        // Skip loading if user went to next value
-                        return;
-                    }
+                    TryShowPreview(preloadValue);
                 }
 
-                if (SettingsHelper.Settings.ImageScaling.ShowImageSideBySide)
+                while (preloadValue.IsLoading)
                 {
-                    var nextPreloadValue = await GetNextPreLoadValueAsync().ConfigureAwait(false);
+                    await Task.Delay(20);
                     lock (_lock)
                     {
                         if (CurrentIndex != index)
@@ -557,257 +531,295 @@ namespace PicView.Avalonia.Navigation
                             return;
                         }
                     }
-                    _vm.SecondaryImageSource = nextPreloadValue.ImageModel.Image;
-                    await UpdateSource(index, preloadValue, nextPreloadValue).ConfigureAwait(false);
                 }
-                else
+            }
+            else
+            {
+                TryShowPreview(preloadValue);
+                preloadValue = await PreLoader.GetAsync(CurrentIndex, ImagePaths).ConfigureAwait(false);
+            }
+
+            lock (_lock)
+            {
+                if (CurrentIndex != index)
                 {
-                    await UpdateSource(index, preloadValue).ConfigureAwait(false);
+                    // Skip loading if user went to next value
+                    return;
                 }
-                
-                if (ImagePaths.Count > 1)
+            }
+
+            if (SettingsHelper.Settings.ImageScaling.ShowImageSideBySide)
+            {
+                var nextPreloadValue = await GetNextPreLoadValueAsync().ConfigureAwait(false);
+                lock (_lock)
                 {
-                    if (SettingsHelper.Settings.UIProperties.IsTaskbarProgressEnabled)
+                    if (CurrentIndex != index)
                     {
-                        await Dispatcher.UIThread.InvokeAsync(() =>
-                        {
-                            _vm.PlatformService.SetTaskbarProgress((ulong)CurrentIndex, (ulong)ImagePaths.Count);
-                        });
+                        // Skip loading if user went to next value
+                        return;
                     }
-
-                    _ = Task.Run(Preload);
                 }
-                
-                await AddAsync(index, preloadValue.ImageModel);
 
-                // Add recent files, except when browsing archive
-                if (string.IsNullOrWhiteSpace(TempFileHelper.TempFilePath) && ImagePaths.Count > index)
-                {
-                    FileHistoryNavigation.Add(ImagePaths[index]);
-                }
-            }
-            catch (Exception e)
-            {
-#if DEBUG
-                Console.WriteLine($"{nameof(IterateToIndex)} exception: \n{e.Message}");
-                await TooltipHelper.ShowTooltipMessageAsync(e.Message);
-#endif
+                _vm.SecondaryImageSource = nextPreloadValue.ImageModel.Image;
+                await UpdateSource(index, preloadValue, nextPreloadValue).ConfigureAwait(false);
             }
-            finally
+            else
             {
-                _vm.IsLoading = false;
+                await UpdateSource(index, preloadValue).ConfigureAwait(false);
             }
-            
-            return;
 
-            void TryShowPreview(PreLoader.PreLoadValue preloadValue)
+            if (ImagePaths.Count > 1)
             {
-                if (preloadValue is null)
-                    return;
-                
-                if (!preloadValue.IsLoading)
-                    return;
-
-                if (index != CurrentIndex)
-                    return;
-
-                if (SettingsHelper.Settings.ImageScaling.ShowImageSideBySide)
+                if (SettingsHelper.Settings.UIProperties.IsTaskbarProgressEnabled)
                 {
-                    SetTitleHelper.SetLoadingTitle(_vm);
-                    _vm.IsLoading = true;
-                    _vm.ImageSource = null;
-                    _vm.SecondaryImageSource = null;
-                }
-                else
-                {
-                    LoadingPreview(index);
+                    await Dispatcher.UIThread.InvokeAsync(() =>
+                    {
+                        _vm.PlatformService.SetTaskbarProgress((ulong)CurrentIndex, (ulong)ImagePaths.Count);
+                    });
                 }
+
+                _ = Task.Run(Preload);
             }
-        }
 
-        private static Timer? _timer;
+            await AddAsync(index, preloadValue.ImageModel);
 
-        internal async Task TimerIteration(int index)
-        {
-            if (_timer is null)
-            {
-                _timer = new Timer
-                {
-                    AutoReset = false,
-                    Enabled = true
-                };
-            }
-            else if (_timer.Enabled)
+            // Add recent files, except when browsing archive
+            if (string.IsNullOrWhiteSpace(TempFileHelper.TempFilePath) && ImagePaths.Count > index)
             {
-                if (!MainKeyboardShortcuts.IsKeyHeldDown)
-                {
-                    _timer = null;
-                }
-
-                return;
+                FileHistoryNavigation.Add(ImagePaths[index]);
             }
-
-            _timer.Interval = TimeSpan.FromSeconds(SettingsHelper.Settings.UIProperties.NavSpeed).TotalMilliseconds;
-            _timer.Start();
-            await IterateToIndex(index);
         }
-
-        public void UpdateFileListAndIndex(List<string> fileList, int index)
+        catch (Exception e)
         {
-            ImagePaths = fileList;
-            CurrentIndex = index;
+#if DEBUG
+            Console.WriteLine($"{nameof(IterateToIndex)} exception: \n{e.Message}");
+            await TooltipHelper.ShowTooltipMessageAsync(e.Message);
+#endif
+        }
+        finally
+        {
+            _vm.IsLoading = false;
         }
 
-        #endregion
-
-        #region Update Source and Preview
+        return;
 
-        private async Task UpdateSource(int index, PreLoader.PreLoadValue? preLoadValue, PreLoader.PreLoadValue? nextPreloadValue = null)
+        void TryShowPreview(PreLoader.PreLoadValue preloadValue)
         {
-            preLoadValue ??= await PreLoader.GetAsync(index, ImagePaths);
-            if (preLoadValue.ImageModel?.Image is null)
+            if (preloadValue is null)
             {
-                var fileInfo = preLoadValue.ImageModel?.FileInfo ?? new FileInfo(ImagePaths[index]);
-                preLoadValue.ImageModel = await ImageHelper.GetImageModelAsync(fileInfo).ConfigureAwait(false);
+                return;
             }
 
-            if (SettingsHelper.Settings.ImageScaling.ShowImageSideBySide)
+            if (!preloadValue.IsLoading)
             {
-                nextPreloadValue ??= await GetNextPreLoadValueAsync();
-                if (nextPreloadValue.ImageModel?.Image is null)
-                {
-                    var fileInfo = nextPreloadValue.ImageModel?.FileInfo ?? new FileInfo(ImagePaths[GetIteration(index, IsReversed ? NavigateTo.Previous : NavigateTo.Next, true)]);
-                    nextPreloadValue.ImageModel = await ImageHelper.GetImageModelAsync(fileInfo).ConfigureAwait(false);
-                }
+                return;
+            }
+
+            if (index != CurrentIndex)
+            {
+                return;
             }
 
-            _vm.IsLoading = false;
-            ExifHandling.SetImageModel(preLoadValue.ImageModel, _vm);
             if (SettingsHelper.Settings.ImageScaling.ShowImageSideBySide)
             {
-                _vm.SecondaryImageSource = nextPreloadValue.ImageModel.Image;
+                SetTitleHelper.SetLoadingTitle(_vm);
+                _vm.IsLoading = true;
+                _vm.ImageSource = null;
+                _vm.SecondaryImageSource = null;
             }
-            _vm.ImageSource = preLoadValue.ImageModel.Image;
-            if (preLoadValue.ImageModel.ImageType is ImageType.AnimatedGif or ImageType.AnimatedWebp)
+            else
             {
-                _vm.ImageViewer.MainImage.InitialAnimatedSource = preLoadValue.ImageModel.FileInfo.FullName;
+                LoadingPreview(index);
             }
+        }
+    }
 
-            _vm.ImageType = preLoadValue.ImageModel.ImageType;
-            await Dispatcher.UIThread.InvokeAsync(() =>
-            {
-                WindowHelper.SetSize(preLoadValue.ImageModel.PixelWidth, preLoadValue.ImageModel.PixelHeight,
-                   nextPreloadValue?.ImageModel?.PixelWidth ?? 0, nextPreloadValue?.ImageModel?.PixelHeight ?? 0, preLoadValue.ImageModel.Rotation, _vm);
-                SetTitleHelper.SetTitle(_vm, preLoadValue.ImageModel);
-            });
+    private static Timer? _timer;
 
-            if (_vm.RotationAngle != 0)
+    internal async Task TimerIteration(int index)
+    {
+        if (_timer is null)
+        {
+            _timer = new Timer
             {
-                await Dispatcher.UIThread.InvokeAsync(() => { _vm.ImageViewer.Rotate(_vm.RotationAngle); });
-            }
-
-            if (SettingsHelper.Settings.WindowProperties.KeepCentered)
+                AutoReset = false,
+                Enabled = true
+            };
+        }
+        else if (_timer.Enabled)
+        {
+            if (!MainKeyboardShortcuts.IsKeyHeldDown)
             {
-                await Dispatcher.UIThread.InvokeAsync(() => { WindowHelper.CenterWindowOnScreen(false); });
+                _timer = null;
             }
 
-            _vm.GetIndex = index + 1;
-            if (_vm.SelectedGalleryItemIndex != index)
-            {
-                _vm.SelectedGalleryItemIndex = index;
-                if (SettingsHelper.Settings.Gallery.IsBottomGalleryShown)
-                {
-                    GalleryNavigation.CenterScrollToSelectedItem(_vm);
-                }
-            }
+            return;
+        }
 
-            await Dispatcher.UIThread.InvokeAsync(TooltipHelper.CloseToolTipMessage);
+        _timer.Interval = TimeSpan.FromSeconds(SettingsHelper.Settings.UIProperties.NavSpeed).TotalMilliseconds;
+        _timer.Start();
+        await IterateToIndex(index);
+    }
+
+    public void UpdateFileListAndIndex(List<string> fileList, int index)
+    {
+        ImagePaths = fileList;
+        CurrentIndex = index;
+    }
 
-            ExifHandling.UpdateExifValues(preLoadValue.ImageModel, _vm);
+    #endregion
+
+    #region Update Source and Preview
+
+    private async Task UpdateSource(int index, PreLoader.PreLoadValue? preLoadValue,
+        PreLoader.PreLoadValue? nextPreloadValue = null)
+    {
+        preLoadValue ??= await PreLoader.GetAsync(index, ImagePaths);
+        if (preLoadValue.ImageModel?.Image is null)
+        {
+            var fileInfo = preLoadValue.ImageModel?.FileInfo ?? new FileInfo(ImagePaths[index]);
+            preLoadValue.ImageModel = await ImageHelper.GetImageModelAsync(fileInfo).ConfigureAwait(false);
         }
 
-        public void LoadingPreview(int index)
+        if (SettingsHelper.Settings.ImageScaling.ShowImageSideBySide)
         {
-            if (index != CurrentIndex)
+            nextPreloadValue ??= await GetNextPreLoadValueAsync();
+            if (nextPreloadValue.ImageModel?.Image is null)
             {
-                return;
+                var fileInfo = nextPreloadValue.ImageModel?.FileInfo ?? new FileInfo(
+                    ImagePaths[GetIteration(index, IsReversed ? NavigateTo.Previous : NavigateTo.Next, true)]);
+                nextPreloadValue.ImageModel = await ImageHelper.GetImageModelAsync(fileInfo).ConfigureAwait(false);
             }
+        }
 
-            SetTitleHelper.SetLoadingTitle(_vm);
+        _vm.IsLoading = false;
+        ExifHandling.SetImageModel(preLoadValue.ImageModel, _vm);
+        if (SettingsHelper.Settings.ImageScaling.ShowImageSideBySide)
+        {
+            _vm.SecondaryImageSource = nextPreloadValue.ImageModel.Image;
+        }
+
+        _vm.ImageSource = preLoadValue.ImageModel.Image;
+        if (preLoadValue.ImageModel.ImageType is ImageType.AnimatedGif or ImageType.AnimatedWebp)
+        {
+            _vm.ImageViewer.MainImage.InitialAnimatedSource = preLoadValue.ImageModel.FileInfo.FullName;
+        }
+
+        _vm.ImageType = preLoadValue.ImageModel.ImageType;
+        await Dispatcher.UIThread.InvokeAsync(() =>
+        {
+            WindowHelper.SetSize(preLoadValue.ImageModel.PixelWidth, preLoadValue.ImageModel.PixelHeight,
+                nextPreloadValue?.ImageModel?.PixelWidth ?? 0, nextPreloadValue?.ImageModel?.PixelHeight ?? 0,
+                preLoadValue.ImageModel.Rotation, _vm);
+            SetTitleHelper.SetTitle(_vm, preLoadValue.ImageModel);
+        });
+
+        if (_vm.RotationAngle != 0)
+        {
+            await Dispatcher.UIThread.InvokeAsync(() => { _vm.ImageViewer.Rotate(_vm.RotationAngle); });
+        }
+
+        if (SettingsHelper.Settings.WindowProperties.KeepCentered)
+        {
+            await Dispatcher.UIThread.InvokeAsync(() => { WindowHelper.CenterWindowOnScreen(false); });
+        }
+
+        _vm.GetIndex = index + 1;
+        if (_vm.SelectedGalleryItemIndex != index)
+        {
             _vm.SelectedGalleryItemIndex = index;
             if (SettingsHelper.Settings.Gallery.IsBottomGalleryShown)
             {
                 GalleryNavigation.CenterScrollToSelectedItem(_vm);
             }
+        }
 
-            using var image = new MagickImage();
-            image.Ping(_vm.ImageIterator.ImagePaths[index]);
-            var thumb = image.GetExifProfile()?.CreateThumbnail();
-            if (thumb is null)
-            {
-                if (index == CurrentIndex)
-                {
-                    _vm.IsLoading = true;
-                    _vm.ImageSource = null;
-                }
+        await Dispatcher.UIThread.InvokeAsync(TooltipHelper.CloseToolTipMessage);
 
-                return;
-            }
+        ExifHandling.UpdateExifValues(preLoadValue.ImageModel, _vm);
+    }
 
-            var byteArray = thumb.ToByteArray();
-            if (byteArray is null)
-            {
-                if (index == CurrentIndex)
-                {
-                    _vm.IsLoading = true;
-                    _vm.ImageSource = null;
-                }
+    public void LoadingPreview(int index)
+    {
+        if (index != CurrentIndex)
+        {
+            return;
+        }
 
-                return;
-            }
+        SetTitleHelper.SetLoadingTitle(_vm);
+        _vm.SelectedGalleryItemIndex = index;
+        if (SettingsHelper.Settings.Gallery.IsBottomGalleryShown)
+        {
+            GalleryNavigation.CenterScrollToSelectedItem(_vm);
+        }
 
-            var stream = new MemoryStream(byteArray);
-            if (index != CurrentIndex)
+        using var image = new MagickImage();
+        image.Ping(_vm.ImageIterator.ImagePaths[index]);
+        var thumb = image.GetExifProfile()?.CreateThumbnail();
+        if (thumb is null)
+        {
+            if (index == CurrentIndex)
             {
-                return;
+                _vm.IsLoading = true;
+                _vm.ImageSource = null;
             }
 
-            _vm.ImageSource = new Bitmap(stream);
-            _vm.ImageType = ImageType.Bitmap;
+            return;
         }
 
-        #endregion
+        var byteArray = thumb.ToByteArray();
+        if (byteArray is null)
+        {
+            if (index == CurrentIndex)
+            {
+                _vm.IsLoading = true;
+                _vm.ImageSource = null;
+            }
 
-        #region IDisposable
+            return;
+        }
 
-        public void Dispose()
+        var stream = new MemoryStream(byteArray);
+        if (index != CurrentIndex)
         {
-            Dispose(true);
-            GC.SuppressFinalize(this);
+            return;
         }
 
-        private void Dispose(bool disposing)
-        {
-            if (_disposed)
-            {
-                return;
-            }
+        _vm.ImageSource = new Bitmap(stream);
+        _vm.ImageType = ImageType.Bitmap;
+    }
 
-            if (disposing)
-            {
-                _watcher?.Dispose();
-                Clear();
-                _timer?.Dispose();
-            }
+    #endregion
+
+    #region IDisposable
+
+    public void Dispose()
+    {
+        Dispose(true);
+        GC.SuppressFinalize(this);
+    }
 
-            _disposed = true;
+    private void Dispose(bool disposing)
+    {
+        if (_disposed)
+        {
+            return;
         }
 
-        ~ImageIterator()
+        if (disposing)
         {
-            Dispose(false);
+            _watcher?.Dispose();
+            Clear();
+            _timer?.Dispose();
         }
 
-        #endregion
+        _disposed = true;
     }
+
+    ~ImageIterator()
+    {
+        Dispose(false);
+    }
+
+    #endregion
 }

+ 41 - 34
src/PicView.Core/Navigation/TitleHelper.cs

@@ -1,7 +1,7 @@
-using PicView.Core.FileHandling;
+using System.Diagnostics;
+using Cysharp.Text;
+using PicView.Core.FileHandling;
 using PicView.Core.Localization;
-using System.Diagnostics;
-using System.Text;
 
 namespace PicView.Core.Navigation;
 
@@ -55,39 +55,39 @@ public static class TitleHelper
         var files = filesList.Count == 1
             ? TranslationHelper.Translation.File
             : TranslationHelper.Translation.Files;
-
-        var stringBuilder = new StringBuilder(90);
-        stringBuilder.Append(fileInfo.Name)
-            .Append(' ')
-            .Append(index + 1)
-            .Append('/')
-            .Append(filesList.Count)
-            .Append(' ')
-            .Append(files)
-            .Append(" (")
-            .Append(width)
-            .Append(" x ")
-            .Append(height)
-            .Append(StringAspect(width, height))
-            .Append(fileInfo.Length.GetReadableFileSize());
+        
+        using var sb = ZString.CreateStringBuilder(true);
+        sb.Append(fileInfo.Name);
+        sb.Append(' ');
+        sb.Append(index + 1);
+        sb.Append('/');
+        sb.Append(filesList.Count);
+        sb.Append(' ');
+        sb.Append(files);
+        sb.Append(" (");
+        sb.Append(width);
+        sb.Append(" x ");
+        sb.Append(height);
+        sb.Append(StringAspect(width, height));
+        sb.Append(fileInfo.Length.GetReadableFileSize());
 
         // Check if ZoomPercentage is not empty
         if (!string.IsNullOrEmpty(ZoomPercentage(zoomValue)))
         {
-            stringBuilder.Append(", ")
-                .Append(ZoomPercentage(zoomValue));
+            sb.Append(", ");
+            sb.Append(ZoomPercentage(zoomValue));
         }
 
-        stringBuilder.Append(" - ")
-            .Append(AppName);
+        sb.Append(" - ");
+        sb.Append(AppName);
 
         var array = new string[3];
-        array[0] = stringBuilder.ToString();
-        stringBuilder.Remove(stringBuilder.Length - (AppName.Length + 3),
+        array[0] = sb.ToString();
+        sb.Remove(sb.Length - (AppName.Length + 3),
             AppName.Length + 3); // Remove AppName + " - "
-        array[1] = stringBuilder.ToString();
-        stringBuilder.Replace(Path.GetFileName(filesList[index]), filesList[index]);
-        array[2] = stringBuilder.ToString();
+        array[1] = sb.ToString();
+        sb.Replace(fileInfo.Name, filesList[index]);
+        array[2] = sb.ToString();
         return array;
     }
 
@@ -127,20 +127,27 @@ public static class TitleHelper
     /// <returns></returns>
     public static string[] TitleString(int width, int height, string path, double zoomValue)
     {
-        var s1 = new StringBuilder();
-        s1.Append(path).Append(" (").Append(width).Append(" x ").Append(height).Append(StringAspect(width, height));
+        using var sb = ZString.CreateStringBuilder(true);
+        sb.Append(path);
+        sb.Append(" (");
+        sb.Append(width);
+        sb.Append(" x ");
+        sb.Append(height);
+        sb.Append(StringAspect(width, height));
 
         if (!string.IsNullOrEmpty(ZoomPercentage(zoomValue)))
         {
-            s1.Append(", ").Append((string?)ZoomPercentage(zoomValue));
+            sb.Append(", ");
+            sb.Append((string?)ZoomPercentage(zoomValue));
         }
 
-        s1.Append(" - ").Append(AppName);
+        sb.Append(" - ");
+        sb.Append(AppName);
 
         var array = new string[2];
-        array[0] = s1.ToString();
-        s1.Remove(s1.Length - (AppName.Length + 3), AppName.Length + 3); // Remove AppName + " - "
-        array[1] = s1.ToString();
+        array[0] = sb.ToString();
+        sb.Remove(sb.Length - (AppName.Length + 3), AppName.Length + 3); // Remove AppName + " - "
+        array[1] = sb.ToString();
         return array;
     }
 

+ 1 - 0
src/PicView.Core/PicView.Core.csproj

@@ -24,6 +24,7 @@
   <ItemGroup>
     <PackageReference Include="Magick.NET-Q8-OpenMP-x64" Version="14.0.0" />
     <PackageReference Include="SharpCompress" Version="0.38.0" />
+    <PackageReference Include="ZString" Version="2.6.0" />
   </ItemGroup>
 
   <ItemGroup>