Browse Source

Refactor rotation commands and remove unused code

Ruben 3 months ago
parent
commit
958da0502f

+ 3 - 2
src/PicView.Avalonia.MacOS/Views/MacOSTitlebar.axaml

@@ -46,7 +46,8 @@
                 ToggleType="Radio"
                 x:Name="Rotation270Item" />
             <Separator />
-            <MenuItem Command="{CompiledBinding Tools.FlipCommand}" Header="{CompiledBinding Translation.IsFlipped.Value}">
+            <MenuItem Command="{CompiledBinding Tools.FlipCommand}"
+                      Header="{CompiledBinding Translation.IsFlipped.Value}">
                 <MenuItem.Icon>
                     <Path
                         Data="{StaticResource FlipGeometry}"
@@ -99,7 +100,7 @@
             BorderBrush="{DynamicResource MainBorderColor}"
             BorderThickness="0"
             Classes="hover"
-            Command="{CompiledBinding Tools.RotateRightWindowBorderButtonCommand}"
+            Command="{CompiledBinding Tools.RotateRightCommand}"
             DockPanel.Dock="Right"
             Foreground="{DynamicResource MainTextColor}"
             Icon="{StaticResource RefreshCcwDot}"

+ 2 - 3
src/PicView.Avalonia.Win32/Views/WinTitleBar.axaml

@@ -100,7 +100,7 @@
             <customControls:IconButton
                 Background="{DynamicResource WindowButtonBackgroundColor}"
                 Classes="hover"
-                Command="{CompiledBinding Tools.RotateRightWindowBorderButtonCommand}"
+                Command="{CompiledBinding Tools.RotateRightCommand}"
                 DockPanel.Dock="Left"
                 Foreground="{DynamicResource MainTextColor}"
                 Icon="{StaticResource RefreshCcwDot}"
@@ -110,11 +110,10 @@
                                             Converter={x:Static ObjectConverters.IsNotNull}}"
                 IsRepeatEnabled="False"
                 Margin="0"
-                Name="RotateRightButton"
                 ToolTip.Tip="{CompiledBinding Translation.RotateRight.Value,
                                               Mode=OneWay}"
                 Width="30"
-                x:Name="RotateLeftButton" />
+                x:Name="RotateRightButton" />
 
             <customControls:IconButton
                 Background="{DynamicResource WindowButtonBackgroundColor}"

+ 32 - 37
src/PicView.Avalonia.Win32/Views/WinTitleBar.axaml.cs

@@ -22,38 +22,39 @@ public partial class WinTitleBar : UserControl
             {
                 TopWindowBorder.Background = Brushes.Transparent;
                 TopWindowBorder.BorderThickness = new Thickness(0);
-            
+
                 LogoBorder.Background = Brushes.Transparent;
                 LogoBorder.BorderThickness = new Thickness(0);
-            
+
                 LogoBorder.Background = Brushes.Transparent;
                 LogoBorder.BorderThickness = new Thickness(0);
-            
+
                 EditableTitlebar.Background = Brushes.Transparent;
                 EditableTitlebar.BorderThickness = new Thickness(0);
-                
+
                 CloseButton.Background = Brushes.Transparent;
                 CloseButton.BorderThickness = new Thickness(0);
-                
+
                 MinimizeButton.Background = Brushes.Transparent;
                 MinimizeButton.BorderThickness = new Thickness(0);
-                
+
                 RestoreButton.Background = Brushes.Transparent;
                 RestoreButton.BorderThickness = new Thickness(0);
-                
+
                 FullscreenButton.Background = Brushes.Transparent;
                 FullscreenButton.BorderThickness = new Thickness(0);
-                
+
                 FlipButton.Background = Brushes.Transparent;
                 FlipButton.BorderThickness = new Thickness(0);
-                
+
                 GalleryButton.Background = Brushes.Transparent;
                 GalleryButton.BorderThickness = new Thickness(0);
-                
+
                 RotateRightButton.Background = Brushes.Transparent;
                 RotateRightButton.BorderThickness = new Thickness(0);
-                
-                if (!Application.Current.TryGetResource("SecondaryTextColor", Application.Current.RequestedThemeVariant, out var color))
+
+                if (!Application.Current.TryGetResource("SecondaryTextColor", Application.Current.RequestedThemeVariant,
+                        out var color))
                 {
                     return;
                 }
@@ -73,21 +74,19 @@ public partial class WinTitleBar : UserControl
                     GalleryButton.Foreground = new SolidColorBrush(secondaryTextColor);
                     RotateRightButton.Foreground = new SolidColorBrush(secondaryTextColor);
                 }
-                #if DEBUG
+#if DEBUG
                 catch (Exception e)
                 {
                     Console.WriteLine(e);
                     throw;
                 }
-                #else
+#else
                 catch (Exception) { }
-                #endif
+#endif
             }
+
             PointerPressed += (_, e) => MoveWindow(e);
