Browse Source

[Avalonia] Gallery thumbnail stretching, refactor, misc

Ruben 1 year ago
parent
commit
14799e417b
28 changed files with 665 additions and 216 deletions
  1. 111 54
      src/PicView.Avalonia.MacOS/App.axaml.cs
  2. 112 52
      src/PicView.Avalonia.Win32/App.axaml.cs
  3. 9 8
      src/PicView.Avalonia/CustomControls/GalleryAnimationControl.cs
  4. 69 0
      src/PicView.Avalonia/CustomControls/GalleryListBox.cs
  5. 3 3
      src/PicView.Avalonia/Gallery/GalleryFunctions.cs
  6. 1 1
      src/PicView.Avalonia/Gallery/GalleryLoad.cs
  7. 5 7
      src/PicView.Avalonia/Gallery/GalleryNavigation.cs
  8. 134 0
      src/PicView.Avalonia/Helpers/GalleryHelper.cs
  9. 13 4
      src/PicView.Avalonia/Helpers/StartUpHelper.cs
  10. 41 0
      src/PicView.Avalonia/Helpers/UIHelper.cs
  11. 5 0
      src/PicView.Avalonia/ImageTransformations/RotationAndFlip.cs
  12. 5 0
      src/PicView.Avalonia/ImageTransformations/Zoom.cs
  13. 2 1
      src/PicView.Avalonia/Navigation/NavigationHelper.cs
  14. 86 17
      src/PicView.Avalonia/ViewModels/MainViewModel.cs
  15. 9 0
      src/PicView.Avalonia/ViewModels/ViewModelBase.cs
  16. 2 2
      src/PicView.Avalonia/Views/AppearanceView.axaml
  17. 11 1
      src/PicView.Avalonia/Views/AppearanceView.axaml.cs
  18. 27 6
      src/PicView.Avalonia/Views/GalleryView.axaml
  19. 2 45
      src/PicView.Avalonia/Views/GalleryView.axaml.cs
  20. 1 1
      src/PicView.Avalonia/Views/MainView.axaml
  21. 1 1
      src/PicView.Avalonia/Views/UC/BottomGalleryItemSizeSlider.axaml
  22. 2 2
      src/PicView.Avalonia/Views/UC/BottomGalleryItemSizeSlider.axaml.cs
  23. 1 1
      src/PicView.Avalonia/Views/UC/FullGalleryItemSizeSlider.axaml
  24. 2 2
      src/PicView.Avalonia/Views/UC/FullGalleryItemSizeSlider.axaml.cs
  25. 3 2
      src/PicView.Avalonia/Views/UC/GalleryItem.axaml
  26. 1 1
      src/PicView.Avalonia/Views/UC/GalleryItemSizeSlider.axaml
  27. 5 5
      src/PicView.Avalonia/Views/UC/GalleryItemSizeSlider.axaml.cs
  28. 2 0
      src/PicView.Core/Config/AppSettings.cs

+ 111 - 54
src/PicView.Avalonia.MacOS/App.axaml.cs

@@ -92,97 +92,154 @@ public class App : Application, IPlatformSpecificService
     }
 
 