-            PointerExited += (_, _) =>
-            {
-                DragAndDropHelper.RemoveDragDropView();
-            };
+            PointerExited += (_, _) => { DragAndDropHelper.RemoveDragDropView(); };
 
             if (DataContext is not MainViewModel vm)
             {
@@ -95,32 +94,24 @@ public partial class WinTitleBar : UserControl
             }
 
             Observable.EveryValueChanged(this, x => x.RotationContextMenu.IsOpen, UIHelper.GetFrameProvider)
-                .Subscribe(_ =>
-                {
-                    UpdateRotation();
-                });
+                .Subscribe(_ => { UpdateRotation(); });
 
-            RotateLeftButton.PointerPressed += (_, e) =>
-            {
-                OpenContextMenu(e);
-            };
-            FlipButton.PointerPressed += (_, e) =>
-            {
-                OpenContextMenu(e);
-            };
-            
+            RotateRightButton.PointerPressed += (_, e) => { OpenContextMenu(e); };
+            RotateRightButton.Click += (_, e) => { vm.MainWindow.IsTopToolbarRotationClicked = true; };
+            FlipButton.PointerPressed += (_, e) => { OpenContextMenu(e); };
         };
     }
 
-    private void OpenContextMenu(PointerPressedEventArgs e)
+    private bool OpenContextMenu(PointerPressedEventArgs e)
     {
         if (!e.GetCurrentPoint(this).Properties.IsRightButtonPressed)
         {
-            return;
+            return false;
         }
 
         // Context menu doesn't want to be opened normally
         RotationContextMenu.Open();
+        return true;
     }
 
     private void UpdateRotation()
@@ -129,6 +120,7 @@ public partial class WinTitleBar : UserControl
         {
             return;
         }
+
         Rotation0Item.IsChecked = false;
         Rotation90Item.IsChecked = false;
         Rotation180Item.IsChecked = false;
@@ -147,18 +139,21 @@ public partial class WinTitleBar : UserControl
             case 270:
                 Rotation270Item.IsChecked = true;
                 break;
-                    
         }
     }
 
     private void MoveWindow(PointerPressedEventArgs e)
     {
-        if (VisualRoot is null || DataContext is not MainViewModel vm) { return; }
-        
+        if (VisualRoot is null || DataContext is not MainViewModel vm)
+        {
+            return;
+        }
+
         if (vm.MainWindow.IsEditableTitlebarOpen.Value)
         {
             return;
         }
+
         WindowFunctions.WindowDragAndDoubleClickBehavior((Window)VisualRoot, e, vm.PlatformWindowService);
     }
 }

+ 0 - 8
src/PicView.Avalonia/ImageTransformations/Rotation/RotationButton.cs

@@ -1,8 +0,0 @@
-namespace PicView.Avalonia.ImageTransformations.Rotation;
-
-public enum RotationButton
-{
-    WindowBorderButton,
-    RotateRightButton,
-    RotateLeftButton
-}

+ 3 - 77
src/PicView.Avalonia/ImageTransformations/Rotation/RotationNavigation.cs

@@ -1,12 +1,7 @@
-using Avalonia;
-using Avalonia.Controls;
-using Avalonia.Threading;
+using Avalonia.Threading;
 using PicView.Avalonia.Gallery;
-using PicView.Avalonia.UI;
 using PicView.Avalonia.ViewModels;
-using PicView.Avalonia.Views.UC.Menus;
 using PicView.Avalonia.WindowBehavior;
-using PicView.Core.DebugTools;
 using PicView.Core.Gallery;
 
 namespace PicView.Avalonia.ImageTransformations.Rotation;
@@ -28,69 +23,13 @@ public static class RotationNavigation
         await Dispatcher.UIThread.InvokeAsync(() => { vm.ImageViewer.Rotate(false); });
     }
 
-    public static async Task RotateRight(MainViewModel? vm, RotationButton rotationButton)
-    {
-        await RotateRight(vm);
-
-        // Check if it should move the cursor
-        if (!Settings.WindowProperties.AutoFit)
-        {
-            return;
-        }
-
-        await MoveCursorAfterRotation(vm, rotationButton);
-    }
-    
     public static async Task RotateTo(MainViewModel? vm, int angle)
     {
-        await Dispatcher.UIThread.InvokeAsync(() =>
-        {
-            vm.ImageViewer.Rotate(angle);
-        });
+        await Dispatcher.UIThread.InvokeAsync(() => { vm.ImageViewer.Rotate(angle); });
         vm.GlobalSettings.RotationAngle.Value = angle;
         await WindowResizing.SetSizeAsync(vm);
     }
 
-    private static async Task MoveCursorAfterRotation(MainViewModel? vm, RotationButton rotationButton)
-    {
-        // Move cursor when button is clicked
-        await Dispatcher.UIThread.InvokeAsync(() =>
-        {
-            try
-            {
-                Button? button;
-                ImageMenu? menu;
-                switch (rotationButton)
-                {
-                    case RotationButton.WindowBorderButton:
-                        button = UIHelper.GetTitlebar.GetControl<Button>("RotateRightButton");
-                        break;
-                    case RotationButton.RotateRightButton:
-                        menu = UIHelper.GetMainView.MainGrid.Children.OfType<ImageMenu>().FirstOrDefault();
-                        button = menu?.GetControl<Button>("RotateRightButton");
-                        break;
-                    case RotationButton.RotateLeftButton:
-                        menu = UIHelper.GetMainView.MainGrid.Children.OfType<ImageMenu>().FirstOrDefault();
-                        button = menu?.GetControl<Button>("RotateLeftButton");
-                        break;
-                    default:
-                        return;
-                }
-
-                if (button is null || !button.IsPointerOver)
-                {
-                    return;
-                }
-
-                var p = button.PointToScreen(new Point(10, 15));
-                vm.PlatformService?.SetCursorPos(p.X, p.Y);
-            }
-            catch (Exception e)
-            {
-                DebugHelper.LogDebug(nameof(Rotation), nameof(MoveCursorAfterRotation), e);
-            }
-        });
-    }
 
     public static async Task RotateLeft(MainViewModel? vm)
     {
@@ -107,23 +46,10 @@ public static class RotationNavigation
         await Dispatcher.UIThread.InvokeAsync(() => { vm.ImageViewer.Rotate(true); });
     }
 
-    public static async Task RotateLeft(MainViewModel vm, RotationButton rotationButton)
-    {
-        await RotateLeft(vm);
-
-        // Check if it should move the cursor
-        if (!Settings.WindowProperties.AutoFit)
-        {
-            return;
-        }
-
-        await MoveCursorAfterRotation(vm, rotationButton);
-    }
-
     public static void Flip(MainViewModel vm)
     {
         Dispatcher.UIThread.Invoke(() => { vm.ImageViewer.Flip(true); });
-        
+
         if (vm.PicViewer.ScaleX.CurrentValue == 1)
         {
             vm.PicViewer.ScaleX.Value = -1;

+ 6 - 5
src/PicView.Avalonia/UI/MenuManager.cs

@@ -29,13 +29,14 @@ public static class MenuManager
     {
         return new T
         {
+            Name = typeof(T).Name,
             VerticalAlignment = VerticalAlignment.Bottom,
             HorizontalAlignment = HorizontalAlignment.Center,
             Margin = margin,
             IsVisible = false
         };
     }
-    
+
     /// <summary>
     /// Closes all menus
     /// </summary>
@@ -87,10 +88,10 @@ public static class MenuManager
 
         // Get the current state of the menu being toggled
         var currentState = GetMenuState(vm, menuType);
-        
+
         // Close all menus
         CloseMenus(vm);
-        
+
         // Only open the menu if it wasn't already open (toggle behavior)
         if (!currentState)
         {
@@ -98,7 +99,7 @@ public static class MenuManager
         }
         // If it was already open, it remains closed after CloseMenus()
     }
-    
+
     private static bool GetMenuState(MainViewModel vm, MenuType menuType)
     {
         return menuType switch
@@ -110,7 +111,7 @@ public static class MenuManager
             _ => false
         };
     }