-    public void ShowAboutWindow()
+public void ShowAboutWindow()
     {
-        if (Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+        if (Dispatcher.UIThread.CheckAccess())
         {
-            return;
-        }
-        if (_aboutWindow is null)
-        {
-            _aboutWindow = new AboutWindow
-            {
-                DataContext = _vm,
-                WindowStartupLocation = WindowStartupLocation.CenterOwner,
-            };
-            _aboutWindow.Show(desktop.MainWindow);
-            _aboutWindow.Closing += (s, e) => _aboutWindow = null;
+            Set();
         }
         else
         {
-            _aboutWindow.Activate();
+            Dispatcher.UIThread.InvokeAsync(Set);
         }
+        return;
+
+        void Set()
+        {
+            if (Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+            {
+                return;
+            }
+
+            if (_aboutWindow is null)
+            {
+                _aboutWindow = new AboutWindow
+                {
+                    DataContext = _vm,
+                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
+                };
+                _aboutWindow.Show(desktop.MainWindow);
+                _aboutWindow.Closing += (s, e) => _aboutWindow = null;
+            }
+            else
+            {
+                _aboutWindow.Activate();
+            }
 
-        FunctionsHelper.CloseMenus();
+            _ = FunctionsHelper.CloseMenus();
+        }
     }
 
     public void ShowExifWindow()
     {
-        if (Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+        if (Dispatcher.UIThread.CheckAccess())
         {
-            return;
+            Set();
         }
-        if (_exifWindow is null)
+        else
         {
-            _exifWindow = new ExifWindow
-            {
-                DataContext = _vm,
-                WindowStartupLocation = WindowStartupLocation.CenterOwner,
-            };
-            _exifWindow.Show(desktop.MainWindow);
-            _exifWindow.Closing += (s, e) => _exifWindow = null;
+            Dispatcher.UIThread.InvokeAsync(Set);
         }
-        else
+        return;
+
+        void Set()
         {
-            _exifWindow.Activate();
+            if (Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+            {
+                return;
+            }
+
+            if (_exifWindow is null)
+            {
+                _exifWindow = new ExifWindow
+                {
+                    DataContext = _vm,
+                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
+                };
+                _exifWindow.Show(desktop.MainWindow);
+                _exifWindow.Closing += (s, e) => _exifWindow = null;
+            }
+            else
+            {
+                _exifWindow.Activate();
+            }
+
+            _ = FunctionsHelper.CloseMenus();
         }
-        FunctionsHelper.CloseMenus();
     }
 
     public void ShowKeybindingsWindow()
     {
-        if (Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+        if (Dispatcher.UIThread.CheckAccess())
         {
-            return;
+            Set();
         }
-        if (_keybindingsWindow is null)
+        else
         {
-            _keybindingsWindow = new KeybindingsWindow
-            {
-                DataContext = _vm,
-                WindowStartupLocation = WindowStartupLocation.CenterOwner,
-            };
-            _keybindingsWindow.Show(desktop.MainWindow);
-            _keybindingsWindow.Closing += (s, e) => _keybindingsWindow = null;
+            Dispatcher.UIThread.InvokeAsync(Set);
         }
-        else
+        return;
+
+        void Set()
         {
-            _keybindingsWindow.Activate();
+            if (Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+            {
+                return;
+            }
+
+            if (_keybindingsWindow is null)
+            {
+                _keybindingsWindow = new KeybindingsWindow
+                {
+                    DataContext = _vm,
+                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
+                };
+                _keybindingsWindow.Show(desktop.MainWindow);
+                _keybindingsWindow.Closing += (s, e) => _keybindingsWindow = null;
+            }
+            else
+            {
+                _keybindingsWindow.Activate();
+            }
+
+            _ = FunctionsHelper.CloseMenus();
         }
-        FunctionsHelper.CloseMenus();
     }
 
     public void ShowSettingsWindow()
     {
-        if (Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+        if (Dispatcher.UIThread.CheckAccess())
         {
-            return;
+            Set();
         }
-        if (_settingsWindow is null)
+        else
         {
-            _settingsWindow = new SettingsWindow
-            {
-                DataContext = _vm,
-                WindowStartupLocation = WindowStartupLocation.CenterOwner,
-            };
-            _settingsWindow.Show(desktop.MainWindow);
-            _settingsWindow.Closing += (s, e) => _settingsWindow = null;
+            Dispatcher.UIThread.InvokeAsync(Set);
         }
-        else
+        return;
+        void Set()
         {
-            _settingsWindow.Activate();
+            if (Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+            {
+                return;
+            }
+            if (_settingsWindow is null)
+            {
+                _settingsWindow = new SettingsWindow
+                {
+                    DataContext = _vm,
+                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
+                };
+                _settingsWindow.Show(desktop.MainWindow);
+                _settingsWindow.Closing += (s, e) => _settingsWindow = null;
+            }
+            else
+            {
+                _settingsWindow.Activate();
+            }
+            _= FunctionsHelper.CloseMenus();
+            
         }
-        FunctionsHelper.CloseMenus();
     }
 
     public void ShowEffectsWindow()

+ 112 - 52
src/PicView.Avalonia.Win32/App.axaml.cs

@@ -10,7 +10,9 @@ using PicView.Core.Config;
 using PicView.Core.FileHandling;
 using PicView.Core.Localization;
 using System.Runtime;
+using System.Windows.Threading;
 using PicView.Core.ProcessHandling;
+using Dispatcher = Avalonia.Threading.Dispatcher;
 using SortHelper = PicView.Avalonia.Helpers.SortHelper;
 
 namespace PicView.Avalonia.Win32;
@@ -90,94 +92,152 @@ public class App : Application, IPlatformSpecificService
 
     public void ShowAboutWindow()
     {
-        if (Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+        if (Dispatcher.UIThread.CheckAccess())
         {
-            return;
+            Set();
         }
-        if (_aboutWindow is null)
+        else
         {
-            _aboutWindow = new AboutWindow
-            {
-                DataContext = _vm,
-                WindowStartupLocation = WindowStartupLocation.CenterOwner,
-            };
-            _aboutWindow.Show(desktop.MainWindow);
-            _aboutWindow.Closing += (s, e) => _aboutWindow = null;
+            Dispatcher.UIThread.InvokeAsync(Set);
         }
-        else
+        return;
+
+        void Set()
         {
-            _aboutWindow.Activate();
+            if (Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+            {
+                return;
+            }
+
+            if (_aboutWindow is null)
+            {
+                _aboutWindow = new AboutWindow
+                {
+                    DataContext = _vm,
+                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
+                };
+                _aboutWindow.Show(desktop.MainWindow);
+                _aboutWindow.Closing += (s, e) => _aboutWindow = null;
+            }
+            else
+            {
+                _aboutWindow.Activate();
+            }
+
+            _ = FunctionsHelper.CloseMenus();
         }
-        _= FunctionsHelper.CloseMenus();
     }
 
     public void ShowExifWindow()
     {
-        if (Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+        if (Dispatcher.UIThread.CheckAccess())
         {
-            return;
+            Set();
         }
-        if (_exifWindow is null)
+        else
         {
-            _exifWindow = new ExifWindow
-            {
-                DataContext = _vm,
-                WindowStartupLocation = WindowStartupLocation.CenterOwner,
-            };
-            _exifWindow.Show(desktop.MainWindow);
-            _exifWindow.Closing += (s, e) => _exifWindow = null;
+            Dispatcher.UIThread.InvokeAsync(Set);
         }
-        else
+        return;
+
+        void Set()
         {
-            _exifWindow.Activate();
+            if (Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+            {
+                return;
+            }
+
+            if (_exifWindow is null)
+            {
+                _exifWindow = new ExifWindow
+                {
+                    DataContext = _vm,
+                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
+                };
+                _exifWindow.Show(desktop.MainWindow);
+                _exifWindow.Closing += (s, e) => _exifWindow = null;
+            }
+            else
+            {
+                _exifWindow.Activate();
+            }
+
+            _ = FunctionsHelper.CloseMenus();
         }
-        _= FunctionsHelper.CloseMenus();
     }
 
     public void ShowKeybindingsWindow()
     {
-        if (Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+        if (Dispatcher.UIThread.CheckAccess())
         {
-            return;
+            Set();
         }
-        if (_keybindingsWindow is null)
+        else
         {
-            _keybindingsWindow = new KeybindingsWindow
-            {
-                DataContext = _vm,
-                WindowStartupLocation = WindowStartupLocation.CenterOwner,
-            };
-            _keybindingsWindow.Show(desktop.MainWindow);
-            _keybindingsWindow.Closing += (s, e) => _keybindingsWindow = null;
+            Dispatcher.UIThread.InvokeAsync(Set);
         }
-        else
+        return;
+
+        void Set()
         {
-            _keybindingsWindow.Activate();
+            if (Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+            {
+                return;
+            }
+
+            if (_keybindingsWindow is null)
+            {
+                _keybindingsWindow = new KeybindingsWindow
+                {
+                    DataContext = _vm,
+                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
+                };
+                _keybindingsWindow.Show(desktop.MainWindow);
+                _keybindingsWindow.Closing += (s, e) => _keybindingsWindow = null;
+            }
+            else
+            {
+                _keybindingsWindow.Activate();
+            }
+
+            _ = FunctionsHelper.CloseMenus();
         }
-        _= FunctionsHelper.CloseMenus();
     }
 
     public void ShowSettingsWindow()
     {
-        if (Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+        if (Dispatcher.UIThread.CheckAccess())
         {
-            return;
+            Set();
         }
-        if (_settingsWindow is null)
+        else
         {
-            _settingsWindow = new SettingsWindow
-            {
-                DataContext = _vm,
-                WindowStartupLocation = WindowStartupLocation.CenterOwner,
-            };
-            _settingsWindow.Show(desktop.MainWindow);
-            _settingsWindow.Closing += (s, e) => _settingsWindow = null;
+            Dispatcher.UIThread.InvokeAsync(Set);
         }
-        else
+        return;
+        void Set()
         {
-            _settingsWindow.Activate();
+            if (Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
+            {
+                return;
+            }
+            if (_settingsWindow is null)
+            {
+                _settingsWindow = new SettingsWindow
+                {
+                    DataContext = _vm,
+                    WindowStartupLocation = WindowStartupLocation.CenterOwner,
+                };
+                _settingsWindow.Show(desktop.MainWindow);
+                _settingsWindow.Closing += (s, e) => _settingsWindow = null;
+            }
+            else
+            {
+                _settingsWindow.Activate();
+            }
+            _= FunctionsHelper.CloseMenus();
+            
         }
-        _= FunctionsHelper.CloseMenus();
     }
     
     public void ShowEffectsWindow()

+ 9 - 8
src/PicView.Avalonia/CustomControls/ImageGallery.cs → src/PicView.Avalonia/CustomControls/GalleryAnimationControl.cs

@@ -14,10 +14,10 @@ using ReactiveUI;
 
 namespace PicView.Avalonia.CustomControls;
 
-public class ImageGallery : UserControl
+public class GalleryAnimationControl : UserControl
 {
     public static readonly AvaloniaProperty<GalleryMode?> GalleryModeProperty =
-        AvaloniaProperty.Register<ImageGallery, GalleryMode?>(nameof(GalleryMode));
+        AvaloniaProperty.Register<GalleryAnimationControl, GalleryMode?>(nameof(GalleryMode));
 
     public GalleryMode GalleryMode
     {
@@ -27,7 +27,7 @@ public class ImageGallery : UserControl
 
     private bool _isAnimating;
 
-    protected ImageGallery()
+    protected GalleryAnimationControl()
     {
         Loaded += (_, _) =>
         {
@@ -68,7 +68,7 @@ public class ImageGallery : UserControl
         });
 
         vm.GalleryOrientation = Orientation.Vertical;
-        vm.GalleryStretch = Stretch.UniformToFill;
+        vm.GalleryStretch = vm.GalleryFullItemStretch;
         vm.IsGalleryCloseIconVisible = true;
         
         const double from = 0d;
@@ -131,7 +131,7 @@ public class ImageGallery : UserControl
         });
         
         vm.GalleryOrientation = Orientation.Horizontal;
-        vm.GalleryStretch = Stretch.UniformToFill;
+        vm.GalleryStretch = vm.GalleryBottomItemStretch;
         vm.IsGalleryCloseIconVisible = false;
         vm.GalleryVerticalAlignment = VerticalAlignment.Bottom;
         
@@ -162,7 +162,6 @@ public class ImageGallery : UserControl
         });
         
         vm.GalleryOrientation = Orientation.Horizontal;
-        vm.GalleryStretch = Stretch.UniformToFill;
         vm.IsGalleryCloseIconVisible = false;
         
         var from = vm.GalleryHeight;
@@ -205,7 +204,7 @@ public class ImageGallery : UserControl
             Height = double.NaN;
             GalleryNavigation.CenterScrollToSelectedItem(vm);
         });
-        vm.GalleryStretch = Stretch.Uniform;
+        vm.GalleryStretch = vm.GalleryFullItemStretch;
         vm.GalleryVerticalAlignment = VerticalAlignment.Stretch;
         _isAnimating = false;
     }
@@ -237,9 +236,11 @@ public class ImageGallery : UserControl
         {
             Height = double.NaN;
             vm.GalleryOrientation = Orientation.Horizontal;
-            vm.GalleryStretch = Stretch.UniformToFill;
+            vm.GalleryStretch = vm.GalleryBottomItemStretch;
             GalleryNavigation.CenterScrollToSelectedItem(vm);
         });
+        
+        
         _isAnimating = false;
     }
 

+ 69 - 0
src/PicView.Avalonia/CustomControls/GalleryListBox.cs

@@ -0,0 +1,69 @@
+using System.Runtime.InteropServices;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.Input;
+using Avalonia.VisualTree;
+using PicView.Core.Config;
+
+namespace PicView.Avalonia.CustomControls;
+public class GalleryListBox : ListBox
+{
+    protected override Type StyleKeyOverride => typeof(ListBox);
+    
+    public GalleryListBox()
+    {
+        SelectionMode = SelectionMode.Single;
+    }
+
+    protected override void OnKeyDown(KeyEventArgs e)
+    {
+        // Disable control from hijacking keys
+        e.Handled = true;
+    }
+    
+    protected override void OnKeyUp(KeyEventArgs e)
+    {
+        // Disable control from hijacking keys
+        e.Handled = true;
+    }
+    
+    protected override void OnPointerWheelChanged(PointerWheelEventArgs e)
+    {
+        e.Handled = true;
+        if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
+        {
+            // macOS already has horizontal scrolling for touchpad
+            return;
+        }
+        var scrollViewer = this.FindDescendantOfType<ScrollViewer>();
+        if (scrollViewer is null)
+        {
+            return;
+        }
+
+        const int speed = 34;
+
+        if (e.Delta.Y > 0)
+        {
+            if (SettingsHelper.Settings.Zoom.HorizontalReverseScroll)
+            {
+                scrollViewer.Offset -= new Vector(speed, 0);
+            }
+            else
+            {
+                scrollViewer.Offset -= new Vector(-speed, 0);
+            }
+        }
+        else
+        {
+            if (SettingsHelper.Settings.Zoom.HorizontalReverseScroll)
+            {
+                scrollViewer.Offset -= new Vector(-speed, 0);
+            }
+            else
+            {
+                scrollViewer.Offset -= new Vector(speed, 0);
+            }
+        }
+    }
+}

+ 3 - 3
src/PicView.Avalonia/Gallery/GalleryFunctions.cs

@@ -29,7 +29,7 @@ public static class GalleryFunctions
                 vm.GalleryMode = GalleryMode.FullToBottom;
                 IsFullGalleryOpen = false;
                 vm.IsGalleryOpen = false;
-                vm.GetGalleryItemSize = vm.GetBottomGalleryItemSize;
+                vm.GetGalleryItemHeight = vm.GetBottomGalleryItemHeight;
             }
             else
             {
@@ -37,7 +37,7 @@ public static class GalleryFunctions
                 vm.GalleryMode = GalleryMode.BottomToFull;
                 IsFullGalleryOpen = true;
                 vm.IsGalleryOpen = true;
-                vm.GetGalleryItemSize = vm.GetExpandedGalleryItemSize;
+                vm.GetGalleryItemHeight = vm.GetExpandedGalleryItemHeight;
             }
         }
         else
@@ -56,7 +56,7 @@ public static class GalleryFunctions
                 IsFullGalleryOpen = true;
                 vm.IsGalleryOpen = true;
                 vm.GalleryMode = GalleryMode.ClosedToFull;
-                vm.GetGalleryItemSize = vm.GetExpandedGalleryItemSize;
+                vm.GetGalleryItemHeight = vm.GetExpandedGalleryItemHeight;
             }
         }
         _ = Task.Run(() => GalleryLoad.LoadGallery(vm, Path.GetDirectoryName(vm.ImageIterator.Pics[0])));

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

@@ -70,7 +70,7 @@ public static class GalleryLoad
         IsLoading = true;
         var cancellationToken = _cancellationTokenSource.Token;
         var index = viewModel.ImageIterator.Index;
-        var galleryItemSize = Math.Max(viewModel.GetBottomGalleryItemSize, viewModel.GetExpandedGalleryItemSize);
+        var galleryItemSize = Math.Max(viewModel.GetBottomGalleryItemHeight, viewModel.GetExpandedGalleryItemHeight);
 
         try
         {

+ 5 - 7
src/PicView.Avalonia/Gallery/GalleryNavigation.cs

@@ -37,7 +37,7 @@ public static class GalleryNavigation
         {
             var mainView = desktop.MainWindow.GetControl<MainView>("MainView");
             var mainGrid = mainView.GetControl<Panel>("MainGrid");
-            var galleryView = mainGrid.GetControl<GalleryView>("GalleryView");
+            var galleryView = mainGrid.GetControl<GalleryAnimationControlView>("GalleryView");
             galleryView.GalleryListBox.ScrollIntoView(vm.SelectedGalleryItemIndex);
             
         }
@@ -125,11 +125,11 @@ public static class GalleryNavigation
         CenterScrollToSelectedItem(vm); // Ensure the selected item is in view
     }
 
-    private static GalleryView GetGallery(IClassicDesktopStyleApplicationLifetime desktop)
+    private static GalleryAnimationControlView GetGallery(IClassicDesktopStyleApplicationLifetime desktop)
     {
         var mainView = desktop.MainWindow.GetControl<MainView>("MainView");
         var mainGrid = mainView.GetControl<Panel>("MainGrid");
-        var galleryView = mainGrid.GetControl<GalleryView>("GalleryView");
+        var galleryView = mainGrid.GetControl<GalleryAnimationControlView>("GalleryView");
 
         return galleryView;
     }
@@ -153,12 +153,10 @@ public static class GalleryNavigation
             return;
         }
         await GalleryFunctions.ToggleGallery(vm);
-        if (vm.SelectedGalleryItemIndex == vm.ImageIterator.Index) 
+        if (vm.SelectedGalleryItemIndex != vm.ImageIterator.Index) 
         {
-           return;
+            await vm.ImageIterator.LoadPicAtIndex(vm.SelectedGalleryItemIndex, vm);
         }
-
-        await vm.ImageIterator.LoadPicAtIndex(vm.SelectedGalleryItemIndex, vm);
     }
 }
 

+ 134 - 0
src/PicView.Avalonia/Helpers/GalleryHelper.cs

@@ -0,0 +1,134 @@
+using Avalonia.Layout;
+using PicView.Avalonia.Gallery;
+using PicView.Avalonia.Navigation;
+using PicView.Avalonia.ViewModels;
+using PicView.Core.Config;
+using PicView.Core.Localization;
+
+namespace PicView.Avalonia.Helpers;
+
+public static class GalleryHelper
+{
+    #region Toggle Gallery 
+    public static bool IsFullGalleryOpen { get; private set; }
+    public static bool IsBottomGalleryOpen { get; private set; }
+
+    public static async Task ToggleGallery(MainViewModel vm)
+    {
+        if (vm is null || !NavigationHelper.CanNavigate(vm))
+        {
+            return;
+        }
+        
+        UIHelper.CloseMenus(vm);
+        if (SettingsHelper.Settings.Gallery.IsBottomGalleryShown)
+        {
+            // Showing bottom gallery is enabled
+            IsBottomGalleryOpen = true;
+            if (IsFullGalleryOpen)
+            {
+                // Switch to bottom gallery
+                vm.GalleryMode = GalleryMode.FullToBottom;
+                IsFullGalleryOpen = false;
+                vm.IsGalleryOpen = false;
+                vm.GetGalleryItemHeight = vm.GetBottomGalleryItemHeight;
+            }
+            else
+            {
+                // Switch to full gallery
+                vm.GalleryMode = GalleryMode.BottomToFull;
+                IsFullGalleryOpen = true;
+                vm.IsGalleryOpen = true;
+                vm.GetGalleryItemHeight = vm.GetExpandedGalleryItemHeight;
+            }
+        }
+        else
+        {
+            IsBottomGalleryOpen = false;
+            if (IsFullGalleryOpen)
+            {
+                // close full gallery
+                IsFullGalleryOpen = false;
+                vm.IsGalleryOpen = false;
+                vm.GalleryMode = GalleryMode.FullToClosed;
+            }
+            else
+            {
+                // open full gallery
+                IsFullGalleryOpen = true;
+                vm.IsGalleryOpen = true;
+                vm.GalleryMode = GalleryMode.ClosedToFull;
+                vm.GetGalleryItemHeight = vm.GetExpandedGalleryItemHeight;
+            }
+        }
+        _ = Task.Run(() => GalleryLoad.LoadGallery(vm, Path.GetDirectoryName(vm.ImageIterator.Pics[0])));
+        await SettingsHelper.SaveSettingsAsync();
+    }
+
+    public static async Task ToggleBottomGallery(MainViewModel vm)
+    {
+        SettingsHelper.Settings.Gallery.IsBottomGalleryShown = !SettingsHelper.Settings.Gallery.IsBottomGalleryShown;
+        await OpenCloseBottomGallery(vm);
+    }
+
+    public static async Task OpenCloseBottomGallery(MainViewModel vm)
+    {
+        if (vm is null)
+        {
+            return;
+        }
+        UIHelper.CloseMenus(vm);
+        
+        if (SettingsHelper.Settings.Gallery.IsBottomGalleryShown)
+        {
+            SettingsHelper.Settings.Gallery.IsBottomGalleryShown = false;
+            IsFullGalleryOpen = false;
+            vm.IsGalleryOpen = false;
+            IsBottomGalleryOpen = false;
+            vm.GalleryMode = GalleryMode.BottomToClosed;
+            vm.GetBottomGallery = TranslationHelper.GetTranslation("ShowBottomGallery");
+            await SettingsHelper.SaveSettingsAsync();
+            return;
+        }
+
+        IsBottomGalleryOpen = true;
+        IsFullGalleryOpen = false;
+        vm.IsGalleryOpen = true;
+        SettingsHelper.Settings.Gallery.IsBottomGalleryShown = true;
+        vm.GalleryMode = GalleryMode.ClosedToBottom;
+        vm.GetBottomGallery = TranslationHelper.GetTranslation("HideBottomGallery");
+        await SettingsHelper.SaveSettingsAsync();
+        if (!NavigationHelper.CanNavigate(vm))
+        {
+            return;
+        }
+        await Task.Run(() => GalleryLoad.LoadGallery(vm, Path.GetDirectoryName(vm.ImageIterator.Pics[0])));
+    }
+
+    public static void OpenBottomGallery(MainViewModel vm)
+    {
+        IsBottomGalleryOpen = true;
+        vm.GalleryMode = GalleryMode.ClosedToBottom;
+        vm.GalleryVerticalAlignment = VerticalAlignment.Bottom;
+    }
+    
+    public static async Task CloseGallery(MainViewModel vm)
+    {
+        if (IsBottomGalleryOpen && !IsFullGalleryOpen)
+        {
+            SettingsHelper.Settings.Gallery.IsBottomGalleryShown = false;
+            IsFullGalleryOpen = false;
+            vm.IsGalleryOpen = false;
+            IsBottomGalleryOpen = false;
+            vm.GalleryMode = GalleryMode.BottomToClosed;
+            vm.GetBottomGallery = TranslationHelper.GetTranslation("ShowBottomGallery");
+            await SettingsHelper.SaveSettingsAsync();
+            return;
+        }
+
+        await ToggleGallery(vm);
+    }
+    #endregion
+    
+    
+}

+ 13 - 4
src/PicView.Avalonia/Helpers/StartUpHelper.cs

@@ -1,6 +1,7 @@
 using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Controls.Primitives;
+using Avalonia.Media;
 using PicView.Avalonia.Gallery;
 using PicView.Avalonia.Keybindings;
 using PicView.Avalonia.Navigation;
@@ -87,14 +88,22 @@ public static class StartUpHelper
         UIHelper.AddMenus(desktop);
         
         // Set default gallery sizes if they are out of range or upgrading from an old version
-        if (vm.GetBottomGalleryItemSize is < 36 or > 110)
+        if (vm.GetBottomGalleryItemHeight is < 36 or > 110)
         {
-            vm.GetBottomGalleryItemSize = 47;
+            vm.GetBottomGalleryItemHeight = 47;
         }
-        if (vm.GetExpandedGalleryItemSize is  < 25 or 110)
+        if (vm.GetExpandedGalleryItemHeight is  < 25 or 110)
         {
-            vm.GetExpandedGalleryItemSize = 47;
+            vm.GetExpandedGalleryItemHeight = 47;
         }
+
+        vm.GalleryBottomItemStretch =
+            Enum.TryParse<Stretch>(SettingsHelper.Settings.Gallery.FullGalleryStretchMode, out var stretchMode) ?
+                stretchMode : Stretch.Uniform;
+
+        vm.GalleryFullItemStretch =
+            Enum.TryParse(SettingsHelper.Settings.Gallery.BottomGalleryStretchMode, out stretchMode) ?
+                stretchMode : Stretch.Uniform;
         
         if (SettingsHelper.Settings.Gallery.IsBottomGalleryShown)
         {

+ 41 - 0
src/PicView.Avalonia/Helpers/UIHelper.cs

@@ -3,6 +3,7 @@ using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Controls.Primitives;
 using Avalonia.Layout;
+using Avalonia.Media;
 using Avalonia.Threading;
 using PicView.Avalonia.Navigation;
 using PicView.Avalonia.ViewModels;
@@ -143,5 +144,45 @@ public class UIHelper
         });
     }
     
+    public static async Task ChangeBottomGalleryItemStretch(MainViewModel vm, Stretch stretch)
+    {
+        vm.GetGalleryItemWidth = double.NaN;
+        vm.GalleryBottomItemStretch = stretch;
+        vm.GalleryStretch = stretch;
+        WindowHelper.SetSize(vm);
+        SettingsHelper.Settings.Gallery.BottomGalleryStretchMode = stretch.ToString();
+        await SettingsHelper.SaveSettingsAsync();
+    }
+    
+    public static async Task ChangeFullGalleryItemStretch(MainViewModel vm, Stretch stretch)
+    {
+        vm.GetGalleryItemWidth = double.NaN;
+        vm.GalleryFullItemStretch = stretch;
+        vm.GalleryStretch = stretch;
+        WindowHelper.SetSize(vm);
+        SettingsHelper.Settings.Gallery.FullGalleryStretchMode = stretch.ToString();
+        await SettingsHelper.SaveSettingsAsync();
+    }
+    
+    public static async Task ChangeBottomGalleryStretchSquare(MainViewModel vm)
+    {
+        vm.GetGalleryItemWidth = vm.GetGalleryItemHeight;
+        vm.GalleryBottomItemStretch = Stretch.UniformToFill;
+        vm.GalleryStretch = Stretch.UniformToFill;
+        WindowHelper.SetSize(vm);
+        SettingsHelper.Settings.Gallery.BottomGalleryStretchMode = "Square";
+        await SettingsHelper.SaveSettingsAsync();
+    }
+
+    public static async Task ChangeFullGalleryStretchSquare(MainViewModel vm)
+    {
+        vm.GetGalleryItemWidth = vm.GetGalleryItemHeight;
+        vm.GalleryFullItemStretch = Stretch.UniformToFill;
+        vm.GalleryStretch = Stretch.UniformToFill;
+        WindowHelper.SetSize(vm);
+        SettingsHelper.Settings.Gallery.FullGalleryStretchMode = "Square";
+        await SettingsHelper.SaveSettingsAsync();
+    }
+    
     #endregion
 }