-    
+
     private static void SetMenuState(MainViewModel vm, MenuType menuType, bool state)
     {
         switch (menuType)

+ 63 - 58
src/PicView.Avalonia/ViewModels/MainWindowViewModel.cs

@@ -11,11 +11,16 @@ namespace PicView.Avalonia.ViewModels;
 
 public class MainWindowViewModel : IDisposable
 {
-    public bool IsNavigationButtonLeftClicked { get; set;  }
-    public bool IsNavigationButtonRightClicked { get; set;  }
-    public bool IsClickArrowLeftClicked { get; set;  }
-    public bool IsClickArrowRightClicked { get; set;  }
-    
+    public bool IsNavigationButtonLeftClicked { get; set; }
+    public bool IsNavigationButtonRightClicked { get; set; }
+    public bool IsClickArrowLeftClicked { get; set; }
+    public bool IsClickArrowRightClicked { get; set; }
+
+    public bool IsRotateLeftClicked { get; set; }
+    public bool IsRotateRightClicked { get; set; }
+
+    public bool IsTopToolbarRotationClicked { get; set; }
+
     public BindableReactiveProperty<Brush?> ImageBackground { get; } = new();
 
     public BindableReactiveProperty<Brush?> ConstrainedImageBackground { get; } = new();
@@ -35,9 +40,9 @@ public class MainWindowViewModel : IDisposable
     public BindableReactiveProperty<double> TitlebarHeight { get; } = new();
 
     public BindableReactiveProperty<double> BottombarHeight { get; } = new();
-    
+
     public BindableReactiveProperty<SizeToContent> SizeToContent { get; } = new();
-    
+
     public BindableReactiveProperty<ScrollBarVisibility> ToggleScrollBarVisibility { get; } = new();
 
     public BindableReactiveProperty<bool> CanResize { get; } = new();
@@ -51,9 +56,9 @@ public class MainWindowViewModel : IDisposable
     public BindableReactiveProperty<bool> IsSettingsMenuVisible { get; } = new();
 
     public BindableReactiveProperty<bool> IsToolsMenuVisible { get; } = new();
-    
+
     public BindableReactiveProperty<double> TitleMaxWidth { get; } = new();
-    
+
     public BindableReactiveProperty<bool> IsFullscreen { get; } = new();
 
     public BindableReactiveProperty<bool> IsMaximized { get; } = new();
@@ -70,64 +75,21 @@ public class MainWindowViewModel : IDisposable
     public BindableReactiveProperty<bool> IsBottomToolbarShown { get; } = new();
 
     public BindableReactiveProperty<bool> IsEditableTitlebarOpen { get; } = new();
-    
+
     public BindableReactiveProperty<IImage?> ChangeCtrlZoomImage { get; } = new();
 
-    public void LayoutButtonSubscription()
-    {
-        Observable.EveryValueChanged(this, x => x.IsMaximized.CurrentValue, UIHelper.GetFrameProvider)
-            .Subscribe(_ => SetButtonValues());
-        Observable.EveryValueChanged(this, x => x.IsFullscreen.CurrentValue, UIHelper.GetFrameProvider)
-            .Subscribe(_ => SetButtonValues());
-    }
-    
-    #region Menus
-    
-    public ReactiveCommand CloseMenuCommand { get; } = new(CloseMenus);
-    
-    public ReactiveCommand ToggleFileMenuCommand { get; } = new(ToggleFileMenu);
-    public ReactiveCommand ToggleImageMenuCommand { get; } = new(ToggleImageMenu);
-    public ReactiveCommand ToggleSettingsMenuCommand { get; } = new(ToggleSettingsMenu);
-    public ReactiveCommand ToggleToolsMenuCommand { get; } = new(ToggleToolsMenu);
+    public ReactiveCommand ExitCommand { get; } = new(Close);
 
-    private static void CloseMenus(Unit unit) => MenuManager.CloseMenus(UIHelper.GetMainView.DataContext as MainViewModel);
-    
-    private static void ToggleFileMenu(Unit unit) => MenuManager.ToggleFileMenu(UIHelper.GetMainView.DataContext as MainViewModel);
-    private static void ToggleImageMenu(Unit unit) => MenuManager.ToggleImageMenu(UIHelper.GetMainView.DataContext as MainViewModel);
-    private static void ToggleSettingsMenu(Unit unit) => MenuManager.ToggleSettingsMenu(UIHelper.GetMainView.DataContext as MainViewModel);
-    private static void ToggleToolsMenu(Unit unit) => MenuManager.ToggleToolsMenu(UIHelper.GetMainView.DataContext as MainViewModel);
+    public ReactiveCommand MaximizeCommand { get; } = new(async (_, _) => { await FunctionsMapper.Maximize(); });
 
-    #endregion Menus
+    public ReactiveCommand MinimizeCommand { get; } = new(async (_, _) => { await WindowFunctions.Minimize(); });
+
+    public ReactiveCommand RestoreCommand { get; } = new(async (_, _) => { await FunctionsMapper.Restore(); });
 
-    public ReactiveCommand ExitCommand { get; } = new(Close);
-    private static void Close(Unit unit) => DialogManager.Close();
-    
-    public ReactiveCommand MaximizeCommand { get; } = new(async (_, _) =>
-    {
-        await FunctionsMapper.Maximize();
-    });
-    
-    public ReactiveCommand MinimizeCommand { get; } = new(async (_, _) =>
-    {
-        await WindowFunctions.Minimize();
-    });
-    
-    public ReactiveCommand RestoreCommand { get; } = new(async (_, _) =>
-    {
-        await FunctionsMapper.Restore();
-    });
-    
     public ReactiveCommand ToggleFullscreenCommand { get; } = new(async (_, _) =>
     {
         await FunctionsMapper.ToggleFullscreen();
     });
-    
-
-    private void SetButtonValues()
-    {
-        ShouldRestore.Value = IsFullscreen.CurrentValue || IsMaximized.CurrentValue;
-        ShouldMaximizeBeShown.Value = !IsFullscreen.CurrentValue && !IsMaximized.CurrentValue;
-    }
 
     public void Dispose()
     {
@@ -151,4 +113,47 @@ public class MainWindowViewModel : IDisposable
             IsBottomToolbarShown,
             IsEditableTitlebarOpen);
     }
+
+    public void LayoutButtonSubscription()
+    {
+        Observable.EveryValueChanged(this, x => x.IsMaximized.CurrentValue, UIHelper.GetFrameProvider)
+            .Subscribe(_ => SetButtonValues());
+        Observable.EveryValueChanged(this, x => x.IsFullscreen.CurrentValue, UIHelper.GetFrameProvider)
+            .Subscribe(_ => SetButtonValues());
+    }
+
+    private static void Close(Unit unit) => DialogManager.Close();
+
+
+    private void SetButtonValues()
+    {
+        ShouldRestore.Value = IsFullscreen.CurrentValue || IsMaximized.CurrentValue;
+        ShouldMaximizeBeShown.Value = !IsFullscreen.CurrentValue && !IsMaximized.CurrentValue;
+    }
+
+    #region Menus
+
+    public ReactiveCommand CloseMenuCommand { get; } = new(CloseMenus);
+
+    public ReactiveCommand ToggleFileMenuCommand { get; } = new(ToggleFileMenu);
+    public ReactiveCommand ToggleImageMenuCommand { get; } = new(ToggleImageMenu);
+    public ReactiveCommand ToggleSettingsMenuCommand { get; } = new(ToggleSettingsMenu);
+    public ReactiveCommand ToggleToolsMenuCommand { get; } = new(ToggleToolsMenu);
+
+    private static void CloseMenus(Unit unit) =>
+        MenuManager.CloseMenus(UIHelper.GetMainView.DataContext as MainViewModel);
+
+    private static void ToggleFileMenu(Unit unit) =>
+        MenuManager.ToggleFileMenu(UIHelper.GetMainView.DataContext as MainViewModel);
+
+    private static void ToggleImageMenu(Unit unit) =>
+        MenuManager.ToggleImageMenu(UIHelper.GetMainView.DataContext as MainViewModel);
+
+    private static void ToggleSettingsMenu(Unit unit) =>
+        MenuManager.ToggleSettingsMenu(UIHelper.GetMainView.DataContext as MainViewModel);
+
+    private static void ToggleToolsMenu(Unit unit) =>
+        MenuManager.ToggleToolsMenu(UIHelper.GetMainView.DataContext as MainViewModel);
+
+    #endregion Menus
 }