+ 5 - 0
src/PicView.Avalonia/ImageTransformations/RotationAndFlip.cs

@@ -0,0 +1,5 @@
+namespace PicView.Avalonia.ImageTransformations;
+public class RotationAndFlip
+{
+    
+}

+ 5 - 0
src/PicView.Avalonia/ImageTransformations/Zoom.cs

@@ -0,0 +1,5 @@
+namespace PicView.Avalonia.ImageTransformations;
+public class Zoom
+{
+    
+}

+ 2 - 1
src/PicView.Avalonia/Navigation/NavigationHelper.cs

@@ -5,6 +5,7 @@ using Avalonia.Media.Imaging;
 using Avalonia.Threading;
 using ImageMagick;
 using PicView.Avalonia.Gallery;
+using PicView.Avalonia.Helpers;
 using PicView.Avalonia.Keybindings;
 using PicView.Avalonia.ViewModels;
 using PicView.Avalonia.Views;
@@ -122,7 +123,7 @@ public static class NavigationHelper
         }
         
         vm.CurrentView = vm.ImageViewer;
-        
+        UIHelper.CloseMenus(vm);
         var fileInfo = new FileInfo(source);
         if (!fileInfo.Exists)
         {

+ 86 - 17
src/PicView.Avalonia/ViewModels/MainViewModel.cs

@@ -34,8 +34,36 @@ namespace PicView.Avalonia.ViewModels
 
         public Stretch GalleryStretch
         {
-            get => _galleryStretch; 
-            set => this.RaiseAndSetIfChanged(ref _galleryStretch, value);
+            get
+            {
+                return GalleryFunctions.IsFullGalleryOpen ? GalleryFullItemStretch : GalleryBottomItemStretch;
+            }
+            set
+            {
+                this.RaiseAndSetIfChanged(ref _galleryStretch, value);
+                if (GalleryFunctions.IsBottomGalleryOpen && !GalleryFunctions.IsFullGalleryOpen)
+                {
+                    GalleryBottomItemStretch = value;
+                }
+                else
+                {
+                    GalleryFullItemStretch = value;
+                }
+            }
+        }
+        
+        private Stretch _galleryBottomItemStretch;
+        public Stretch GalleryBottomItemStretch
+        {
+            get => _galleryBottomItemStretch;
+            set => this.RaiseAndSetIfChanged(ref _galleryBottomItemStretch, value);
+        }
+        
+        private Stretch _galleryFullItemStretch;
+        public Stretch GalleryFullItemStretch
+        {
+            get => _galleryFullItemStretch;
+            set => this.RaiseAndSetIfChanged(ref _galleryFullItemStretch, value);
         }
         
         private int _selectedGalleryItemIndex;
@@ -80,44 +108,53 @@ namespace PicView.Avalonia.ViewModels
         {
             get
             {
-                return GetBottomGalleryItemSize + ImageSizeCalculationHelper.ScrollbarSize;
+                return GetBottomGalleryItemHeight + ImageSizeCalculationHelper.ScrollbarSize;
             }
         }
-        private double _getGalleryItemSize;
-        public double GetGalleryItemSize
+
+        private double _gatGalleryItemWidth = double.NaN;
+
+        public double GetGalleryItemWidth
+        {
+            get => _gatGalleryItemWidth;
+            set => this.RaiseAndSetIfChanged(ref _gatGalleryItemWidth, value);
+        }
+        
+        private double _getGalleryItemHeight;
+        public double GetGalleryItemHeight
         {
             get
             {
-                return GalleryFunctions.IsFullGalleryOpen ? GetExpandedGalleryItemSize : GetBottomGalleryItemSize;
+                return GalleryFunctions.IsFullGalleryOpen ? GetExpandedGalleryItemHeight : GetBottomGalleryItemHeight;
             }
             set
             {
-                this.RaiseAndSetIfChanged(ref _getGalleryItemSize, value);
+                this.RaiseAndSetIfChanged(ref _getGalleryItemHeight, value);
                 if (GalleryFunctions.IsBottomGalleryOpen && !GalleryFunctions.IsFullGalleryOpen)
                 {
-                    GetBottomGalleryItemSize = value;
+                    GetBottomGalleryItemHeight = value;
                 }
                 else
                 {
-                    GetExpandedGalleryItemSize = value;
+                    GetExpandedGalleryItemHeight = value;
                 }
             }
         }
         
-        private double _getExpandedGalleryItemSize = SettingsHelper.Settings.Gallery.ExpandedGalleryItemSize;
+        private double _getExpandedGalleryItemHeight = SettingsHelper.Settings.Gallery.ExpandedGalleryItemSize;
 
-        public double GetExpandedGalleryItemSize
+        public double GetExpandedGalleryItemHeight
         {
-            get => _getExpandedGalleryItemSize;
-            set => this.RaiseAndSetIfChanged(ref _getExpandedGalleryItemSize, value);
+            get => _getExpandedGalleryItemHeight;
+            set => this.RaiseAndSetIfChanged(ref _getExpandedGalleryItemHeight, value);
         }
         
-        private double _getBottomGalleryItemSize = SettingsHelper.Settings.Gallery.BottomGalleryItemSize;
+        private double _getBottomGalleryItemHeight = SettingsHelper.Settings.Gallery.BottomGalleryItemSize;
         
-        public double GetBottomGalleryItemSize
+        public double GetBottomGalleryItemHeight
         {
-            get => _getBottomGalleryItemSize;
-            set => this.RaiseAndSetIfChanged(ref _getBottomGalleryItemSize, value);
+            get => _getBottomGalleryItemHeight;
+            set => this.RaiseAndSetIfChanged(ref _getBottomGalleryItemHeight, value);
         }
 
         #endregion Gallery
@@ -213,6 +250,8 @@ namespace PicView.Avalonia.ViewModels
         public ReactiveCommand<string, Unit>? SetAsWallpaperCommand { get; }
         
         public ReactiveCommand<string, Unit>? SetAsLockScreenCommand { get; }
+        
+        public ReactiveCommand<string, Unit>? GalleryItemStretchCommand { get; }
 
         #endregion Commands
 
@@ -1666,6 +1705,34 @@ namespace PicView.Avalonia.ViewModels
             });
         }
 
+        private async Task GalleryItemStretchTask(string value)
+        {
+            if (value.Equals("Square", StringComparison.OrdinalIgnoreCase))
+            {
+                if (GalleryFunctions.IsFullGalleryOpen && !GalleryFunctions.IsBottomGalleryOpen)
+                {
+                    await UIHelper.ChangeFullGalleryStretchSquare(this);
+                }
+                else
+                {
+                    await UIHelper.ChangeBottomGalleryStretchSquare(this);
+                }
+                return;
+            }
+
+            if (Enum.TryParse<Stretch>(value, out var stretch))
+            {
+                if (GalleryFunctions.IsFullGalleryOpen && !GalleryFunctions.IsBottomGalleryOpen)
+                {
+                    await UIHelper.ChangeFullGalleryItemStretch(this, stretch);
+                }
+                else
+                {
+                    await UIHelper.ChangeBottomGalleryItemStretch(this, stretch);
+                }
+            }
+        }
+
         #endregion Methods
 
         public MainViewModel(IPlatformSpecificService? platformSpecificService)
@@ -1822,6 +1889,8 @@ namespace PicView.Avalonia.ViewModels
 
             ToggleBottomGalleryCommand = ReactiveCommand.CreateFromTask(FunctionsHelper.OpenCloseBottomGallery);
             CloseGalleryCommand = ReactiveCommand.CreateFromTask(FunctionsHelper.CloseGallery);