+ 0 - 18
src/PicView.Avalonia/ViewModels/ToolsViewModel.cs

@@ -209,26 +209,8 @@ public class ToolsViewModel : IDisposable
     // Image related
     public ReactiveCommand RotateLeftCommand { get; } = new(async (_, _) => { await FunctionsMapper.RotateLeft(); });
 
-    public ReactiveCommand RotateLeftButtonCommand { get; } = new(async (_, _) =>
-    {
-        await RotationNavigation.RotateLeft(UIHelper.GetMainView.DataContext as MainViewModel,
-            RotationButton.RotateLeftButton);
-    });
-
     public ReactiveCommand RotateRightCommand { get; } = new(async (_, _) => { await FunctionsMapper.RotateRight(); });
 
-    public ReactiveCommand RotateRightButtonCommand { get; } = new(async (_, _) =>
-    {
-        await RotationNavigation.RotateRight(UIHelper.GetMainView.DataContext as MainViewModel,
-            RotationButton.RotateLeftButton);
-    });
-
-    public ReactiveCommand RotateRightWindowBorderButtonCommand { get; } = new(async (_, _) =>
-    {
-        await RotationNavigation.RotateRight(UIHelper.GetMainView.DataContext as MainViewModel,
-            RotationButton.WindowBorderButton);
-    });
-
     public ReactiveCommand FlipCommand { get; } = new(async (_, _) => { await FunctionsMapper.Flip(); });
 
     public ReactiveCommand StretchCommand { get; } = new(async (_, _) => { await FunctionsMapper.Stretch(); });

+ 1 - 17
src/PicView.Avalonia/Views/ImageInfoView.axaml.cs

@@ -9,7 +9,6 @@ using PicView.Avalonia.Resizing;
 using PicView.Avalonia.UI;
 using PicView.Avalonia.ViewModels;
 using PicView.Core.Extensions;
-using PicView.Core.FileHandling;
 using PicView.Core.Titles;
 using R3;
 
@@ -72,7 +71,7 @@ public partial class ImageInfoView : UserControl
             Observable.EveryValueChanged(vm.PicViewer.FileInfo, x => x.Value, UIHelper.GetFrameProvider)
                 .Subscribe(UpdateValues).AddTo(_disposables);
 
-            vm.Exif.RemoveImageDataCommand.SubscribeAwait(RemoveValues);
+            vm.Exif.RemoveImageDataCommand.Delay(TimeSpan.FromSeconds(2)).Subscribe(UpdateValues);
 
             ResetButton.Click += (_, _) =>
             {
@@ -155,21 +154,6 @@ public partial class ImageInfoView : UserControl
         };
     }
 
-    private async ValueTask RemoveValues(FileInfo arg1, CancellationToken arg2)
-    {
-        if (DataContext is not MainViewModel vm)
-        {
-            return;
-        }
-        
-        while (FileHelper.IsFileInUse(vm.PicViewer.FileInfo.CurrentValue.FullName))
-        {
-            await Task.Delay(100, arg2).ConfigureAwait(false);
-        }
-        
-        UpdateValues(arg1);
-    }
-
     private void UpdateValues(FileInfo? fileInfo)
     {
         if (DataContext is not MainViewModel vm)

+ 10 - 6
src/PicView.Avalonia/Views/UC/Menus/ImageMenu.axaml

@@ -49,7 +49,7 @@
                     Canvas.Left="-1"
                     Canvas.Top="-1"
                     Classes="hover"
-                    Command="{CompiledBinding Tools.RotateLeftButtonCommand}"
+                    Command="{CompiledBinding Tools.RotateLeftCommand}"
                     CornerRadius="8,0,0,0"
                     Data="{StaticResource RotateLeftGeometry}"
                     Foreground="{DynamicResource MainTextColor}"
@@ -68,7 +68,7 @@
                     Canvas.Left="44"
                     Canvas.Top="-1"
                     Classes="hover"
-                    Command="{CompiledBinding Tools.RotateRightButtonCommand}"
+                    Command="{CompiledBinding Tools.RotateRightCommand}"
                     Data="{StaticResource RotateRightGeometry}"
                     Foreground="{DynamicResource MainTextColor}"
                     Height="47"
@@ -116,7 +116,8 @@
                     <Image Height="17" Width="17">
                         <DrawingImage>
                             <DrawingGroup>
-                                <GeometryDrawing Brush="{DynamicResource MainTextColor}" Geometry="F1 M142.9 142.9C205.1 80.7 305.6 80.4 368.2 141.9L327 183C320.1 189.9 318.1 200.2 321.8 209.2C325.5 218.2 334.3 224 344 224L463.5 224C463.5 224 463.5 224 463.5 224L472 224C485.3 224 496 213.3 496 200L496 72C496 62.3 490.2 53.5 481.2 49.8C472.2 46.1 461.9 48.1 455 55L413.4 96.6C325.8 10.1 184.7 10.4 97.6 97.6C73.2 122 55.6 150.7 44.8 181.4C38.9 198.1 47.7 216.3 64.3 222.2C80.9 228.1 99.2 219.3 105.1 202.7C112.8 180.9 125.3 160.4 142.9 142.9L142.9 142.9ZM16 312L16 319.6L16 320.3L16 440C16 449.7 21.8 458.5 30.8 462.2C39.8 465.9 50.1 463.9 57 457L98.6 415.4C186.2 501.9 327.3 501.6 414.4 414.4C438.8 390 456.5 361.3 467.3 330.7C473.2 314 464.4 295.8 447.8 289.9C431.2 284 412.9 292.8 407 309.4C399.3 331.2 386.8 351.7 369.2 369.2C307 431.4 206.5 431.7 143.9 370.2L185 329C191.9 322.1 193.9 311.8 190.2 302.8C186.5 293.8 177.7 288 168 288L48.4 288L47.7 288L40 288C26.7 288 16 298.7 16 312Z" />
+                                <GeometryDrawing Brush="{DynamicResource MainTextColor}"
+                                                 Geometry="F1 M142.9 142.9C205.1 80.7 305.6 80.4 368.2 141.9L327 183C320.1 189.9 318.1 200.2 321.8 209.2C325.5 218.2 334.3 224 344 224L463.5 224C463.5 224 463.5 224 463.5 224L472 224C485.3 224 496 213.3 496 200L496 72C496 62.3 490.2 53.5 481.2 49.8C472.2 46.1 461.9 48.1 455 55L413.4 96.6C325.8 10.1 184.7 10.4 97.6 97.6C73.2 122 55.6 150.7 44.8 181.4C38.9 198.1 47.7 216.3 64.3 222.2C80.9 228.1 99.2 219.3 105.1 202.7C112.8 180.9 125.3 160.4 142.9 142.9L142.9 142.9ZM16 312L16 319.6L16 320.3L16 440C16 449.7 21.8 458.5 30.8 462.2C39.8 465.9 50.1 463.9 57 457L98.6 415.4C186.2 501.9 327.3 501.6 414.4 414.4C438.8 390 456.5 361.3 467.3 330.7C473.2 314 464.4 295.8 447.8 289.9C431.2 284 412.9 292.8 407 309.4C399.3 331.2 386.8 351.7 369.2 369.2C307 431.4 206.5 431.7 143.9 370.2L185 329C191.9 322.1 193.9 311.8 190.2 302.8C186.5 293.8 177.7 288 168 288L48.4 288L47.7 288L40 288C26.7 288 16 298.7 16 312Z" />
                             </DrawingGroup>
                         </DrawingImage>
                     </Image>
@@ -140,7 +141,8 @@
                         <Image Height="17" Width="17">
                             <DrawingImage>
                                 <DrawingGroup>
-                                    <GeometryDrawing Geometry="F1 M112 111L112 401C112 418.44 129 429.52 143 421.16L390.9 272.79C403.02 265.54 403.02 246.46 390.9 239.21L143 90.84C129 82.48 112 93.56 112 111Z">
+                                    <GeometryDrawing
+                                        Geometry="F1 M112 111L112 401C112 418.44 129 429.52 143 421.16L390.9 272.79C403.02 265.54 403.02 246.46 390.9 239.21L143 90.84C129 82.48 112 93.56 112 111Z">
                                         <GeometryDrawing.Pen>
                                             <Pen
                                                 Brush="{DynamicResource MainTextColor}"
@@ -217,7 +219,8 @@
                                     <DrawingImage>
                                         <DrawingImage.Drawing>
                                             <DrawingGroup ClipGeometry="M0,0 V512 H512 V0 H0 Z">
-                                                <GeometryDrawing Brush="{StaticResource ResizeBrush}" Geometry="F1 M512,512z M0,0z M464,448L48,448C21.49,448,0,426.51,0,400L0,112C0,85.49,21.49,64,48,64L464,64C490.51,64,512,85.49,512,112L512,400C512,426.51,490.51,448,464,448z M112,120C81.072,120 56,145.072 56,176 56,206.928 81.072,232 112,232 142.928,232 168,206.928 168,176 168,145.072 142.928,120 112,120z M64,384L448,384 448,272 360.485,184.485C355.799,179.799,348.201,179.799,343.514,184.485L208,320 152.485,264.485C147.799,259.799,140.201,259.799,135.514,264.485L64,336 64,384z" />
+                                                <GeometryDrawing Brush="{StaticResource ResizeBrush}"
+                                                                 Geometry="F1 M512,512z M0,0z M464,448L48,448C21.49,448,0,426.51,0,400L0,112C0,85.49,21.49,64,48,64L464,64C490.51,64,512,85.49,512,112L512,400C512,426.51,490.51,448,464,448z M112,120C81.072,120 56,145.072 56,176 56,206.928 81.072,232 112,232 142.928,232 168,206.928 168,176 168,145.072 142.928,120 112,120z M64,384L448,384 448,272 360.485,184.485C355.799,179.799,348.201,179.799,343.514,184.485L208,320 152.485,264.485C147.799,259.799,140.201,259.799,135.514,264.485L64,336 64,384z" />
                                             </DrawingGroup>
                                         </DrawingImage.Drawing>
                                     </DrawingImage>
@@ -232,7 +235,8 @@
                                     <DrawingImage>
                                         <DrawingImage.Drawing>
                                             <DrawingGroup ClipGeometry="M0,0 V512 H512 V0 H0 Z">
-                                                <GeometryDrawing Brush="{StaticResource ResizeBrush}" Geometry="F1 M512,512z M0,0z M464,448L48,448C21.49,448,0,426.51,0,400L0,112C0,85.49,21.49,64,48,64L464,64C490.51,64,512,85.49,512,112L512,400C512,426.51,490.51,448,464,448z M112,120C81.072,120 56,145.072 56,176 56,206.928 81.072,232 112,232 142.928,232 168,206.928 168,176 168,145.072 142.928,120 112,120z M64,384L448,384 448,272 360.485,184.485C355.799,179.799,348.201,179.799,343.514,184.485L208,320 152.485,264.485C147.799,259.799,140.201,259.799,135.514,264.485L64,336 64,384z" />
+                                                <GeometryDrawing Brush="{StaticResource ResizeBrush}"
+                                                                 Geometry="F1 M512,512z M0,0z M464,448L48,448C21.49,448,0,426.51,0,400L0,112C0,85.49,21.49,64,48,64L464,64C490.51,64,512,85.49,512,112L512,400C512,426.51,490.51,448,464,448z M112,120C81.072,120 56,145.072 56,176 56,206.928 81.072,232 112,232 142.928,232 168,206.928 168,176 168,145.072 142.928,120 112,120z M64,384L448,384 448,272 360.485,184.485C355.799,179.799,348.201,179.799,343.514,184.485L208,320 152.485,264.485C147.799,259.799,140.201,259.799,135.514,264.485L64,336 64,384z" />
                                             </DrawingGroup>
                                         </DrawingImage.Drawing>
                                     </DrawingImage>

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

@@ -18,6 +18,33 @@ public partial class ImageMenu : AnimatedMenu
         InitializeComponent();
         Loaded += delegate
         {
+            GoToPicBox.KeyDown += async (_, e) => await GoToPicBox_OnKeyDown(e);
+            Observable.EveryValueChanged(this, x => x.IsVisible, UIHelper.GetFrameProvider)
+                .Skip(1)
+                .Where(isVisible => !isVisible)
+                .Subscribe(_ => { SlideShowButton.Flyout.Hide(); });
+            // Determine if crop should be enabled every time it opens
+            Observable.EveryValueChanged(this, x => x.IsOpen, UIHelper.GetFrameProvider)
+                .Where(x => x)
+                .Subscribe(_ => { DetermineIfCropShouldBeEnabled(); });
+
+            Item2.Header = $"2 {TranslationManager.Translation.SecAbbreviation}";
+            Item5.Header = $"5 {TranslationManager.Translation.SecAbbreviation}";
+            Item10.Header = $"10 {TranslationManager.Translation.SecAbbreviation}";
+            Item20.Header = $"20 {TranslationManager.Translation.SecAbbreviation}";
+            Item30.Header = $"30 {TranslationManager.Translation.SecAbbreviation}";
+            Item60.Header = $"60 {TranslationManager.Translation.SecAbbreviation}";
+            Item90.Header = $"90 {TranslationManager.Translation.SecAbbreviation}";
+            Item120.Header = $"120 {TranslationManager.Translation.SecAbbreviation}";
+
+            if (DataContext is not MainViewModel vm)
+            {
+                return;
+            }
+
+            RotateLeftButton.Click += (_, _) => vm.MainWindow.IsRotateLeftClicked = true;
+            RotateRightButton.Click += (_, _) => vm.MainWindow.IsRotateRightClicked = true;
+
             if (Settings.Theme.GlassTheme)
             {
                 GoToPicButton.Classes.Remove("noBorderHover");
@@ -74,25 +101,6 @@ public partial class ImageMenu : AnimatedMenu
                     }
                 }
             }