+            
+            GalleryItemStretchCommand = ReactiveCommand.CreateFromTask<string>(GalleryItemStretchTask);
 
             #endregion Gallery Commands
 

+ 9 - 0
src/PicView.Avalonia/ViewModels/ViewModelBase.cs

@@ -212,10 +212,19 @@ public class ViewModelBase : ReactiveObject
         ColorPickerToolTooltip = TranslationHelper.GetTranslation("ColorPickerToolTooltip");
         ExpandedGalleryItemSize = TranslationHelper.GetTranslation("ExpandedGalleryItemSize");
         BottomGalleryItemSize = TranslationHelper.GetTranslation("BottomGalleryItemSize");
+        Square = TranslationHelper.GetTranslation("Square");
     }
 
     #region Strings
     
+    private string? _square;
+    
+    public string? Square
+    {
+        get => _square;
+        set => this.RaiseAndSetIfChanged(ref _square, value);
+    }
+    
     private string? _bottomGalleryItemSize;
     
     public string? BottomGalleryItemSize

+ 2 - 2
src/PicView.Avalonia/Views/AppearanceView.axaml

@@ -138,7 +138,7 @@
             FontFamily="/Assets/Fonts/Roboto-Bold.ttf#Roboto"
             Foreground="{StaticResource MainTextColorFaded}"
             Margin="0,0,0,15"