-
-            GoToPicBox.KeyDown += async (_, e) => await GoToPicBox_OnKeyDown(e);
-            Observable.EveryValueChanged(this, x => x.IsVisible, UIHelper.GetFrameProvider)
-                .Skip(1)
-                .Where(isVisible => !isVisible)
-                .Subscribe(_ => { SlideShowButton.Flyout.Hide(); });
-            // Determine if crop should be enabled every time it opens
-            Observable.EveryValueChanged(this, x => x.IsOpen, UIHelper.GetFrameProvider)
-                .Where(x => x)
-                .Subscribe(_ => { DetermineIfCropShouldBeEnabled(); });
-
-            Item2.Header = $"2 {TranslationManager.Translation.SecAbbreviation}";
-            Item5.Header = $"5 {TranslationManager.Translation.SecAbbreviation}";
-            Item10.Header = $"10 {TranslationManager.Translation.SecAbbreviation}";
-            Item20.Header = $"20 {TranslationManager.Translation.SecAbbreviation}";
-            Item30.Header = $"30 {TranslationManager.Translation.SecAbbreviation}";
-            Item60.Header = $"60 {TranslationManager.Translation.SecAbbreviation}";
-            Item90.Header = $"90 {TranslationManager.Translation.SecAbbreviation}";
-            Item120.Header = $"120 {TranslationManager.Translation.SecAbbreviation}";
         };
     }
 

+ 35 - 2
src/PicView.Avalonia/WindowBehavior/WindowResizing.cs

@@ -4,10 +4,12 @@ using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Media.Imaging;
 using Avalonia.Threading;
 using ImageMagick;
+using PicView.Avalonia.CustomControls;
 using PicView.Avalonia.Gallery;
 using PicView.Avalonia.Navigation;
 using PicView.Avalonia.UI;
 using PicView.Avalonia.ViewModels;
+using PicView.Avalonia.Views.UC.Menus;
 using PicView.Core.DebugTools;
 using PicView.Core.Sizing;
 
@@ -84,6 +86,35 @@ public static class WindowResizing
             vm.PlatformService?.SetCursorPos(p.X, p.Y);
             vm.MainWindow.IsClickArrowRightClicked = false;
         }
+
+        if (vm.MainWindow.IsRotateLeftClicked)
+        {
+            var imageMenu = UIHelper.GetMainView.MainGrid.Children.OfType<ImageMenu>().FirstOrDefault();
+            var leftRotationBtn = imageMenu.GetControl<IconButton>("RotateLeftButton");
+            var point = new Point(20, 15);
+            var p = leftRotationBtn.PointToScreen(point);
+            vm.PlatformService?.SetCursorPos(p.X, p.Y);
+            vm.MainWindow.IsRotateLeftClicked = false;
+        }
+
+        if (vm.MainWindow.IsRotateRightClicked)
+        {
+            var imageMenu = UIHelper.GetMainView.MainGrid.Children.OfType<ImageMenu>().FirstOrDefault();
+            var rightRotationBtn = imageMenu.GetControl<IconButton>("RotateRightButton");
+            var point = new Point(20, 15);
+            var p = rightRotationBtn.PointToScreen(point);
+            vm.PlatformService?.SetCursorPos(p.X, p.Y);
+            vm.MainWindow.IsRotateRightClicked = false;
+        }
+
+        if (vm.MainWindow.IsTopToolbarRotationClicked)
+        {
+            var rotationRightBtn = UIHelper.GetTitlebar.GetControl<IconButton>("RotateRightButton");
+            var point = new Point(11, 7);
+            var p = rotationRightBtn.PointToScreen(point);
+            vm.PlatformService?.SetCursorPos(p.X, p.Y);
+            vm.MainWindow.IsTopToolbarRotationClicked = false;
+        }
     }
 
     #endregion
@@ -146,13 +177,14 @@ public static class WindowResizing
 
         vm.PicViewer.ScrollViewerWidth.Value = size.ScrollViewerWidth;
         vm.PicViewer.ScrollViewerHeight.Value = size.ScrollViewerHeight;
-        
+
         vm.PicViewer.AspectRatio.Value = size.AspectRatio;
 
         if (vm.Gallery is not { } gallery)
         {
             return;
         }
+
         gallery.GalleryMargin.Value = new Thickness(0, 0, 0, size.Margin);
         if (Settings.WindowProperties.AutoFit)
         {
@@ -245,7 +277,8 @@ public static class WindowResizing
             secondHeight = 0;
         }
 
-        return GetSize(firstWidth, firstHeight, secondWidth, secondHeight, vm.GlobalSettings.RotationAngle.CurrentValue, vm);
+        return GetSize(firstWidth, firstHeight, secondWidth, secondHeight, vm.GlobalSettings.RotationAngle.CurrentValue,
+            vm);
     }
 
     public static ImageSize? GetSize(double width, double height, double secondWidth, double secondHeight,

+ 0 - 14
src/PicView.Core/ImageDecoding/EXIFHelper.cs

@@ -137,20 +137,6 @@ public static class EXIFHelper
         return true;
     }
 
-    public static IExifProfile? GetExifProfile(string path)
-    {
-        using var magick = new MagickImage();
-        try
-        {
-            magick.Ping(path);
-            return magick.GetExifProfile();
-        }
-        catch (Exception)
-        {
-            return null;
-        }
-    }
-
     public static string GetDateTaken(IExifProfile profile)
     {
         var getDateTaken =