-            Text="{Binding GetExpandedGalleryItemSize}" />
+            Text="{Binding GetExpandedGalleryItemHeight}" />
 
         <TextBlock
             Classes="txt"
@@ -153,7 +153,7 @@
             FontFamily="/Assets/Fonts/Roboto-Bold.ttf#Roboto"
             Foreground="{StaticResource MainTextColorFaded}"
             Margin="0,0,0,15"
-            Text="{CompiledBinding GetBottomGalleryItemSize}" />
+            Text="{CompiledBinding GetBottomGalleryItemHeight}" />
 
         <Button
             Classes="BorderStyle altHover mainBtn"

+ 11 - 1
src/PicView.Avalonia/Views/AppearanceView.axaml.cs

@@ -18,7 +18,17 @@ public partial class AppearanceView : UserControl
         InitializeComponent();
         if (!RuntimeInformation.IsOSPlatform(OSPlatform.Windows))
         {
-            TaskBarToggleButton.IsVisible = false;
+            if (Dispatcher.UIThread.CheckAccess())
+            {
+                TaskBarToggleButton.IsVisible = false;
+            }
+            else
+            {
+                Dispatcher.UIThread.InvokeAsync(() =>
+                {
+                    TaskBarToggleButton.IsVisible = false;
+                });
+            }
         }
         Loaded += AppearanceView_Loaded;
     }

+ 27 - 6
src/PicView.Avalonia/Views/GalleryView.axaml

@@ -1,6 +1,6 @@
-<customControls:ImageGallery
+<customControls:GalleryAnimationControl
     mc:Ignorable="d"
-    x:Class="PicView.Avalonia.Views.GalleryView"
+    x:Class="PicView.Avalonia.Views.GalleryAnimationControlView"
     x:DataType="viewModels:MainViewModel"
     xmlns="https://github.com/avaloniaui"
     xmlns:customControls="clr-namespace:PicView.Avalonia.CustomControls"
@@ -15,6 +15,28 @@
     <Panel Background="{StaticResource BackgroundAlpha}">
         <Panel.ContextMenu>
             <ContextMenu>
+                <MenuItem Header="{CompiledBinding Stretch}">
+                    <MenuItem.Icon>
+                        <Path
+                            Data="{StaticResource ImageGeometry}"
+                            Fill="{StaticResource MainIconColor}"
+                            Height="12"
+                            Stretch="Fill"
+                            Width="12" />
+                    </MenuItem.Icon>
+                    <MenuItem
+                        Command="{CompiledBinding GalleryItemStretchCommand}"
+                        CommandParameter="Uniform"
+                        Header="Uniform" />
+                    <MenuItem
+                        Command="{CompiledBinding GalleryItemStretchCommand}"
+                        CommandParameter="UniformToFill"
+                        Header="UniformToFill" />
+                    <MenuItem
+                        Command="{CompiledBinding GalleryItemStretchCommand}"
+                        CommandParameter="Square"
+                        Header="{CompiledBinding Square}" />
+                </MenuItem>
                 <MenuItem
                     Header="{CompiledBinding ExpandedGalleryItemSize}"
                     PointerPressed="Flyout_OnPointerPressed"
@@ -68,14 +90,13 @@
                 Width="23" />
         </Button>
 
-        <ListBox
+        <customControls:GalleryListBox
             AutoScrollToSelectedItem="True"
             Background="Transparent"
             BorderBrush="{StaticResource TertiaryBorderColor}"
             BorderThickness="0,1,0,0"
             Focusable="False"
             Padding="0"
-            PointerWheelChanged="GalleryListBox_OnPointerWheelChanged"
             ScrollViewer.IsScrollInertiaEnabled="True"
             ScrollViewer.VerticalScrollBarVisibility="Disabled"
             SelectedIndex="{CompiledBinding SelectedGalleryItemIndex}"
@@ -88,6 +109,6 @@
                         VerticalAlignment="Bottom" />
                 </ItemsPanelTemplate>
             </ListBox.ItemsPanel>
-        </ListBox>
+        </customControls:GalleryListBox>
     </Panel>
-</customControls:ImageGallery>
+</customControls:GalleryAnimationControl>

+ 2 - 45
src/PicView.Avalonia/Views/GalleryView.axaml.cs

@@ -1,19 +1,15 @@
 using Avalonia.Controls;
 using Avalonia.Input;
 using Avalonia.Interactivity;
-using Avalonia.VisualTree;
-using PicView.Core.Config;
-using System.Runtime.InteropServices;
-using Avalonia;
 using Avalonia.Controls.Primitives;
 using PicView.Avalonia.CustomControls;
 using PicView.Avalonia.Keybindings;
 
 namespace PicView.Avalonia.Views;
 
-public partial class GalleryView : ImageGallery
+public partial class GalleryAnimationControlView : GalleryAnimationControl
 {
-    public GalleryView()
+    public GalleryAnimationControlView()
     {
         InitializeComponent();
         Loaded += (_, _) =>
@@ -49,45 +45,6 @@ public partial class GalleryView : ImageGallery
         e.Handled = true;
     }
 
-    private void GalleryListBox_OnPointerWheelChanged(object? sender, PointerWheelEventArgs e)
-    {
-        if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
-        {
-            // macOS already has horizontal scrolling for touchpad
-            return;
-        }
-        var scrollViewer = GalleryListBox.FindDescendantOfType<ScrollViewer>();
-        if (scrollViewer is null)
-        {
-            return;
-        }
-
-        const int speed = 34;
-
-        if (e.Delta.Y > 0)
-        {
-            if (SettingsHelper.Settings.Zoom.HorizontalReverseScroll)
-            {
-                scrollViewer.Offset -= new Vector(speed, 0);
-            }
-            else
-            {
-                scrollViewer.Offset -= new Vector(-speed, 0);
-            }
-        }
-        else
-        {
-            if (SettingsHelper.Settings.Zoom.HorizontalReverseScroll)
-            {
-                scrollViewer.Offset -= new Vector(-speed, 0);
-            }
-            else
-            {
-                scrollViewer.Offset -= new Vector(speed, 0);
-            }
-        }
-    }
-
     private void Flyout_OnPointerPressed(object? sender, PointerPressedEventArgs e)
     {
         if (sender is Control ctl)

+ 1 - 1
src/PicView.Avalonia/Views/MainView.axaml

@@ -465,7 +465,7 @@
     </UserControl.ContextMenu>
     <Panel x:Name="MainGrid">
         <ContentControl Content="{CompiledBinding CurrentView}" Margin="{CompiledBinding ImageMargin}" />
-        <views:GalleryView
+        <views:GalleryAnimationControlView
             GalleryMode="{CompiledBinding GalleryMode}"
             Height="0"
             IsVisible="False"

+ 1 - 1
src/PicView.Avalonia/Views/UC/BottomGalleryItemSizeSlider.axaml

@@ -18,7 +18,7 @@
         Minimum="36"
         TickFrequency="1"
         TickPlacement="BottomRight"
-        Value="{CompiledBinding GetBottomGalleryItemSize}"
+        Value="{CompiledBinding GetBottomGalleryItemHeight}"
         ValueChanged="BottomGallery_OnValueChanged"
         Width="270" />
 </UserControl>

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

@@ -23,14 +23,14 @@ public partial class BottomGalleryItemSizeSlider : UserControl
             return;
         }
         // ReSharper disable once CompareOfFloatsByEqualityOperator
-        if (vm.GetBottomGalleryItemSize == e.NewValue)
+        if (vm.GetBottomGalleryItemHeight == e.NewValue)
         {
             return;
         }
         SettingsHelper.Settings.Gallery.BottomGalleryItemSize = e.NewValue;
         if (GalleryFunctions.IsBottomGalleryOpen && !GalleryFunctions.IsFullGalleryOpen)
         {
-            vm.GetGalleryItemSize = e.NewValue;
+            vm.GetGalleryItemHeight = e.NewValue;
             WindowHelper.SetSize(vm);
             var mainView = desktop.MainWindow.GetControl<MainView>("MainView");
             var gallery = mainView.GalleryView;

+ 1 - 1
src/PicView.Avalonia/Views/UC/FullGalleryItemSizeSlider.axaml

@@ -18,7 +18,7 @@
         Minimum="36"
         TickFrequency="1"
         TickPlacement="BottomRight"
-        Value="{CompiledBinding GetExpandedGalleryItemSize}"
+        Value="{CompiledBinding GetExpandedGalleryItemHeight}"
         ValueChanged="FullGallery_OnValueChanged"
         Width="270" />
 </UserControl>

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

@@ -21,14 +21,14 @@ public partial class FullGalleryItemSizeSlider : UserControl
         }
 
         // ReSharper disable once CompareOfFloatsByEqualityOperator
-        if (vm.GetExpandedGalleryItemSize == e.NewValue)
+        if (vm.GetExpandedGalleryItemHeight == e.NewValue)
         {
             return;
         }
         WindowHelper.SetSize(vm);
         if (GalleryFunctions.IsFullGalleryOpen)
         {
-            vm.GetGalleryItemSize = vm.GetExpandedGalleryItemSize;
+            vm.GetGalleryItemHeight = vm.GetExpandedGalleryItemHeight;
         }
         // Binding to height depends on timing of the update. Maybe find a cleaner mvvm solution one day
 

+ 3 - 2
src/PicView.Avalonia/Views/UC/GalleryItem.axaml

@@ -11,13 +11,14 @@
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
     <Border
         Background="Transparent"
-        BorderThickness="1"
+        BorderThickness="3"
         CornerRadius="8"
-        Height="{CompiledBinding GetGalleryItemSize}"
+        Height="{CompiledBinding GetGalleryItemHeight}"
         MinWidth="25"
         ToolTip.HorizontalOffset="0"
         ToolTip.Placement="TopEdgeAlignedLeft"
         ToolTip.VerticalOffset="0"
+        Width="{CompiledBinding GetGalleryItemWidth}"
         x:DataType="viewModels:MainViewModel"
         x:Name="ImageBorder">
         <Image Stretch="{CompiledBinding GalleryStretch}" x:Name="GalleryImage" />

+ 1 - 1
src/PicView.Avalonia/Views/UC/GalleryItemSizeSlider.axaml

@@ -18,7 +18,7 @@
         Minimum="36"
         TickFrequency="1"
         TickPlacement="BottomRight"
-        Value="{CompiledBinding GetGalleryItemSize}"
+        Value="{CompiledBinding GetGalleryItemHeight}"
         ValueChanged="RangeBase_OnValueChanged"
         Width="270" />
 </UserControl>

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

@@ -28,12 +28,12 @@ public partial class GalleryItemSizeSlider : UserControl
         {
             // Change the sizes of the bottom gallery items
             // ReSharper disable once CompareOfFloatsByEqualityOperator
-            if (vm.GetBottomGalleryItemSize == e.NewValue)
+            if (vm.GetBottomGalleryItemHeight == e.NewValue)
             {
                 return;
             }
             SettingsHelper.Settings.Gallery.BottomGalleryItemSize = e.NewValue;
-            vm.GetGalleryItemSize = e.NewValue;
+            vm.GetGalleryItemHeight = e.NewValue;
             WindowHelper.SetSize(vm);
             var mainView = desktop.MainWindow.GetControl<MainView>("MainView");
             var gallery = mainView.GalleryView;
@@ -42,15 +42,15 @@ public partial class GalleryItemSizeSlider : UserControl
         else
         {
             // ReSharper disable once CompareOfFloatsByEqualityOperator
-            if (vm.GetExpandedGalleryItemSize == e.NewValue)
+            if (vm.GetExpandedGalleryItemHeight == e.NewValue)
             {
                 return;
             }
-            vm.GetExpandedGalleryItemSize = e.NewValue;
+            vm.GetExpandedGalleryItemHeight = e.NewValue;
             if (GalleryFunctions.IsFullGalleryOpen)
             {
                 WindowHelper.SetSize(vm);
-                vm.GetGalleryItemSize = e.NewValue;
+                vm.GetGalleryItemHeight = e.NewValue;
             }
             SettingsHelper.Settings.Gallery.ExpandedGalleryItemSize = e.NewValue;
         }

+ 2 - 0
src/PicView.Core/Config/AppSettings.cs

@@ -54,6 +54,8 @@ public class Gallery
     public bool ShowBottomGalleryInHiddenUI { get; set; } = false;
     public double BottomGalleryItemSize { get; set; } = 37;
     public double ExpandedGalleryItemSize { get; set; } = 23;
+    public string FullGalleryStretchMode { get; set; } = "Uniform";
+    public string BottomGalleryStretchMode { get; set; } = "Uniform";
 }
 
 public class ImageScaling