1
1
Эх сурвалжийг харах

Initial commit for switching from ReactiveUI to R3

Ruben 3 сар өмнө
parent
commit
361df7dc27
53 өөрчлөгдсөн 425 нэмэгдсэн , 483 устгасан
  1. 9 9
      src/PicView.Avalonia.MacOS/Views/ExifWindow.axaml
  2. 13 13
      src/PicView.Avalonia.MacOS/Views/MacMainWindow.axaml
  3. 3 3
      src/PicView.Avalonia.MacOS/Views/OpenWithView.axaml.cs
  4. 1 0
      src/PicView.Avalonia.Win32/Program.cs
  5. 1 1
      src/PicView.Avalonia.Win32/Views/EffectsWindow.axaml
  6. 9 9
      src/PicView.Avalonia.Win32/Views/ExifWindow.axaml
  7. 1 1
      src/PicView.Avalonia.Win32/Views/WinMainWindow.axaml
  8. 14 3
      src/PicView.Avalonia.Win32/Views/WinMainWindow.axaml.cs
  9. 3 3
      src/PicView.Avalonia/Clipboard/ClipboardFileOperations.cs
  10. 3 3
      src/PicView.Avalonia/Clipboard/ClipboardImageOperations.cs
  11. 6 6
      src/PicView.Avalonia/Converters/ConversionHelper.cs
  12. 2 2
      src/PicView.Avalonia/Crop/CropDragHandler.cs
  13. 12 12
      src/PicView.Avalonia/Crop/CropFunctions.cs
  14. 9 9
      src/PicView.Avalonia/Crop/CropLayoutManager.cs
  15. 2 2
      src/PicView.Avalonia/CustomControls/PicBox.cs
  16. 4 5
      src/PicView.Avalonia/FileSystem/FileSaverHelper.cs
  17. 25 25
      src/PicView.Avalonia/Functions/FunctionsMapper.cs
  18. 5 5
      src/PicView.Avalonia/ImageHandling/ImageFormatConverter.cs
  19. 1 1
      src/PicView.Avalonia/ImageHandling/ImageOptimizer.cs
  20. 3 3
      src/PicView.Avalonia/ImageTransformations/Rotation/RotationNavigation.cs
  21. 5 5
      src/PicView.Avalonia/ImageTransformations/Rotation/RotationTransformer.cs
  22. 1 1
      src/PicView.Avalonia/Navigation/ErrorHandling.cs
  23. 9 9
      src/PicView.Avalonia/Navigation/ExifHandling.cs
  24. 3 3
      src/PicView.Avalonia/Navigation/FileListManager.cs
  25. 10 10
      src/PicView.Avalonia/Navigation/ImageIterator.cs
  26. 7 7
      src/PicView.Avalonia/Navigation/ImageLoader.cs
  27. 8 8
      src/PicView.Avalonia/Navigation/NavigationManager.cs
  28. 23 23
      src/PicView.Avalonia/Navigation/UpdateImage.cs
  29. 1 0
      src/PicView.Avalonia/PicView.Avalonia.csproj
  30. 2 2
      src/PicView.Avalonia/Resizing/AspectRatioHelper.cs
  31. 1 1
      src/PicView.Avalonia/SettingsManagement/LanguageUpdater.cs
  32. 12 12
      src/PicView.Avalonia/SettingsManagement/SettingsUpdater.cs
  33. 7 7
      src/PicView.Avalonia/StartUp/QuickLoad.cs
  34. 1 1
      src/PicView.Avalonia/UI/HideInterfaceLogic.cs
  35. 42 39
      src/PicView.Avalonia/UI/TitleManager.cs
  36. 4 0
      src/PicView.Avalonia/UI/UIHelper.cs
  37. 3 3
      src/PicView.Avalonia/ViewModels/ImageCropperViewModel.cs
  38. 2 2
      src/PicView.Avalonia/Views/BatchResizeView.axaml.cs
  39. 1 1
      src/PicView.Avalonia/Views/EffectsView.axaml
  40. 17 17
      src/PicView.Avalonia/Views/EffectsView.axaml.cs
  41. 27 27
      src/PicView.Avalonia/Views/ImageInfoView.axaml
  42. 19 19
      src/PicView.Avalonia/Views/ImageInfoView.axaml.cs
  43. 11 11
      src/PicView.Avalonia/Views/ImageViewer.axaml
  44. 11 11
      src/PicView.Avalonia/Views/MainView.axaml
  45. 2 4
      src/PicView.Avalonia/Views/SingleImageResizeView.axaml
  46. 13 16
      src/PicView.Avalonia/Views/SingleImageResizeView.axaml.cs
  47. 2 2
      src/PicView.Avalonia/Views/UC/EditableTitlebar.axaml
  48. 4 5
      src/PicView.Avalonia/Views/UC/EditableTitlebar.axaml.cs
  49. 6 6
      src/PicView.Avalonia/Views/UC/Menus/FileMenu.axaml
  50. 4 4
      src/PicView.Avalonia/WindowBehavior/WindowFunctions.cs
  51. 13 13
      src/PicView.Avalonia/WindowBehavior/WindowResizing.cs
  52. 2 1
      src/PicView.Core/PicView.Core.csproj
  53. 26 98
      src/PicView.Core/ViewModels/PicViewerModel.cs

+ 9 - 9
src/PicView.Avalonia.MacOS/Views/ExifWindow.axaml

@@ -35,7 +35,7 @@
                 Classes="errorHover"
                 ClickMode="Release"
                 Command="{CompiledBinding RecycleFileCommand}"
-                CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                    FallbackValue=''}"
                 Data="{StaticResource RecycleGeometry}"
                 DockPanel.Dock="Right"
@@ -53,7 +53,7 @@
                 BorderThickness="0,0,1,0"
                 Classes="noBorderHover"
                 Command="{Binding OptimizeImageCommand}"
-                CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                    FallbackValue=''}"
                 DockPanel.Dock="Right"
                 Foreground="{StaticResource SecondaryTextColor}"
@@ -71,7 +71,7 @@
                 BorderThickness="0,0,1,0"
                 Classes="noBorderHover"
                 Command="{CompiledBinding OpenWithCommand}"
-                CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                    FallbackValue=''}"
                 Data="{StaticResource OpenWithGeometry}"
                 DockPanel.Dock="Right"
@@ -89,7 +89,7 @@
                 BorderThickness="0,0,1,0"
                 Classes="noBorderHover"
                 Command="{CompiledBinding PrintCommand}"
-                CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                    FallbackValue=''}"
                 Data="{StaticResource PrintGeometry}"
                 DockPanel.Dock="Right"
@@ -107,7 +107,7 @@
                 BorderThickness="0,0,1,0"
                 Classes="noBorderHover"
                 Command="{CompiledBinding CopyImageCommand}"
-                CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                    FallbackValue=''}"
                 DockPanel.Dock="Right"
                 Foreground="{StaticResource SecondaryTextColor}"
@@ -124,7 +124,7 @@
                 BorderThickness="0,0,1,0"
                 Classes="noBorderHover"
                 Command="{CompiledBinding CopyFileCommand}"
-                CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                    FallbackValue=''}"
                 Data="{StaticResource CopyGeometry}"
                 DockPanel.Dock="Right"
@@ -142,7 +142,7 @@
                 BorderThickness="0,0,1,0"
                 Classes="noBorderHover"
                 Command="{CompiledBinding DuplicateFileCommand}"
-                CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                    FallbackValue=''}"
                 Data="{StaticResource DuplicateGeometry}"
                 DockPanel.Dock="Right"
@@ -160,7 +160,7 @@
                 BorderThickness="1,0,1,0"
                 Classes="noBorderHover"
                 Command="{CompiledBinding LocateOnDiskCommand}"
-                CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                    FallbackValue=''}"
                 Data="{StaticResource ShowInFolderGeometry}"
                 DockPanel.Dock="Right"
@@ -180,7 +180,7 @@
                 BorderThickness="0,0,1,0"
                 Classes="noBorderHover"
                 Command="{CompiledBinding SetExifRating0Command}"
-                CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                    FallbackValue=''}"
                 DockPanel.Dock="Right"
                 Foreground="{StaticResource SecondaryTextColor}"

+ 13 - 13
src/PicView.Avalonia.MacOS/Views/MacMainWindow.axaml

@@ -8,7 +8,7 @@
     MinWidth="{CompiledBinding WindowMinSize}"
     SizeChanged="Control_OnSizeChanged"
     SizeToContent="{CompiledBinding SizeToContent}"
-    Title="{CompiledBinding PicViewer.WindowTitle}"
+    Title="{CompiledBinding PicViewer.WindowTitle.Value}"
     x:Class="PicView.Avalonia.MacOS.Views.MacMainWindow"
     x:DataType="viewModels:MainViewModel"
     xmlns="https://github.com/avaloniaui"
@@ -43,7 +43,7 @@
                                                     Converter={x:Static ObjectConverters.IsNotNull}}" />
                     <NativeMenuItem
                         Command="{CompiledBinding LocateOnDiskCommand}"
-                        CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                        CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                            FallbackValue=''}"
                         Header="{CompiledBinding Translation.ShowInFolder,
                                                  Mode=OneWay}"
@@ -52,7 +52,7 @@
                     <NativeMenuItemSeparator />
                     <NativeMenuItem
                         Command="{CompiledBinding DeleteFileCommand}"
-                        CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                        CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                            FallbackValue=''}"
                         Header="{CompiledBinding Translation.DeleteFile,
                                                  Mode=OneWay}"
@@ -61,7 +61,7 @@
                     <NativeMenuItemSeparator />
                     <NativeMenuItem
                         Command="{CompiledBinding ReloadCommand}"
-                        CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                        CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                            FallbackValue=''}"
                         Header="{CompiledBinding Translation.Reload,
                                                  Mode=OneWay}"
@@ -71,7 +71,7 @@
                     <NativeMenuItemSeparator />
                     <NativeMenuItem
                         Command="{CompiledBinding RenameCommand}"
-                        CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                        CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                            FallbackValue=''}"
                         Header="{CompiledBinding Translation.RenameFile,
                                                  Mode=OneWay}"
@@ -82,7 +82,7 @@
                     <NativeMenuItemSeparator />
                     <NativeMenuItem
                         Command="{CompiledBinding CopyFileCommand}"
-                        CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                        CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                            FallbackValue=''}"
                         Header="{CompiledBinding Translation.CopyFile,
                                                  Mode=OneWay}"
@@ -90,7 +90,7 @@
                                                     Converter={x:Static ObjectConverters.IsNotNull}}" />
                     <NativeMenuItem
                         Command="{CompiledBinding CopyImageCommand}"
-                        CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                        CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                            FallbackValue=''}"
                         Header="{CompiledBinding Translation.CopyImage,
                                                  Mode=OneWay}"
@@ -98,7 +98,7 @@
                                                     Converter={x:Static ObjectConverters.IsNotNull}}" />
                     <NativeMenuItem
                         Command="{CompiledBinding CopyFilePathCommand}"
-                        CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                        CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                            FallbackValue=''}"
                         Header="{CompiledBinding Translation.FileCopyPath,
                                                  Mode=OneWay}"
@@ -106,7 +106,7 @@
                                                     Converter={x:Static ObjectConverters.IsNotNull}}" />
                     <NativeMenuItem
                         Command="{CompiledBinding DuplicateFileCommand}"
-                        CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                        CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                            FallbackValue=''}"
                         Header="{CompiledBinding Translation.DuplicateFile,
                                                  Mode=OneWay}"
@@ -139,7 +139,7 @@
                     <NativeMenuItemSeparator />
                     <NativeMenuItem
                         Command="{CompiledBinding CropCommand}"
-                        CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                        CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                            FallbackValue=''}"
                         Header="{CompiledBinding Translation.Crop,
                                                  Mode=OneWay}"
@@ -148,7 +148,7 @@
                     <NativeMenuItemSeparator />
                     <NativeMenuItem
                         Command="{CompiledBinding SetAsWallpaperCommand}"
-                        CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                        CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                            FallbackValue=''}"
                         Header="{CompiledBinding Translation.SetAsWallpaper,
                                                  Mode=OneWay}"
@@ -158,7 +158,7 @@
                     <NativeMenuItem Command="{CompiledBinding ShowExifWindowCommand}" Header="{CompiledBinding Translation.ImageInfo, Mode=OneWay}" />
                     <NativeMenuItem
                         Command="{CompiledBinding FilePropertiesCommand}"
-                        CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                        CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                            FallbackValue=''}"
                         Header="{CompiledBinding Translation.FileProperties,
                                                  Mode=OneWay}"
@@ -170,7 +170,7 @@
                     <NativeMenuItemSeparator />
                     <NativeMenuItem
                         Command="{CompiledBinding OptimizeImageCommand}"
-                        CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                        CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                            FallbackValue=''}"
                         Header="{CompiledBinding Translation.OptimizeImage,
                                                  Mode=OneWay}"

+ 3 - 3
src/PicView.Avalonia.MacOS/Views/OpenWithView.axaml.cs

@@ -1,12 +1,12 @@
+using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Input;
 using Avalonia.Interactivity;
 using Avalonia.Layout;
 using Avalonia.Threading;
 using PicView.Avalonia.ViewModels;
-using PicView.Core.MacOS.FileAssociation;
-using Avalonia;
 using PicView.Core.MacOS.AppLauncher;
+using PicView.Core.MacOS.FileAssociation;
 
 namespace PicView.Avalonia.MacOS.Views;
 
@@ -50,7 +50,7 @@ public partial class OpenWithView : Window
             {
                 return;
             }
-            _filePath = vm.PicViewer?.FileInfo?.FullName;
+            _filePath = vm.PicViewer?.FileInfo?.CurrentValue.FullName;
         }
 
 

+ 1 - 0
src/PicView.Avalonia.Win32/Program.cs

@@ -22,6 +22,7 @@ internal class Program
             .LogToTrace()
 #endif
             .UseReactiveUI()
+            .UseR3()
             .With(new SkiaOptions
             {
                 MaxGpuResourceSizeBytes = 256_000_000,

+ 1 - 1
src/PicView.Avalonia.Win32/Views/EffectsWindow.axaml

@@ -114,7 +114,7 @@
                             <Separator />
                             <MenuItem
                                 Command="{CompiledBinding SetAsWallpaperFilledCommand}"
-                                CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                                CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                                    FallbackValue=''}"
                                 Header="{CompiledBinding Translation.SetAsWallpaper,
                                                          Mode=OneWay}">

+ 9 - 9
src/PicView.Avalonia.Win32/Views/ExifWindow.axaml

@@ -80,7 +80,7 @@
                     BorderThickness="0,0,1,0"
                     Classes="noBorderHover"
                     Command="{CompiledBinding SetExifRating0Command}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     DockPanel.Dock="Right"
                     Foreground="{DynamicResource MainTextColor}"
@@ -96,7 +96,7 @@
                     Classes="errorHover"
                     ClickMode="Release"
                     Command="{CompiledBinding RecycleFileCommand}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     Data="{StaticResource RecycleGeometry}"
                     DockPanel.Dock="Right"
@@ -114,7 +114,7 @@
                     BorderThickness="0,0,1,0"
                     Classes="noBorderHover"
                     Command="{Binding OptimizeImageCommand}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     DockPanel.Dock="Right"
                     Foreground="{DynamicResource MainTextColor}"
@@ -132,7 +132,7 @@
                     BorderThickness="0,0,1,0"
                     Classes="noBorderHover"
                     Command="{CompiledBinding OpenWithCommand}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     Data="{StaticResource OpenWithGeometry}"
                     DockPanel.Dock="Right"
@@ -150,7 +150,7 @@
                     BorderThickness="0,0,1,0"
                     Classes="noBorderHover"
                     Command="{CompiledBinding PrintCommand}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     Data="{StaticResource PrintGeometry}"
                     DockPanel.Dock="Right"
@@ -168,7 +168,7 @@
                     BorderThickness="0,0,1,0"
                     Classes="noBorderHover"
                     Command="{CompiledBinding CopyImageCommand}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     DockPanel.Dock="Right"
                     Foreground="{DynamicResource MainTextColor}"
@@ -185,7 +185,7 @@
                     BorderThickness="0,0,1,0"
                     Classes="noBorderHover"
                     Command="{CompiledBinding CopyFileCommand}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     Data="{StaticResource CopyGeometry}"
                     DockPanel.Dock="Right"
@@ -203,7 +203,7 @@
                     BorderThickness="0,0,1,0"
                     Classes="noBorderHover"
                     Command="{CompiledBinding DuplicateFileCommand}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     Data="{StaticResource DuplicateGeometry}"
                     DockPanel.Dock="Right"
@@ -221,7 +221,7 @@
                     BorderThickness="1,0,1,0"
                     Classes="noBorderHover"
                     Command="{CompiledBinding LocateOnDiskCommand}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     Data="{StaticResource ShowInFolderGeometry}"
                     DockPanel.Dock="Right"

+ 1 - 1
src/PicView.Avalonia.Win32/Views/WinMainWindow.axaml

@@ -5,7 +5,7 @@
     MinWidth="{CompiledBinding WindowMinSize}"
     SizeChanged="Control_OnSizeChanged"
     SizeToContent="{CompiledBinding SizeToContent}"
-    Title="{CompiledBinding PicViewer.WindowTitle,
+    Title="{CompiledBinding PicViewer.WindowTitle.Value,
                             Mode=OneWay}"
     TransparencyLevelHint="AcrylicBlur"
     x:Class="PicView.Avalonia.Win32.Views.WinMainWindow"

+ 14 - 3
src/PicView.Avalonia.Win32/Views/WinMainWindow.axaml.cs

@@ -5,16 +5,23 @@ using PicView.Avalonia.DragAndDrop;
 using PicView.Avalonia.UI;
 using PicView.Avalonia.ViewModels;
 using PicView.Avalonia.WindowBehavior;
-using ReactiveUI;
+using R3;
+using R3.Avalonia;
 
 namespace PicView.Avalonia.Win32.Views;
 
 public partial class WinMainWindow : Window
 {
+    private readonly AvaloniaRenderingFrameProvider _frameProvider;
+    
     public WinMainWindow()
     {
         InitializeComponent();
         
+        // initialize RenderingFrameProvider
+        _frameProvider = new AvaloniaRenderingFrameProvider(GetTopLevel(this)!);
+        UIHelper.SetFrameProvider(_frameProvider);
+        
         if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
         {
             return;
@@ -42,9 +49,8 @@ public partial class WinMainWindow : Window
                 DragAndDropHelper.RemoveDragDropView();
             };
             
-            this.WhenAnyValue(x => x.WindowState).Subscribe(state =>
+            Observable.EveryValueChanged(this, x => x.WindowState, _frameProvider).Subscribe( state =>
             {
-
                 switch (state)
                 {
                     case WindowState.FullScreen:
@@ -101,4 +107,9 @@ public partial class WinMainWindow : Window
         var wm = (MainViewModel)DataContext;
         WindowResizing.SetSize(wm);
     }
+    
+    protected override void OnClosed(EventArgs e)
+    {
+        _frameProvider.Dispose();
+    }
 }

+ 3 - 3
src/PicView.Avalonia/Clipboard/ClipboardFileOperations.cs

@@ -33,7 +33,7 @@ public static class ClipboardFileOperations
         {
             vm.IsLoading = true;
             
-            if (path == vm.PicViewer.FileInfo?.FullName)
+            if (path == vm.PicViewer.FileInfo?.CurrentValue.FullName)
             {
                 await DuplicateCurrentFile(vm);
             }
@@ -66,8 +66,8 @@ public static class ClipboardFileOperations
             return;
         }
 
-        var oldPath = vm.PicViewer.FileInfo.FullName;
-        var duplicatedPath = await FileHelper.DuplicateAndReturnFileNameAsync(oldPath, vm.PicViewer.FileInfo);
+        var oldPath = vm.PicViewer.FileInfo.CurrentValue.FullName;
+        var duplicatedPath = await FileHelper.DuplicateAndReturnFileNameAsync(oldPath, vm.PicViewer.FileInfo.CurrentValue);
 
         if (string.IsNullOrWhiteSpace(duplicatedPath) || !File.Exists(duplicatedPath))
         {

+ 3 - 3
src/PicView.Avalonia/Clipboard/ClipboardImageOperations.cs

@@ -23,7 +23,7 @@ public static class ClipboardImageOperations
     public static async Task<bool> CopyImageToClipboard(MainViewModel vm)
     {
         var clipboard = ClipboardService.GetClipboard();
-        if (clipboard == null || vm.PicViewer.ImageSource is not Bitmap bitmap)
+        if (clipboard == null || vm.PicViewer.ImageSource.CurrentValue is not Bitmap bitmap)
         {
             return false;
         }
@@ -93,12 +93,12 @@ public static class ClipboardImageOperations
             return Convert.ToBase64String(await File.ReadAllBytesAsync(path));
         }
 
-        switch (vm.PicViewer.ImageType)
+        switch (vm.PicViewer.ImageType.CurrentValue)
         {
             case ImageType.AnimatedGif:
             case ImageType.AnimatedWebp:
             case ImageType.Bitmap:
-                if (vm.PicViewer.ImageSource is not Bitmap bitmap)
+                if (vm.PicViewer.ImageSource.CurrentValue is not Bitmap bitmap)
                 {
                     return string.Empty;
                 }

+ 6 - 6
src/PicView.Avalonia/Converters/ConversionHelper.cs

@@ -25,7 +25,7 @@ internal static class ConversionHelper
     public static async Task ResizeImageByPercentage(int percentage, MainViewModel vm)
     {
         TitleManager.SetLoadingTitle(vm);
-        var success = await ResizeImageByPercentage(vm.PicViewer.FileInfo, percentage);
+        var success = await ResizeImageByPercentage(vm.PicViewer.FileInfo.CurrentValue, percentage);
         if (success)
         {
             await NavigationManager.QuickReload();
@@ -94,7 +94,7 @@ internal static class ConversionHelper
             return;
         }
 
-        var newPath = await ConvertTask(vm.PicViewer.FileInfo, index, vm.PlatformService);
+        var newPath = await ConvertTask(vm.PicViewer.FileInfo.CurrentValue, index, vm.PlatformService);
         if (!string.IsNullOrWhiteSpace(newPath))
         {
             await NavigationManager.LoadPicFromStringAsync(newPath, vm);
@@ -109,10 +109,10 @@ internal static class ConversionHelper
             return;
         }
 
-        if (vm.PicViewer.FileInfo.Extension.Equals(".jpg", StringComparison.InvariantCultureIgnoreCase)
-            || vm.PicViewer.FileInfo.Extension.Equals(".jpeg", StringComparison.InvariantCultureIgnoreCase)
-            || vm.PicViewer.FileInfo.Extension.Equals(".png", StringComparison.InvariantCultureIgnoreCase)
-            || vm.PicViewer.FileInfo.Extension.Equals(".gif", StringComparison.InvariantCultureIgnoreCase))
+        if (vm.PicViewer.FileInfo.CurrentValue.Extension.Equals(".jpg", StringComparison.InvariantCultureIgnoreCase)
+            || vm.PicViewer.FileInfo.CurrentValue.Extension.Equals(".jpeg", StringComparison.InvariantCultureIgnoreCase)
+            || vm.PicViewer.FileInfo.CurrentValue.Extension.Equals(".png", StringComparison.InvariantCultureIgnoreCase)
+            || vm.PicViewer.FileInfo.CurrentValue.Extension.Equals(".gif", StringComparison.InvariantCultureIgnoreCase))
         {
             vm.ShouldOptimizeImageBeEnabled = true;
         }

+ 2 - 2
src/PicView.Avalonia/Crop/CropDragHandler.cs

@@ -68,8 +68,8 @@ public class CropDragHandler(CropControl control)
         var newTop = _originalRect.Y + delta.Y;
 
         // Clamp the newLeft and newTop values to keep the rectangle within bounds
-        newLeft = Math.Max(0, Math.Min(vm.PicViewer.ImageWidth - vm.Crop.SelectionWidth, newLeft));
-        newTop = Math.Max(0, Math.Min(vm.PicViewer.ImageHeight - vm.Crop.SelectionHeight, newTop));
+        newLeft = Math.Max(0, Math.Min(vm.PicViewer.ImageWidth.CurrentValue - vm.Crop.SelectionWidth, newLeft));
+        newTop = Math.Max(0, Math.Min(vm.PicViewer.ImageHeight.CurrentValue - vm.Crop.SelectionHeight, newTop));
 
         // Only proceed if new positions are valid (i.e., not NaN)
         if (double.IsNaN(newLeft) || double.IsNaN(newTop))

+ 12 - 12
src/PicView.Avalonia/Crop/CropFunctions.cs

@@ -33,7 +33,7 @@ public static class CropFunctions
             return;
         }
 
-        if (vm?.PicViewer.ImageSource is not Bitmap bitmap)
+        if (vm?.PicViewer.ImageSource.CurrentValue is not Bitmap bitmap)
         {
             return;
         }
@@ -48,14 +48,14 @@ public static class CropFunctions
             await WindowResizing.SetSizeAsync(vm);
         }
 
-        var size = new Size(vm.PicViewer.ImageWidth, vm.PicViewer.ImageHeight);
+        var size = new Size(vm.PicViewer.ImageWidth.CurrentValue, vm.PicViewer.ImageHeight.CurrentValue);
         await Dispatcher.UIThread.InvokeAsync(() =>
         {
             vm.Crop = new ImageCropperViewModel(bitmap)
             {
                 ImageWidth = size.Width,
                 ImageHeight = size.Height,
-                AspectRatio = vm.PicViewer.AspectRatio
+                AspectRatio = vm.PicViewer.AspectRatio.CurrentValue
             };
             var cropControl = new CropControl
             {
@@ -68,8 +68,8 @@ public static class CropFunctions
         });
 
         IsCropping = true;
-        vm.PicViewer.Title = TranslationManager.Translation.CropMessage;
-        vm.PicViewer.TitleTooltip = TranslationManager.Translation.CropMessage;
+        vm.PicViewer.Title.Value = TranslationManager.Translation.CropMessage;
+        vm.PicViewer.TitleTooltip.Value = TranslationManager.Translation.CropMessage;
 
         await FunctionsMapper.CloseMenus();
 
@@ -92,15 +92,15 @@ public static class CropFunctions
         TitleManager.SetTitle(vm);
 
         // Reset image type to fix issue with animated images
-        switch (vm.PicViewer.ImageType)
+        switch (vm.PicViewer.ImageType.CurrentValue)
         {
             case ImageType.AnimatedWebp:
-                vm.PicViewer.ImageType = ImageType.Bitmap;
-                vm.PicViewer.ImageType = ImageType.AnimatedWebp;
+                vm.PicViewer.ImageType.Value = ImageType.Bitmap;
+                vm.PicViewer.ImageType.Value = ImageType.AnimatedWebp;
                 break;
             case ImageType.AnimatedGif:
-                vm.PicViewer.ImageType = ImageType.Bitmap;
-                vm.PicViewer.ImageType = ImageType.AnimatedGif;
+                vm.PicViewer.ImageType.Value = ImageType.Bitmap;
+                vm.PicViewer.ImageType.Value = ImageType.AnimatedGif;
                 break;
         }
 
@@ -114,7 +114,7 @@ public static class CropFunctions
             return false;
         }
 
-        if (vm?.PicViewer.ImageSource is not Bitmap || Settings.ImageScaling.ShowImageSideBySide)
+        if (vm?.PicViewer.ImageSource.CurrentValue is not Bitmap || Settings.ImageScaling.ShowImageSideBySide)
         {
             vm.ShouldCropBeEnabled = false;
             return false;
@@ -130,7 +130,7 @@ public static class CropFunctions
             return false;
         }
 
-        if (vm.RotationAngle is 0 && vm.PicViewer.ScaleX is 1)
+        if (vm.RotationAngle is 0 && vm.PicViewer.ScaleX.CurrentValue is 1)
         {
             vm.ShouldCropBeEnabled = true;
             return true;

+ 9 - 9
src/PicView.Avalonia/Crop/CropLayoutManager.cs

@@ -17,14 +17,14 @@ public class CropLayoutManager(CropControl control)
         }
 
         // Ensure image dimensions are valid before proceeding
-        if (vm.PicViewer.ImageWidth <= 0 || vm.PicViewer.ImageHeight <= 0)
+        if (vm.PicViewer.ImageWidth.CurrentValue <= 0 || vm.PicViewer.ImageHeight.CurrentValue <= 0)
         {
             return;
         }
 
         // Set initial width and height for the crop rectangle
-        var pixelWidth = vm.PicViewer.ImageWidth / vm.PicViewer.AspectRatio;
-        var pixelHeight = vm.PicViewer.ImageHeight / vm.PicViewer.AspectRatio;
+        var pixelWidth = vm.PicViewer.ImageWidth.CurrentValue / vm.PicViewer.AspectRatio.CurrentValue;
+        var pixelHeight = vm.PicViewer.ImageHeight.CurrentValue / vm.PicViewer.AspectRatio.CurrentValue;
 
         if (pixelWidth >= DefaultSelectionSize * 2 || pixelHeight >= DefaultSelectionSize * 2)
         {
@@ -38,8 +38,8 @@ public class CropLayoutManager(CropControl control)
         }
 
         // Calculate centered position
-        vm.Crop.SelectionX = Convert.ToInt32((vm.PicViewer.ImageWidth - vm.Crop.SelectionWidth) / 2);
-        vm.Crop.SelectionY = Convert.ToInt32((vm.PicViewer.ImageHeight - vm.Crop.SelectionHeight) / 2);
+        vm.Crop.SelectionX = Convert.ToInt32((vm.PicViewer.ImageWidth.CurrentValue - vm.Crop.SelectionWidth) / 2);
+        vm.Crop.SelectionY = Convert.ToInt32((vm.PicViewer.ImageHeight.CurrentValue - vm.Crop.SelectionHeight) / 2);
 
         // Apply the calculated position to the MainRectangle
         Canvas.SetLeft(control.MainRectangle, vm.Crop.SelectionX);
@@ -76,13 +76,13 @@ public class CropLayoutManager(CropControl control)
 
         // Calculate the positions and sizes for the surrounding rectangles
         // Top Rectangle (above MainRectangle)
-        control.TopRectangle.Width = vm.PicViewer.ImageWidth;
+        control.TopRectangle.Width = vm.PicViewer.ImageWidth.CurrentValue;
         control.TopRectangle.Height = top < 0 ? 0 : top;
         Canvas.SetTop(control.TopRectangle, 0);
 
         // Bottom Rectangle (below MainRectangle)
-        control.BottomRectangle.Width = vm.PicViewer.ImageWidth;
-        var newBottomRectangleHeight = vm.PicViewer.ImageHeight - bottom < 0 ? 0 : vm.PicViewer.ImageHeight - bottom;
+        control.BottomRectangle.Width = vm.PicViewer.ImageWidth.CurrentValue;
+        var newBottomRectangleHeight = vm.PicViewer.ImageHeight.CurrentValue - bottom < 0 ? 0 : vm.PicViewer.ImageHeight.CurrentValue - bottom;
         control.BottomRectangle.Height = newBottomRectangleHeight;
         Canvas.SetTop(control.BottomRectangle, bottom);
 
@@ -93,7 +93,7 @@ public class CropLayoutManager(CropControl control)
         Canvas.SetTop(control.LeftRectangle, top);
 
         // Right Rectangle (right of MainRectangle)
-        var newRightRectangleWidth = vm.PicViewer.ImageWidth - right < 0 ? 0 : vm.PicViewer.ImageWidth - right;
+        var newRightRectangleWidth = vm.PicViewer.ImageWidth.CurrentValue - right < 0 ? 0 : vm.PicViewer.ImageWidth.CurrentValue - right;
         control.RightRectangle.Width = newRightRectangleWidth;
         control.RightRectangle.Height = vm.Crop.SelectionHeight;
         Canvas.SetLeft(control.RightRectangle, right);

+ 2 - 2
src/PicView.Avalonia/CustomControls/PicBox.cs

@@ -282,7 +282,7 @@ public class PicBox : Control, IDisposable
             return new Size(preloadValue.ImageModel.PixelWidth, preloadValue.ImageModel.PixelHeight);
         }
 
-        if (vm.PicViewer.FileInfo?.Exists != true)
+        if (vm.PicViewer.FileInfo?.CurrentValue.Exists != true)
         {
             return new Size();
         }
@@ -290,7 +290,7 @@ public class PicBox : Control, IDisposable
         try
         {
             using var magickImage = new MagickImage();
-            magickImage.Ping(vm.PicViewer.FileInfo);
+            magickImage.Ping(vm.PicViewer.FileInfo.CurrentValue);
             return new Size(magickImage.Width, magickImage.Height);
         }
         catch (Exception exception)

+ 4 - 5
src/PicView.Avalonia/FileSystem/FileSaverHelper.cs

@@ -1,5 +1,4 @@
 using Avalonia.Media.Imaging;
-using PicView.Avalonia.ImageHandling;
 using PicView.Avalonia.ViewModels;
 using PicView.Core.FileHandling;
 using PicView.Core.ImageDecoding;
@@ -21,7 +20,7 @@ public static class FileSaverHelper
         }
         else
         {
-            await SaveFileAsync(vm.PicViewer.FileInfo.FullName, vm.PicViewer.FileInfo.FullName, vm);
+            await SaveFileAsync(vm.PicViewer.FileInfo.CurrentValue.FullName, vm.PicViewer.FileInfo.CurrentValue.FullName, vm);
         }
         
         //TODO: Add visual design to tell the user that file was saved
@@ -35,7 +34,7 @@ public static class FileSaverHelper
         }
         
         // Suggest random filename for saving, if it is not an existing file
-        var fileName = vm.PicViewer.FileInfo is null ? Path.GetRandomFileName() : vm.PicViewer.FileInfo.Name;
+        var fileName = vm.PicViewer.FileInfo is null ? Path.GetRandomFileName() : vm.PicViewer.FileInfo.CurrentValue.Name;
 
         await FilePicker.PickAndSaveFileAsAsync(fileName, vm);
     }
@@ -74,13 +73,13 @@ public static class FileSaverHelper
             
             try
             {
-                switch (vm.PicViewer.ImageType)
+                switch (vm.PicViewer.ImageType.CurrentValue)
                 {
                     case ImageType.AnimatedGif: // TODO: Add animated GIF support
                     case ImageType.AnimatedWebp: // TODO: Add animated WebP support
                     case ImageType.Bitmap:
                     {
-                        if (vm.PicViewer.ImageSource is not Bitmap bitmap)
+                        if (vm.PicViewer.ImageSource.CurrentValue is not Bitmap bitmap)
                         {
                             throw new InvalidOperationException("No bitmap available for saving.");
                         }

+ 25 - 25
src/PicView.Avalonia/Functions/FunctionsMapper.cs

@@ -536,11 +536,11 @@ public static class FunctionsMapper
 
     /// <inheritdoc cref="FileManager.OpenWith(string, MainViewModel)" />
     public static async Task OpenWith() =>
-        await Task.Run(() => Vm?.PlatformService?.OpenWith(Vm.PicViewer.FileInfo?.FullName)).ConfigureAwait(false);
+        await Task.Run(() => Vm?.PlatformService?.OpenWith(Vm.PicViewer.FileInfo?.CurrentValue.FullName)).ConfigureAwait(false);
     
     /// <inheritdoc cref="FileManager.LocateOnDisk(string, MainViewModel)" />
     public static async Task OpenInExplorer()=>
-        await Task.Run(() => Vm?.PlatformService?.LocateOnDisk(Vm.PicViewer.FileInfo?.FullName)).ConfigureAwait(false);
+        await Task.Run(() => Vm?.PlatformService?.LocateOnDisk(Vm.PicViewer.FileInfo?.CurrentValue.FullName)).ConfigureAwait(false);
 
     /// <inheritdoc cref="FileSaverHelper.SaveCurrentFile(MainViewModel)" />
     public static async Task Save() =>
@@ -552,11 +552,11 @@ public static class FunctionsMapper
     
     /// <inheritdoc cref="FileManager.DeleteFileWithOptionalDialog" />
     public static async Task DeleteFile() =>
-        await FileManager.DeleteFileWithOptionalDialog(true, Vm.PicViewer?.FileInfo?.FullName, Vm.PlatformService).ConfigureAwait(false);
+        await FileManager.DeleteFileWithOptionalDialog(true, Vm.PicViewer?.FileInfo?.CurrentValue.FullName, Vm.PlatformService).ConfigureAwait(false);
     
     /// <inheritdoc cref="FileManager.DeleteFileWithOptionalDialog" />
     public static async Task DeleteFilePermanently() =>
-        await FileManager.DeleteFileWithOptionalDialog(false, Vm.PicViewer?.FileInfo?.FullName, Vm.PlatformService).ConfigureAwait(false);
+        await FileManager.DeleteFileWithOptionalDialog(false, Vm.PicViewer?.FileInfo?.CurrentValue.FullName, Vm.PlatformService).ConfigureAwait(false);
 
     public static async Task Rename()
     {
@@ -569,7 +569,7 @@ public static class FunctionsMapper
     
     /// <inheritdoc cref="FileManager.ShowFileProperties(string, MainViewModel)" />
     public static async Task ShowFileProperties() =>
-        await Task.Run(() => Vm?.PlatformService?.ShowFileProperties(Vm.PicViewer.FileInfo?.FullName)).ConfigureAwait(false);
+        await Task.Run(() => Vm?.PlatformService?.ShowFileProperties(Vm.PicViewer.FileInfo?.CurrentValue.FullName)).ConfigureAwait(false);
     
     #endregion
 
@@ -577,11 +577,11 @@ public static class FunctionsMapper
 
     /// <inheritdoc cref="ClipboardFileOperations.CopyFileToClipboard(string, MainViewModel)" />
     public static async Task CopyFile() =>
-        await ClipboardFileOperations.CopyFileToClipboard(Vm?.PicViewer.FileInfo?.FullName, Vm).ConfigureAwait(false);
+        await ClipboardFileOperations.CopyFileToClipboard(Vm?.PicViewer.FileInfo?.CurrentValue.FullName, Vm).ConfigureAwait(false);
     
     /// <inheritdoc cref="ClipboardTextOperations.CopyTextToClipboard(string)" />
     public static async Task CopyFilePath() => 
-        await ClipboardTextOperations.CopyTextToClipboard(Vm?.PicViewer.FileInfo?.FullName).ConfigureAwait(false);
+        await ClipboardTextOperations.CopyTextToClipboard(Vm?.PicViewer.FileInfo?.CurrentValue.FullName).ConfigureAwait(false);
 
     /// <inheritdoc cref="ClipboardImageOperations.CopyImageToClipboard(MainViewModel)" />
     public static async Task CopyImage() => 
@@ -589,15 +589,15 @@ public static class FunctionsMapper
 
     /// <inheritdoc cref="ClipboardImageOperations.CopyBase64ToClipboard(string, MainViewModel)" />
     public static async Task CopyBase64() =>
-        await ClipboardImageOperations.CopyBase64ToClipboard(Vm.PicViewer.FileInfo?.FullName, vm: Vm).ConfigureAwait(false);
+        await ClipboardImageOperations.CopyBase64ToClipboard(Vm.PicViewer.FileInfo?.CurrentValue.FullName, vm: Vm).ConfigureAwait(false);
 
     /// <inheritdoc cref="ClipboardFileOperations.Duplicate(string, MainViewModel)" />
     public static async Task DuplicateFile() => 
-        await ClipboardFileOperations.Duplicate(Vm.PicViewer.FileInfo?.FullName, Vm).ConfigureAwait(false);
+        await ClipboardFileOperations.Duplicate(Vm.PicViewer.FileInfo?.CurrentValue.FullName, Vm).ConfigureAwait(false);
 
     /// <inheritdoc cref="ClipboardFileOperations.CutFile(string, MainViewModel)" />
     public static async Task CutFile() =>
-        await ClipboardFileOperations.CutFile(Vm.PicViewer.FileInfo.FullName, Vm).ConfigureAwait(false);
+        await ClipboardFileOperations.CutFile(Vm.PicViewer.FileInfo.CurrentValue.FullName, Vm).ConfigureAwait(false);
 
     /// <inheritdoc cref="ClipboardPasteOperations.Paste(MainViewModel)" />
     public static async Task Paste() =>
@@ -697,7 +697,7 @@ public static class FunctionsMapper
             return;
         }
 
-        await Task.Run(() => { EXIFHelper.SetEXIFRating(Vm.PicViewer.FileInfo.FullName, 0); });
+        await Task.Run(() => { EXIFHelper.SetEXIFRating(Vm.PicViewer.FileInfo.CurrentValue.FullName, 0); });
         Vm.EXIFRating = 0;
     }
 
@@ -709,7 +709,7 @@ public static class FunctionsMapper
             return;
         }
 
-        await Task.Run(() => { EXIFHelper.SetEXIFRating(Vm.PicViewer.FileInfo.FullName, 1); });
+        await Task.Run(() => { EXIFHelper.SetEXIFRating(Vm.PicViewer.FileInfo.CurrentValue.FullName, 1); });
         Vm.EXIFRating = 1;
     }
 
@@ -720,7 +720,7 @@ public static class FunctionsMapper
         {
             return;
         }
-        await Task.Run(() => { EXIFHelper.SetEXIFRating(Vm.PicViewer.FileInfo.FullName, 2); });
+        await Task.Run(() => { EXIFHelper.SetEXIFRating(Vm.PicViewer.FileInfo.CurrentValue.FullName, 2); });
         Vm.EXIFRating = 2;
     }
 
@@ -731,7 +731,7 @@ public static class FunctionsMapper
         {
             return;
         }
-        await Task.Run(() => { EXIFHelper.SetEXIFRating(Vm.PicViewer.FileInfo.FullName, 3); });
+        await Task.Run(() => { EXIFHelper.SetEXIFRating(Vm.PicViewer.FileInfo.CurrentValue.FullName, 3); });
         Vm.EXIFRating = 3;
     }
 
@@ -742,7 +742,7 @@ public static class FunctionsMapper
         {
             return;
         }
-        await Task.Run(() => { EXIFHelper.SetEXIFRating(Vm.PicViewer.FileInfo.FullName, 4); });
+        await Task.Run(() => { EXIFHelper.SetEXIFRating(Vm.PicViewer.FileInfo.CurrentValue.FullName, 4); });
         Vm.EXIFRating = 4;
     }
 
@@ -753,7 +753,7 @@ public static class FunctionsMapper
         {
             return;
         }
-        await Task.Run(() => { EXIFHelper.SetEXIFRating(Vm.PicViewer.FileInfo.FullName, 5); });
+        await Task.Run(() => { EXIFHelper.SetEXIFRating(Vm.PicViewer.FileInfo.CurrentValue.FullName, 5); });
         Vm.EXIFRating = 5;
     }
 
@@ -799,25 +799,25 @@ public static class FunctionsMapper
         await SetAsWallpaperFilled();
 
     public static async Task SetAsWallpaperTiled() =>
-        await Task.Run(() => Vm.PlatformService.SetAsWallpaper(Vm.PicViewer.FileInfo.FullName, 0)).ConfigureAwait(false);
+        await Task.Run(() => Vm.PlatformService.SetAsWallpaper(Vm.PicViewer.FileInfo.CurrentValue.FullName, 0)).ConfigureAwait(false);
     
     public static async Task SetAsWallpaperCentered() =>
-        await Task.Run(() => Vm.PlatformService.SetAsWallpaper(Vm.PicViewer.FileInfo.FullName, 1)).ConfigureAwait(false);
+        await Task.Run(() => Vm.PlatformService.SetAsWallpaper(Vm.PicViewer.FileInfo.CurrentValue.FullName, 1)).ConfigureAwait(false);
     
     public static async Task SetAsWallpaperStretched() =>
-        await Task.Run(() => Vm.PlatformService.SetAsWallpaper(Vm.PicViewer.FileInfo.FullName, 2)).ConfigureAwait(false);
+        await Task.Run(() => Vm.PlatformService.SetAsWallpaper(Vm.PicViewer.FileInfo.CurrentValue.FullName, 2)).ConfigureAwait(false);
     
     public static async Task SetAsWallpaperFitted() =>
-        await Task.Run(() => Vm.PlatformService.SetAsWallpaper(Vm.PicViewer.FileInfo.FullName, 3)).ConfigureAwait(false);
+        await Task.Run(() => Vm.PlatformService.SetAsWallpaper(Vm.PicViewer.FileInfo.CurrentValue.FullName, 3)).ConfigureAwait(false);
     
     public static async Task SetAsWallpaperFilled() =>
-        await Task.Run(() => Vm.PlatformService.SetAsWallpaper(Vm.PicViewer.FileInfo.FullName, 4)).ConfigureAwait(false);
+        await Task.Run(() => Vm.PlatformService.SetAsWallpaper(Vm.PicViewer.FileInfo.CurrentValue.FullName, 4)).ConfigureAwait(false);
     
     public static async Task SetAsLockscreenCentered() =>
-        await Task.Run(() => Vm.PlatformService.SetAsLockScreen(Vm.PicViewer.FileInfo.FullName)).ConfigureAwait(false);
+        await Task.Run(() => Vm.PlatformService.SetAsLockScreen(Vm.PicViewer.FileInfo.CurrentValue.FullName)).ConfigureAwait(false);
     
     public static async Task SetAsLockScreen() =>
-        await Task.Run(() => Vm.PlatformService.SetAsLockScreen(Vm.PicViewer.FileInfo.FullName)).ConfigureAwait(false);
+        await Task.Run(() => Vm.PlatformService.SetAsLockScreen(Vm.PicViewer.FileInfo.CurrentValue.FullName)).ConfigureAwait(false);
 
     #endregion
 
@@ -834,9 +834,9 @@ public static class FunctionsMapper
         var getFromArgs = false;
         if (Vm?.PicViewer.FileInfo is not null)
         {
-            if (Vm.PicViewer.FileInfo.Exists)
+            if (Vm.PicViewer.FileInfo.CurrentValue.Exists)
             {
-                openFile = Vm.PicViewer.FileInfo.FullName;
+                openFile = Vm.PicViewer.FileInfo.CurrentValue.FullName;
             }
             else
             {

+ 5 - 5
src/PicView.Avalonia/ImageHandling/ImageFormatConverter.cs

@@ -28,7 +28,7 @@ public static class ImageFormatConverter
         // Primary case: Handle effect applied or empty path by saving current ImageSource
         if (vm.PicViewer.EffectConfig is not null || string.IsNullOrWhiteSpace(path))
         {
-            if (vm.PicViewer.ImageSource is Bitmap bmp)
+            if (vm.PicViewer.ImageSource.CurrentValue is Bitmap bmp)
             {
                 source = bmp;
             }
@@ -36,15 +36,15 @@ public static class ImageFormatConverter
         else if (NavigationManager.CanNavigate(vm) && !string.IsNullOrEmpty(path))
         {
             // Handle effects for the current file
-            if (vm.PicViewer.EffectConfig is not null && vm.PicViewer.FileInfo?.FullName == path)
+            if (vm.PicViewer.EffectConfig is not null && vm.PicViewer.FileInfo?.CurrentValue.FullName == path)
             {
-                if (vm.PicViewer.ImageSource is Bitmap bmp)
+                if (vm.PicViewer.ImageSource.CurrentValue is Bitmap bmp)
                 {
                     source = bmp;
                 }
             }
             // Current path that's already in common format
-            else if (path == vm.PicViewer.FileInfo?.FullName)
+            else if (path == vm.PicViewer.FileInfo?.CurrentValue.FullName)
             {
                 if (path.IsCommon())
                 {
@@ -52,7 +52,7 @@ public static class ImageFormatConverter
                     return path;
                 }
 
-                if (vm.PicViewer.ImageSource is Bitmap bmp)
+                if (vm.PicViewer.ImageSource.CurrentValue is Bitmap bmp)
                 {
                     source = bmp;
                 }

+ 1 - 1
src/PicView.Avalonia/ImageHandling/ImageOptimizer.cs

@@ -29,7 +29,7 @@ public static class ImageOptimizer
                 {
                     OptimalCompression = true
                 };
-                optimizer.LosslessCompress(vm.PicViewer.FileInfo.FullName);
+                optimizer.LosslessCompress(vm.PicViewer.FileInfo.CurrentValue.FullName);
             }
             catch (Exception ex)
             {

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

@@ -122,14 +122,14 @@ public static class RotationNavigation
 
     public static void Flip(MainViewModel vm)
     {
-        if (vm.PicViewer.ScaleX == 1)
+        if (vm.PicViewer.ScaleX.CurrentValue == 1)
         {
-            vm.PicViewer.ScaleX = -1;
+            vm.PicViewer.ScaleX.Value = -1;
             vm.Translation.IsFlipped = vm.Translation.UnFlip;
         }
         else
         {
-            vm.PicViewer.ScaleX = 1;
+            vm.PicViewer.ScaleX.Value = 1;
             vm.Translation.IsFlipped = vm.Translation.Flip;
         }
 

+ 5 - 5
src/PicView.Avalonia/ImageTransformations/Rotation/RotationTransformer.cs

@@ -75,7 +75,7 @@ public class RotationTransformer(
 
         if (animate)
         {
-            var flipTransform = new ScaleTransform(prevScaleX, 1)
+            var flipTransform = new ScaleTransform(prevScaleX.CurrentValue, 1)
             {
                 Transitions =
                 [
@@ -84,11 +84,11 @@ public class RotationTransformer(
                 ]
             };
             imageLayoutTransformControl.RenderTransform = flipTransform;
-            flipTransform.ScaleX = vm.PicViewer.ScaleX;
+            flipTransform.ScaleX = vm.PicViewer.ScaleX.CurrentValue;
         }
         else
         {
-            imageLayoutTransformControl.RenderTransform = new ScaleTransform(vm.PicViewer.ScaleX, 1);
+            imageLayoutTransformControl.RenderTransform = new ScaleTransform(vm.PicViewer.ScaleX.CurrentValue, 1);
         }
     }
 
@@ -99,9 +99,9 @@ public class RotationTransformer(
             return;
         }
 
-        vm.PicViewer.ScaleX = scaleX;
+        vm.PicViewer.ScaleX.Value = scaleX;
         vm.RotationAngle = rotationAngle;
-        imageLayoutTransformControl.RenderTransform = new ScaleTransform(vm.PicViewer.ScaleX, 1);
+        imageLayoutTransformControl.RenderTransform = new ScaleTransform(vm.PicViewer.ScaleX.CurrentValue, 1);
         imageLayoutTransformControl.LayoutTransform = new RotateTransform(rotationAngle);
 
         resetZoom?.Invoke();

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

@@ -99,7 +99,7 @@ public static class ErrorHandling
         {
             if (!NavigationManager.CanNavigate(vm))
             {
-                var url = vm.PicViewer.Title.GetURL();
+                var url = vm.PicViewer.Title.CurrentValue.GetURL();
                 if (!string.IsNullOrEmpty(url))
                 {
                     await NavigationManager.LoadPicFromUrlAsync(url, vm).ConfigureAwait(false);

+ 9 - 9
src/PicView.Avalonia/Navigation/ExifHandling.cs

@@ -13,7 +13,7 @@ public static class ExifHandling
 {
     public static void UpdateExifValues(MainViewModel vm)
     {
-        if (vm.PicViewer?.FileInfo is null || vm.PicViewer is { PixelWidth: <= 0, PixelHeight: <= 0 })
+        if (vm.PicViewer?.FileInfo is null || vm.PicViewer is { PixelWidth.CurrentValue: <= 0, PixelHeight.CurrentValue: <= 0 })
         {
             return;
         }
@@ -21,11 +21,11 @@ public static class ExifHandling
         
         try
         {
-            if (!vm.PicViewer.FileInfo.Exists)
+            if (!vm.PicViewer.FileInfo.CurrentValue.Exists)
             {
                 return;
             }
-            magick.Ping(vm.PicViewer.FileInfo);
+            magick.Ping(vm.PicViewer.FileInfo.CurrentValue);
             var profile = magick.GetExifProfile();
 
             if (profile != null)
@@ -44,16 +44,16 @@ public static class ExifHandling
                 }
             }
 
-            if (vm.Exif.DpiX is 0 && vm.PicViewer.ImageType is ImageType.Bitmap or ImageType.AnimatedGif or ImageType.AnimatedWebp)
+            if (vm.Exif.DpiX is 0 && vm.PicViewer.ImageType.CurrentValue is ImageType.Bitmap or ImageType.AnimatedGif or ImageType.AnimatedWebp)
             {
-                if (vm.PicViewer.ImageSource is Bitmap bmp)
+                if (vm.PicViewer.ImageSource.CurrentValue is Bitmap bmp)
                 {
                     vm.Exif.DpiX = bmp?.Dpi.X ?? 0;
                     vm.Exif.DpiY = bmp?.Dpi.Y ?? 0;
                 }
             }
 
-            vm.Exif.Orientation = vm.PicViewer.ExifOrientation switch
+            vm.Exif.Orientation = vm.PicViewer.ExifOrientation.CurrentValue switch
             {
                 EXIFHelper.EXIFOrientation.Horizontal => TranslationManager.Translation.Normal,
                 EXIFHelper.EXIFOrientation.MirrorHorizontal => TranslationManager.Translation.Flipped,
@@ -82,7 +82,7 @@ public static class ExifHandling
             }
             else 
             {
-                var printSizes = AspectRatioHelper.GetPrintSizes( vm.PicViewer.PixelWidth, vm.PicViewer.PixelHeight, vm.Exif.DpiX, vm.Exif.DpiY);
+                var printSizes = AspectRatioHelper.GetPrintSizes( vm.PicViewer.PixelWidth.CurrentValue, vm.PicViewer.PixelHeight.CurrentValue, vm.Exif.DpiX, vm.Exif.DpiY);
 
                 vm.Exif.PrintSizeCm = printSizes.PrintSizeCm;
                 vm.Exif.PrintSizeInch = printSizes.PrintSizeInch;
@@ -91,10 +91,10 @@ public static class ExifHandling
                 vm.Exif.Resolution = $"{vm.Exif.DpiX} x {vm.Exif.DpiY} {TranslationManager.Translation.Dpi}";
             }
 
-            var gcd = ImageTitleFormatter.GCD(vm.PicViewer.PixelWidth, vm.PicViewer.PixelHeight);
+            var gcd = ImageTitleFormatter.GCD(vm.PicViewer.PixelWidth.CurrentValue, vm.PicViewer.PixelHeight.CurrentValue);
             if (gcd != 0) // Check for zero before division
             {
-                vm.Exif.AspectRatio = AspectRatioHelper.GetFormattedAspectRatio(gcd, vm.PicViewer.PixelWidth, vm.PicViewer.PixelHeight);
+                vm.Exif.AspectRatio = AspectRatioHelper.GetFormattedAspectRatio(gcd, vm.PicViewer.PixelWidth.CurrentValue, vm.PicViewer.PixelHeight.CurrentValue);
             }
             else
             {

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

@@ -63,13 +63,13 @@ public static class FileListManager
         {
             try
             {
-                var files = platformSpecificService.GetFiles(vm.PicViewer.FileInfo);
+                var files = platformSpecificService.GetFiles(vm.PicViewer.FileInfo.CurrentValue);
                 if (files is not { Count: > 0 })
                 {
                     return false;
                 }
 
-                NavigationManager.UpdateFileListAndIndex(files, files.IndexOf(vm.PicViewer.FileInfo));
+                NavigationManager.UpdateFileListAndIndex(files, files.IndexOf(vm.PicViewer.FileInfo.CurrentValue));
                 TitleManager.SetTitle(vm);
                 return true;
             }
@@ -89,7 +89,7 @@ public static class FileListManager
 
         if (!_cancellationTokenSource.IsCancellationRequested)
         {
-            await GalleryLoad.ReloadGalleryAsync(vm, vm.PicViewer.FileInfo.DirectoryName);
+            await GalleryLoad.ReloadGalleryAsync(vm, vm.PicViewer.FileInfo.CurrentValue.DirectoryName);
         }
     }
 }

+ 10 - 10
src/PicView.Avalonia/Navigation/ImageIterator.cs

@@ -283,7 +283,7 @@ public class ImageIterator : IAsyncDisposable
                 PreLoader.Resynchronize(ImagePaths);
                 var newIndex = GetIteration(index, NavigateTo.Previous);
                 CurrentIndex = newIndex;
-                _vm.PicViewer.FileInfo = ImagePaths[CurrentIndex];
+                _vm.PicViewer.FileInfo.Value = ImagePaths[CurrentIndex];
                 await IterateToIndex(CurrentIndex, new CancellationTokenSource());
             }
             else
@@ -303,7 +303,7 @@ public class ImageIterator : IAsyncDisposable
                     }
                 }
 
-                var indexOf = ImagePaths.FindIndex(x => x.FullName.Equals(_vm.PicViewer.FileInfo.FullName));
+                var indexOf = ImagePaths.FindIndex(x => x.FullName.Equals(_vm.PicViewer.FileInfo.CurrentValue.FullName));
                 _vm.SelectedGalleryItemIndex = indexOf; // Fixes deselection bug 
                 CurrentIndex = indexOf;
                 if (isSameFile)
@@ -369,7 +369,7 @@ public class ImageIterator : IAsyncDisposable
 
             if (sameFile)
             {
-                _vm.PicViewer.FileInfo = newFileInfo;
+                _vm.PicViewer.FileInfo.Value = newFileInfo;
                 CurrentIndex = newIndex;
             }
 
@@ -449,12 +449,12 @@ public class ImageIterator : IAsyncDisposable
 
     public PreLoadValue? GetCurrentPreLoadValue() =>
         _isRunning
-            ? PreLoader.Get(_vm.PicViewer.FileInfo, ImagePaths)
+            ? PreLoader.Get(_vm.PicViewer.FileInfo.CurrentValue, ImagePaths)
             : PreLoader.Get(CurrentIndex, ImagePaths);
 
     public async Task<PreLoadValue?> GetCurrentPreLoadValueAsync() =>
         _isRunning
-            ? await PreLoader.GetOrLoadAsync(_vm.PicViewer.FileInfo, ImagePaths)
+            ? await PreLoader.GetOrLoadAsync(_vm.PicViewer.FileInfo.CurrentValue, ImagePaths)
             : await PreLoader.GetOrLoadAsync(CurrentIndex, ImagePaths);
 
     public PreLoadValue? GetNextPreLoadValue()
@@ -487,11 +487,11 @@ public class ImageIterator : IAsyncDisposable
         try
         {
             _isRunning = true;
-            var fileList = await Task.FromResult(_vm.PlatformService.GetFiles(_vm.PicViewer.FileInfo))
+            var fileList = await Task.FromResult(_vm.PlatformService.GetFiles(_vm.PicViewer.FileInfo.CurrentValue))
                 .ConfigureAwait(false);
             var oldList = ImagePaths;
             ImagePaths = fileList;
-            CurrentIndex = ImagePaths.FindIndex(x => x.FullName.Equals(_vm.PicViewer.FileInfo.FullName));
+            CurrentIndex = ImagePaths.FindIndex(x => x.FullName.Equals(_vm.PicViewer.FileInfo.CurrentValue.FullName));
             TitleManager.SetTitle(_vm);
             await ClearAsync().ConfigureAwait(false);
             await PreloadAsync().ConfigureAwait(false);
@@ -815,7 +815,7 @@ public class ImageIterator : IAsyncDisposable
             {
                 if (thumb is not null)
                 {
-                    _vm.PicViewer.ImageSource = thumb;
+                    _vm.PicViewer.ImageSource.Value = thumb;
                 }
             }
             else
@@ -826,8 +826,8 @@ public class ImageIterator : IAsyncDisposable
                     return;
                 }
 
-                _vm.PicViewer.ImageSource = thumb;
-                _vm.PicViewer.SecondaryImageSource = secondaryThumb;
+                _vm.PicViewer.ImageSource.Value = thumb;
+                _vm.PicViewer.SecondaryImageSource.Value = secondaryThumb;
                 _vm.IsLoading = thumb is null || secondaryThumb is null;
             }
         }

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

@@ -333,9 +333,9 @@ public static class ImageLoader
                 var displayProgress = HttpManager.GetProgressDisplay(totalFileSize, totalBytesDownloaded,
                     progressPercentage);
                 var title = $"{fileName} {TranslationManager.Translation.Downloading} {displayProgress}";
-                vm.PicViewer.Title = title;
-                vm.PicViewer.TitleTooltip = title;
-                vm.PicViewer.WindowTitle = title;
+                vm.PicViewer.Title.Value = title;
+                vm.PicViewer.TitleTooltip.Value = title;
+                vm.PicViewer.WindowTitle.Value = title;
                 if (Settings.UIProperties.IsTaskbarProgressEnabled)
                 {
                     vm.PlatformService.SetTaskbarProgress((ulong)totalBytesDownloaded, (ulong)totalFileSize);
@@ -369,8 +369,8 @@ public static class ImageLoader
         await UpdateImage.SetSingleImageAsync(imageModel.Image, imageModel.ImageType, url, vm);
 
         vm.IsLoading = false;
-        vm.PicViewer.FileInfo = fileInfo;
-        vm.PicViewer.ExifOrientation = imageModel.EXIFOrientation;
+        vm.PicViewer.FileInfo.Value = fileInfo;
+        vm.PicViewer.ExifOrientation.Value = imageModel.EXIFOrientation;
         FileHistoryManager.Add(url);
 
         await NavigationManager.DisposeImageIteratorAsync();
@@ -391,8 +391,8 @@ public static class ImageLoader
     {
         TitleManager.SetLoadingTitle(vm);
         vm.IsLoading = true;
-        vm.PicViewer.ImageSource = null;
-        vm.PicViewer.FileInfo = null;
+        vm.PicViewer.ImageSource.Value = null;
+        vm.PicViewer.FileInfo.Value = null;
 
         if (_cancellationTokenSource is not null)
         {

+ 8 - 8
src/PicView.Avalonia/Navigation/NavigationManager.cs

@@ -45,8 +45,8 @@ public static class NavigationManager
     {
         var imageModel = await GetImageModel.GetImageModelAsync(fileInfo).ConfigureAwait(false);
         ImageModel? nextImageModel = null;
-        vm.PicViewer.ImageSource = imageModel.Image;
-        vm.PicViewer.ImageType = imageModel.ImageType;
+        vm.PicViewer.ImageSource.Value = imageModel.Image;
+        vm.PicViewer.ImageType.Value = imageModel.ImageType;
         if (!Settings.ImageScaling.ShowImageSideBySide)
         {
             await Dispatcher.UIThread.InvokeAsync(() =>
@@ -72,7 +72,7 @@ public static class NavigationManager
         if (Settings.ImageScaling.ShowImageSideBySide)
         {
             nextImageModel = (await ImageIterator.GetNextPreLoadValueAsync()).ImageModel;
-            vm.PicViewer.SecondaryImageSource = nextImageModel.Image;
+            vm.PicViewer.SecondaryImageSource.Value = nextImageModel.Image;
             await Dispatcher.UIThread.InvokeAsync(() =>
             {
                 WindowResizing.SetSize(imageModel.PixelWidth, imageModel.PixelHeight, nextImageModel.PixelWidth,
@@ -145,7 +145,7 @@ public static class NavigationManager
             if (vm.PicViewer.FileInfo is null && ImageIterator is not null)
             {
                 // Fixes issue that shouldn't happen. Should investigate.
-                vm.PicViewer.FileInfo = ImageIterator.ImagePaths[0];
+                vm.PicViewer.FileInfo.Value = ImageIterator.ImagePaths[0];
             }
             else
             {
@@ -167,7 +167,7 @@ public static class NavigationManager
             if (vm.PicViewer.FileInfo is not null)
             {
                 var newIndex =
-                    ImageIterator.ImagePaths.FindIndex(x => x.FullName.Equals(vm.PicViewer.FileInfo.FullName));
+                    ImageIterator.ImagePaths.FindIndex(x => x.FullName.Equals(vm.PicViewer.FileInfo.CurrentValue.FullName));
                 if (newIndex == -1)
                 {
                     ErrorHandling.ShowStartUpMenu(vm);
@@ -248,7 +248,7 @@ public static class NavigationManager
             else
             {
                 await UpdateImage.SetTiffImageAsync(TiffNavigationInfo, ImageIterator.CurrentIndex,
-                    vm.PicViewer.FileInfo, vm);
+                    vm.PicViewer.FileInfo.CurrentValue, vm);
             }
         }
 
@@ -446,7 +446,7 @@ public static class NavigationManager
             {
                 vm.PlatformService.StopTaskbarProgress();
                 await LoadWithoutImageIterator(fileList[0], vm, fileList);
-                if (vm.PicViewer.Title == TranslationManager.Translation.Loading)
+                if (vm.PicViewer.Title.CurrentValue == TranslationManager.Translation.Loading)
                 {
                     TitleManager.SetTitle(vm);
                 }
@@ -570,7 +570,7 @@ public static class NavigationManager
 
     public static void InitializeImageIterator(MainViewModel vm)
     {
-        ImageIterator ??= new ImageIterator(vm.PicViewer.FileInfo, vm);
+        ImageIterator ??= new ImageIterator(vm.PicViewer.FileInfo.CurrentValue, vm);
     }
 
     public static async Task DisposeImageIteratorAsync()

+ 23 - 23
src/PicView.Avalonia/Navigation/UpdateImage.cs

@@ -83,10 +83,10 @@ public static class UpdateImage
 
             if (Settings.ImageScaling.ShowImageSideBySide && nextPreloadValue is { ImageModel: not null })
             {
-                vm.PicViewer.SecondaryImageSource = nextPreloadValue.ImageModel.Image;
+                vm.PicViewer.SecondaryImageSource.Value = nextPreloadValue.ImageModel.Image;
                 if (preLoadValue is { ImageModel: not null})
                 {
-                    vm.PicViewer.ImageSource = preLoadValue.ImageModel.Image;
+                    vm.PicViewer.ImageSource.Value = preLoadValue.ImageModel.Image;
                 }
             }
             else if (preLoadValue is { ImageModel: not null})
@@ -96,9 +96,9 @@ public static class UpdateImage
                     vm.ImageViewer.MainImage.InitialAnimatedSource = preLoadValue.ImageModel.FileInfo.FullName;
                 }
                 
-                vm.PicViewer.ImageSource = preLoadValue.ImageModel.Image;
-                vm.PicViewer.SecondaryImageSource = null;
-                vm.PicViewer.ImageType = preLoadValue.ImageModel.ImageType;
+                vm.PicViewer.ImageSource.Value = preLoadValue.ImageModel.Image;
+                vm.PicViewer.SecondaryImageSource.Value = null;
+                vm.PicViewer.ImageType.Value = preLoadValue.ImageModel.ImageType;
             }
             else
             {
@@ -190,9 +190,9 @@ public static class UpdateImage
         MainViewModel vm)
     {
         var source = await Task.Run( () => tiffNavigationInfo.Pages[tiffNavigationInfo.CurrentPage].ToWriteableBitmap()).ConfigureAwait(false);
-        vm.PicViewer.ImageSource = source;
-        vm.PicViewer.SecondaryImageSource = null;
-        vm.PicViewer.ImageType = ImageType.Bitmap;
+        vm.PicViewer.ImageSource.Value = source;
+        vm.PicViewer.SecondaryImageSource.Value = null;
+        vm.PicViewer.ImageType.Value = ImageType.Bitmap;
         var width = source?.PixelSize.Width ?? 0;
         var height = source?.PixelSize.Height ?? 0;
         
@@ -250,21 +250,21 @@ public static class UpdateImage
             var path = source as string;
             using var magickImage = new MagickImage();
             magickImage.Ping(path);
-            vm.PicViewer.ImageSource = source;
-            vm.PicViewer.ImageType = ImageType.Svg;
+            vm.PicViewer.ImageSource.Value = source;
+            vm.PicViewer.ImageType.Value = ImageType.Svg;
             width = (int)magickImage.Width;
             height = (int)magickImage.Height;
         }
         else
         {
             var bitmap = source as Bitmap;
-            vm.PicViewer.ImageSource = source;
-            vm.PicViewer.ImageType = imageType == ImageType.Invalid ? ImageType.Bitmap : imageType;
+            vm.PicViewer.ImageSource.Value = source;
+            vm.PicViewer.ImageType.Value = imageType == ImageType.Invalid ? ImageType.Bitmap : imageType;
             width = bitmap?.PixelSize.Width ?? 0;
             height = bitmap?.PixelSize.Height ?? 0;
         }
 
-        vm.PicViewer.FileInfo = null;
+        vm.PicViewer.FileInfo.Value = null;
 
         await Dispatcher.UIThread.InvokeAsync(() =>
         {
@@ -276,14 +276,14 @@ public static class UpdateImage
         }, DispatcherPriority.Send);
 
         var singeImageWindowTitles = ImageTitleFormatter.GenerateTitleForSingleImage(width, height, name, 1);
-        vm.PicViewer.WindowTitle = singeImageWindowTitles.TitleWithAppName;
-        vm.PicViewer.Title = singeImageWindowTitles.BaseTitle;
-        vm.PicViewer.TitleTooltip = singeImageWindowTitles.BaseTitle;
+        vm.PicViewer.WindowTitle.Value = singeImageWindowTitles.TitleWithAppName;
+        vm.PicViewer.Title.Value = singeImageWindowTitles.BaseTitle;
+        vm.PicViewer.TitleTooltip.Value = singeImageWindowTitles.BaseTitle;
 
         vm.PlatformService.StopTaskbarProgress();
 
-        vm.PicViewer.PixelWidth = width;
-        vm.PicViewer.PixelHeight = height;
+        vm.PicViewer.PixelWidth.Value = width;
+        vm.PicViewer.PixelHeight.Value = height;
 
         if (Settings.Gallery.IsBottomGalleryShown)
         {
@@ -303,11 +303,11 @@ public static class UpdateImage
     public static void SetStats(MainViewModel vm, ImageModel imageModel)
     {
         vm.IsSingleImage = false;
-        vm.PicViewer.PixelWidth = imageModel.PixelWidth;
-        vm.PicViewer.PixelHeight = imageModel.PixelHeight;
+        vm.PicViewer.PixelWidth.Value = imageModel.PixelWidth;
+        vm.PicViewer.PixelHeight.Value = imageModel.PixelHeight;
         vm.GetIndex = NavigationManager.GetNonZeroIndex;
-        vm.PicViewer.ExifOrientation = imageModel.EXIFOrientation;
-        vm.PicViewer.FileInfo = imageModel.FileInfo;
+        vm.PicViewer.ExifOrientation.Value = imageModel.EXIFOrientation;
+        vm.PicViewer.FileInfo.Value = imageModel.FileInfo;
         vm.ZoomValue = 1;
 
         if (Settings.ImageScaling.ShowImageSideBySide)
@@ -318,7 +318,7 @@ public static class UpdateImage
         }
 
         // Reset effects
-        vm.PicViewer.EffectConfig = null;
+        vm.PicViewer.EffectConfig.Value = null;
     }
 
     #endregion

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

@@ -130,6 +130,7 @@
     <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
     <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.3.2" />
     <PackageReference Include="Avalonia.Svg.Skia" Version="11.2.7" />
+    <PackageReference Include="R3Extensions.Avalonia" Version="1.3.0" />
   </ItemGroup>
 
   <ItemGroup>

+ 2 - 2
src/PicView.Avalonia/Resizing/AspectRatioHelper.cs

@@ -14,8 +14,8 @@ public static class AspectRatioHelper
         var percentage = isWidth ? widthTextBox.Text.GetPercentage() : heightTextBox.Text.GetPercentage();
         if (percentage > 0)
         {
-            var newWidth = Convert.ToUInt32(vm.PicViewer.PixelWidth * (percentage / 100));
-            var newHeight = Convert.ToUInt32(vm.PicViewer.PixelHeight * (percentage / 100));
+            var newWidth = Convert.ToUInt32(vm.PicViewer.PixelWidth.CurrentValue * (percentage / 100));
+            var newHeight = Convert.ToUInt32(vm.PicViewer.PixelHeight.CurrentValue * (percentage / 100));
 
             widthTextBox.Text = newWidth.ToString("# ", CultureInfo.CurrentCulture);
             heightTextBox.Text = newHeight.ToString("# ", CultureInfo.CurrentCulture);

+ 1 - 1
src/PicView.Avalonia/SettingsManagement/LanguageUpdater.cs

@@ -18,7 +18,7 @@ public static class LanguageUpdater
 
         translationViewModel.UpdateLanguage();
 
-        translationViewModel.IsFlipped = picViewerModel.ScaleX == 1 ? translationViewModel.Flip : translationViewModel.UnFlip;
+        translationViewModel.IsFlipped = picViewerModel.ScaleX.CurrentValue == 1 ? translationViewModel.Flip : translationViewModel.UnFlip;
         
         translationViewModel.IsShowingUI = !Settings.UIProperties.ShowInterface ? translationViewModel.ShowUI : translationViewModel.HideUI;
         

+ 12 - 12
src/PicView.Avalonia/SettingsManagement/SettingsUpdater.cs

@@ -56,7 +56,7 @@ public static class SettingsUpdater
     }
 
     public static void InitializeSettings(MainViewModel vm)
-    {    
+    {
         // Set corner radius on macOS
         if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
         {
@@ -74,7 +74,7 @@ public static class SettingsUpdater
         vm.GetNavSpeed = Settings.UIProperties.NavSpeed;
         vm.GetSlideshowSpeed = Settings.UIProperties.SlideShowTimer;
         vm.GetZoomSpeed = Settings.Zoom.ZoomSpeed;
-        vm.PicViewer.IsShowingSideBySide = Settings.ImageScaling.ShowImageSideBySide;
+        vm.PicViewer.IsShowingSideBySide.Value = Settings.ImageScaling.ShowImageSideBySide;
         vm.IsBottomGalleryShown = Settings.Gallery.IsBottomGalleryShown;
         vm.IsBottomGalleryShownInHiddenUI = Settings.Gallery.ShowBottomGalleryInHiddenUI;
         vm.IsAvoidingZoomingOut  = Settings.Zoom.AvoidZoomingOut;
@@ -312,8 +312,8 @@ public static class SettingsUpdater
     public static void TurnOffSideBySide(MainViewModel vm)
     {
         Settings.ImageScaling.ShowImageSideBySide = false;
-        vm.PicViewer.IsShowingSideBySide = false;
-        vm.PicViewer.SecondaryImageSource = null;
+        vm.PicViewer.IsShowingSideBySide.Value = false;
+        vm.PicViewer.SecondaryImageSource.Value = null;
         WindowResizing.SetSize(vm);
         TitleManager.SetTitle(vm);
     }
@@ -321,7 +321,7 @@ public static class SettingsUpdater
     public static async Task TurnOnSideBySide(MainViewModel vm)
     {
         Settings.ImageScaling.ShowImageSideBySide = true;
-        vm.PicViewer.IsShowingSideBySide = true;
+        vm.PicViewer.IsShowingSideBySide.Value = true;
         if (NavigationManager.CanNavigate(vm))
         {
             var preloadValue = await NavigationManager.GetNextPreLoadValueAsync();
@@ -332,15 +332,15 @@ public static class SettingsUpdater
 #endif
                 return;
             }
-            vm.PicViewer.SecondaryImageSource = preloadValue.ImageModel.Image;
+            vm.PicViewer.SecondaryImageSource.Value = preloadValue.ImageModel.Image;
             var imageModel1 = new ImageModel
             {
-                FileInfo = vm.PicViewer.FileInfo,
-                PixelWidth = (int)vm.PicViewer.ImageWidth,
-                PixelHeight = (int)vm.PicViewer.ImageHeight,
-                ImageType = vm.PicViewer.ImageType,
+                FileInfo = vm.PicViewer.FileInfo.CurrentValue,
+                PixelWidth = (int)vm.PicViewer.ImageWidth.CurrentValue,
+                PixelHeight = (int)vm.PicViewer.ImageHeight.CurrentValue,
+                ImageType = vm.PicViewer.ImageType.CurrentValue,
                 Image = vm.PicViewer.ImageSource,
-                EXIFOrientation = vm.PicViewer.ExifOrientation
+                EXIFOrientation = vm.PicViewer.ExifOrientation.CurrentValue
             };
             var imageModel2 = new ImageModel
             {
@@ -353,7 +353,7 @@ public static class SettingsUpdater
             };
             await Dispatcher.UIThread.InvokeAsync(() =>
             {
-                WindowResizing.SetSize(vm.PicViewer.ImageWidth, vm.PicViewer.ImageHeight, preloadValue.ImageModel.PixelWidth,
+                WindowResizing.SetSize(vm.PicViewer.ImageWidth.CurrentValue, vm.PicViewer.ImageHeight.CurrentValue, preloadValue.ImageModel.PixelWidth,
                     preloadValue.ImageModel.PixelHeight, vm.RotationAngle, vm);
                 TitleManager.SetSideBySideTitle(vm, imageModel1, imageModel2);
             });

+ 7 - 7
src/PicView.Avalonia/StartUp/QuickLoad.cs

@@ -49,7 +49,7 @@ public static class QuickLoad
 
         var magickImage = new MagickImage();
         magickImage.Ping(fileInfo);
-        vm.PicViewer.FileInfo = fileInfo;
+        vm.PicViewer.FileInfo.Value = fileInfo;
         var isLargeImage = magickImage.Width * magickImage.Height > 5000000; // ~5 megapixels threshold
         if (isLargeImage || Settings.ImageScaling.ShowImageSideBySide)
         {
@@ -144,7 +144,7 @@ public static class QuickLoad
         NavigationManager.InitializeImageIterator(vm);
         var imageModel = await GetImageModel.GetImageModelAsync(fileInfo, magickImage);
         var secondaryPreloadValue = await NavigationManager.GetNextPreLoadValueAsync();
-        vm.PicViewer.SecondaryImageSource = secondaryPreloadValue?.ImageModel?.Image;
+        vm.PicViewer.SecondaryImageSource.Value = secondaryPreloadValue?.ImageModel?.Image;
         SetPicViewerValues(vm, imageModel, fileInfo);
         await RenderingFixes(vm, imageModel, secondaryPreloadValue.ImageModel);
         TitleManager.SetSideBySideTitle(vm, imageModel, secondaryPreloadValue?.ImageModel);
@@ -169,13 +169,13 @@ public static class QuickLoad
             vm.ImageViewer.MainImage.InitialAnimatedSource = fileInfo.FullName;
         }
         
-        vm.PicViewer.ImageSource = imageModel.Image;
-        vm.PicViewer.ImageType = imageModel.ImageType;
+        vm.PicViewer.ImageSource.Value = imageModel.Image;
+        vm.PicViewer.ImageType.Value = imageModel.ImageType;
         vm.ZoomValue = 1;
-        vm.PicViewer.PixelWidth = imageModel.PixelWidth;
-        vm.PicViewer.PixelHeight = imageModel.PixelHeight;
+        vm.PicViewer.PixelWidth.Value = imageModel.PixelWidth;
+        vm.PicViewer.PixelHeight.Value = imageModel.PixelHeight;
 
-        vm.PicViewer.ExifOrientation = imageModel.EXIFOrientation;
+        vm.PicViewer.ExifOrientation.Value = imageModel.EXIFOrientation;
     }
 
     /// <summary>

+ 1 - 1
src/PicView.Avalonia/UI/HideInterfaceLogic.cs

@@ -71,7 +71,7 @@ public static class HideInterfaceLogic
                                 GalleryFunctions.OpenBottomGallery(vm);
                             }
                         });
-                        _ = GalleryLoad.LoadGallery(vm, vm.PicViewer.FileInfo.DirectoryName);
+                        _ = GalleryLoad.LoadGallery(vm, vm.PicViewer.FileInfo.CurrentValue.DirectoryName);
                     }
 
                     vm.IsBottomGalleryShown = true;

+ 42 - 39
src/PicView.Avalonia/UI/TitleManager.cs

@@ -19,10 +19,14 @@ public static class TitleManager
     /// <remarks>Can be used to refresh the title when files are added or removed.</remarks>
     public static void SetTitle(MainViewModel vm)
     {
+        var pWidth = vm.PicViewer.PixelWidth.Value;
+        var pHeight = vm.PicViewer.PixelHeight.Value;
+        var fileInfo = vm.PicViewer.FileInfo.Value;
+        
         if (!NavigationManager.CanNavigate(vm))
         {
             string title;
-            var s = vm.PicViewer.WindowTitle;
+            var s = vm.PicViewer.WindowTitle.Value;
             var url = s.GetURL();
             if (!string.IsNullOrWhiteSpace(url))
             {
@@ -38,17 +42,17 @@ public static class TitleManager
             }
 
             var singleImageWindowTitles =
-                ImageTitleFormatter.GenerateTitleForSingleImage(vm.PicViewer.PixelWidth, vm.PicViewer.PixelWidth, title, vm.ZoomValue);
-            vm.PicViewer.WindowTitle = singleImageWindowTitles.BaseTitle;
-            vm.PicViewer.Title = singleImageWindowTitles.TitleWithAppName;
-            vm.PicViewer.TitleTooltip = singleImageWindowTitles.TitleWithAppName;
+                ImageTitleFormatter.GenerateTitleForSingleImage(pWidth, pHeight, title, vm.ZoomValue);
+            vm.PicViewer.WindowTitle.Value = singleImageWindowTitles.BaseTitle;
+            vm.PicViewer.Title.Value = singleImageWindowTitles.TitleWithAppName;
+            vm.PicViewer.TitleTooltip.Value = singleImageWindowTitles.TitleWithAppName;
             return;
         }
 
         if (NavigationManager.TiffNavigationInfo is not null)
         {
-            SetTiffTitle(NavigationManager.TiffNavigationInfo, vm.PicViewer.PixelWidth, vm.PicViewer.PixelHeight,
-                NavigationManager.GetCurrentIndex, vm.PicViewer.FileInfo, vm);
+            SetTiffTitle(NavigationManager.TiffNavigationInfo, pWidth, pHeight,
+                NavigationManager.GetCurrentIndex, vm.PicViewer.FileInfo.Value, vm);
             return;
         }
 
@@ -56,9 +60,9 @@ public static class TitleManager
         {
             var imageModel1 = new ImageModel
             {
-                FileInfo = vm.PicViewer.FileInfo,
-                PixelWidth = vm.PicViewer.PixelWidth,
-                PixelHeight = vm.PicViewer.PixelHeight
+                FileInfo = vm.PicViewer.FileInfo.Value,
+                PixelWidth = vm.PicViewer.PixelWidth.Value,
+                PixelHeight = vm.PicViewer.PixelHeight.Value
             };
             var nextFileName = NavigationManager.GetNextFileName;
             using var magickImage = new MagickImage();
@@ -73,12 +77,12 @@ public static class TitleManager
             return;
         }
 
-        var windowTitles = ImageTitleFormatter.GenerateTitleStrings(vm.PicViewer.PixelWidth, vm.PicViewer.PixelHeight,
+        var windowTitles = ImageTitleFormatter.GenerateTitleStrings(pWidth, pHeight,
             NavigationManager.GetCurrentIndex,
-            vm.PicViewer.FileInfo, vm.ZoomValue, NavigationManager.GetCollection);
-        vm.PicViewer.WindowTitle = windowTitles.TitleWithAppName;
-        vm.PicViewer.Title = windowTitles.BaseTitle;
-        vm.PicViewer.TitleTooltip = windowTitles.FilePathTitle;
+            fileInfo, vm.ZoomValue, NavigationManager.GetCollection);
+        vm.PicViewer.WindowTitle.Value = windowTitles.TitleWithAppName;
+        vm.PicViewer.Title.Value = windowTitles.BaseTitle;
+        vm.PicViewer.TitleTooltip.Value = windowTitles.FilePathTitle;
     }
 
     /// <summary>
@@ -88,9 +92,8 @@ public static class TitleManager
     /// <param name="vm">The main view model instance.</param>
     public static void SetLoadingTitle(MainViewModel vm)
     {
-        vm.PicViewer.WindowTitle = $"{TranslationManager.Translation.Loading} - PicView";
-        vm.PicViewer.Title = TranslationManager.Translation.Loading;
-        vm.PicViewer.TitleTooltip = vm.PicViewer.Title;
+        vm.PicViewer.TitleTooltip.Value = vm.PicViewer.WindowTitle.Value = $"{TranslationManager.Translation.Loading} - PicView";
+        vm.PicViewer.Title.Value = TranslationManager.Translation.Loading;
     }
 
     /// <summary>
@@ -110,18 +113,18 @@ public static class TitleManager
             }
             imageModel = new ImageModel
             {
-                FileInfo = vm.PicViewer.FileInfo,
-                PixelWidth = vm.PicViewer.PixelWidth,
-                PixelHeight = vm.PicViewer.PixelHeight
+                FileInfo = vm.PicViewer.FileInfo.Value,
+                PixelWidth = vm.PicViewer.PixelWidth.Value,
+                PixelHeight = vm.PicViewer.PixelHeight.Value
             };
         }
 
         var windowTitles = ImageTitleFormatter.GenerateTitleStrings(imageModel.PixelWidth, imageModel.PixelHeight,
             NavigationManager.GetCurrentIndex,
             imageModel.FileInfo, vm.ZoomValue, NavigationManager.GetCollection);
-        vm.PicViewer.WindowTitle = windowTitles.TitleWithAppName;
-        vm.PicViewer.Title = windowTitles.BaseTitle;
-        vm.PicViewer.TitleTooltip = windowTitles.FilePathTitle;
+        vm.PicViewer.WindowTitle.Value = windowTitles.TitleWithAppName;
+        vm.PicViewer.Title.Value = windowTitles.BaseTitle;
+        vm.PicViewer.TitleTooltip.Value = windowTitles.FilePathTitle;
     }
 
     /// <summary>
@@ -143,9 +146,9 @@ public static class TitleManager
     {
         var singeImageWindowTitles = ImageTitleFormatter.GenerateTiffTitleStrings(width, height, index, fileInfo,
             tiffNavigationInfo, 1, NavigationManager.GetCollection);
-        vm.PicViewer.WindowTitle = singeImageWindowTitles.TitleWithAppName;
-        vm.PicViewer.Title = singeImageWindowTitles.BaseTitle;
-        vm.PicViewer.TitleTooltip = singeImageWindowTitles.BaseTitle;
+        vm.PicViewer.WindowTitle.Value = singeImageWindowTitles.TitleWithAppName;
+        vm.PicViewer.Title.Value = singeImageWindowTitles.BaseTitle;
+        vm.PicViewer.TitleTooltip.Value = singeImageWindowTitles.BaseTitle;
     }
 
     /// <summary>
@@ -207,9 +210,9 @@ public static class TitleManager
             }
             imageModel1 = new ImageModel
             {
-                FileInfo = vm.PicViewer.FileInfo,
-                PixelWidth = vm.PicViewer.PixelWidth,
-                PixelHeight = vm.PicViewer.PixelHeight
+                FileInfo = vm.PicViewer.FileInfo.Value,
+                PixelWidth = vm.PicViewer.PixelWidth.Value,
+                PixelHeight = vm.PicViewer.PixelHeight.Value
             };
         }
         if (!ValidateImageModel(imageModel2, vm))
@@ -242,9 +245,9 @@ public static class TitleManager
         var windowTitle = $"{firstWindowTitles.BaseTitle} \u21dc || \u21dd {secondWindowTitles.BaseTitle} - PicView";
         var title = $"{firstWindowTitles.BaseTitle} \u21dc || \u21dd  {secondWindowTitles.BaseTitle}";
         var titleTooltip = $"{firstWindowTitles.FilePathTitle} \u21dc || \u21dd  {secondWindowTitles.FilePathTitle}";
-        vm.PicViewer.WindowTitle = windowTitle;
-        vm.PicViewer.Title = title;
-        vm.PicViewer.TitleTooltip = titleTooltip;
+        vm.PicViewer.WindowTitle.Value = windowTitle;
+        vm.PicViewer.Title.Value = title;
+        vm.PicViewer.TitleTooltip.Value = titleTooltip;
     }
 
     /// <summary>
@@ -257,16 +260,16 @@ public static class TitleManager
     /// </remarks>
     public static void SetNoImageTitle(MainViewModel vm)
     {
-        vm.PicViewer.Title = TranslationManager.Translation.NoImage;
-        vm.PicViewer.WindowTitle = TranslationManager.Translation.NoImage + " - PicView";
-        vm.PicViewer.TitleTooltip = TranslationManager.Translation.NoImage;
+        vm.PicViewer.Title.Value = TranslationManager.Translation.NoImage;
+        vm.PicViewer.WindowTitle.Value = TranslationManager.Translation.NoImage + " - PicView";
+        vm.PicViewer.TitleTooltip.Value = TranslationManager.Translation.NoImage;
     }
 
     private static void ReturnError(MainViewModel vm)
     {
-        vm.PicViewer.WindowTitle =
-            vm.PicViewer.Title =
-                vm.PicViewer.TitleTooltip = TranslationManager.GetTranslation("UnableToRender");
+        vm.PicViewer.WindowTitle.Value =
+            vm.PicViewer.Title.Value =
+                vm.PicViewer.TitleTooltip.Value = TranslationManager.GetTranslation("UnableToRender");
     }
 
     private static bool ValidateImageModel(ImageModel? imageModel, MainViewModel vm)

+ 4 - 0
src/PicView.Avalonia/UI/UIHelper.cs

@@ -10,6 +10,7 @@ using PicView.Avalonia.ViewModels;
 using PicView.Avalonia.Views;
 using PicView.Avalonia.Views.UC;
 using PicView.Avalonia.WindowBehavior;
+using R3.Avalonia;
 
 namespace PicView.Avalonia.UI;
 
@@ -26,6 +27,9 @@ public static class UIHelper
     public static GalleryAnimationControlView? GetGalleryView { get; private set; }
     public static BottomBar? GetBottomBar { get; private set; }
     public static ToolTipMessage? GetToolTipMessage { get; private set; }
+    
+    public static AvaloniaRenderingFrameProvider ? GetFrameProvider { get; private set; }
+    public static void SetFrameProvider(AvaloniaRenderingFrameProvider frameProvider) => GetFrameProvider = frameProvider;
 
     /// <summary>
     /// Sets up control references from the main desktop application

+ 3 - 3
src/PicView.Avalonia/ViewModels/ImageCropperViewModel.cs

@@ -126,7 +126,7 @@ public class ImageCropperViewModel : ReactiveObject
         
         CropFunctions.CloseCropControl(vm);
 
-        if (vm.PicViewer.FileInfo.FullName == saveFileDialog)
+        if (vm.PicViewer.FileInfo.CurrentValue.FullName == saveFileDialog)
         {
             await ErrorHandling.ReloadAsync(vm);
         }
@@ -135,7 +135,7 @@ public class ImageCropperViewModel : ReactiveObject
     private async Task CopyCroppedImageAsync()
     {
         if (UIHelper.GetMainView.DataContext is not MainViewModel vm) return;
-        if (vm.PicViewer.ImageSource is not Bitmap sourceBitmap) return;
+        if (vm.PicViewer.ImageSource.CurrentValue is not Bitmap sourceBitmap) return;
 
         var x = Convert.ToInt32(SelectionX / AspectRatio);
         var y = Convert.ToInt32(SelectionY / AspectRatio);
@@ -151,7 +151,7 @@ public class ImageCropperViewModel : ReactiveObject
     }
 
     private (string fileName, FileInfo fileInfo, Bitmap? bitmap) PrepareCropData(MainViewModel vm)
-      => NavigationManager.IsCollectionEmpty ? CreateNewCroppedImage() : (vm.PicViewer.FileInfo.FullName, vm.PicViewer.FileInfo, null);
+      => NavigationManager.IsCollectionEmpty ? CreateNewCroppedImage() : (vm.PicViewer.FileInfo.Value.FullName, vm.PicViewer.FileInfo.Value, null);
 
     private (string fileName, FileInfo fileInfo, Bitmap bitmap) CreateNewCroppedImage()
     {

+ 2 - 2
src/PicView.Avalonia/Views/BatchResizeView.axaml.cs

@@ -117,7 +117,7 @@ public partial class BatchResizeView : UserControl
                 return;
             }
 
-            SourceFolderTextBox.Text = vm.PicViewer.FileInfo?.DirectoryName ?? string.Empty;
+            SourceFolderTextBox.Text = vm.PicViewer.FileInfo?.CurrentValue.DirectoryName ?? string.Empty;
             
             this.WhenAny(x => x.SourceFolderTextBox.Text, x => x.Value)
                 .ObserveOn(RxApp.MainThreadScheduler)
@@ -208,7 +208,7 @@ public partial class BatchResizeView : UserControl
             return;
         }
 
-        SourceFolderTextBox.Text = vm.PicViewer.FileInfo?.DirectoryName ?? string.Empty;
+        SourceFolderTextBox.Text = vm.PicViewer.FileInfo?.CurrentValue.DirectoryName ?? string.Empty;
 
         ResetButton.IsVisible = false;
         CancelButton.IsVisible = true;

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

@@ -50,7 +50,7 @@
             <Separator />
             <MenuItem
                 Command="{CompiledBinding SetAsWallpaperFilledCommand}"
-                CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                    FallbackValue=''}"
                 Header="{CompiledBinding Translation.SetAsWallpaper,
                                          Mode=OneWay}">

+ 17 - 17
src/PicView.Avalonia/Views/EffectsView.axaml.cs

@@ -73,16 +73,16 @@ public partial class EffectsView : UserControl
 
         if (vm.PicViewer.EffectConfig == null)
         {
-            vm.PicViewer.EffectConfig = new ImageEffectConfig();
+            vm.PicViewer.EffectConfig.Value = new ImageEffectConfig();
             HideResetBtn();
         }
-        else if (IsDefaultEffectConfig(vm.PicViewer.EffectConfig))
+        else if (IsDefaultEffectConfig(vm.PicViewer.EffectConfig.CurrentValue))
         {
             HideResetBtn();
         }
         else
         {
-            ApplyEffectConfig(vm.PicViewer.EffectConfig);
+            ApplyEffectConfig(vm.PicViewer.EffectConfig.CurrentValue);
         }
     }
 
@@ -108,37 +108,37 @@ public partial class EffectsView : UserControl
 
         BrightnessSlider.ValueChanged += (s, e) =>
         {
-            vm.PicViewer.EffectConfig ??= new ImageEffectConfig();
+            vm.PicViewer.EffectConfig.Value ??= new ImageEffectConfig();
             UpdateEffectConfig(vm, config => config.Brightness = new Percentage(e.NewValue));
             HideCancelBtn();
         };
         ContrastSlider.ValueChanged += (s, e) =>
         {
-            vm.PicViewer.EffectConfig ??= new ImageEffectConfig();
+            vm.PicViewer.EffectConfig.Value ??= new ImageEffectConfig();
             UpdateEffectConfig(vm, config => config.Contrast = new Percentage(e.NewValue));
             HideCancelBtn();
         };
         PencilSketchSlider.ValueChanged += (s, e) =>
         {
-            vm.PicViewer.EffectConfig ??= new ImageEffectConfig();
+            vm.PicViewer.EffectConfig.Value ??= new ImageEffectConfig();
             UpdateEffectConfig(vm, config => config.SketchStrokeWidth = e.NewValue);
             HideCancelBtn();
         };
         PosterizeSlider.ValueChanged += (s, e) =>
         {
-            vm.PicViewer.EffectConfig ??= new ImageEffectConfig();
+            vm.PicViewer.EffectConfig.Value ??= new ImageEffectConfig();
             UpdateEffectConfig(vm, config => config.PosterizeLevel = (int)e.NewValue == 1 ? 2 : (int)e.NewValue);
             HideCancelBtn();
         };
         SolarizeSlider.ValueChanged += (s, e) =>
         {
-            vm.PicViewer.EffectConfig ??= new ImageEffectConfig();
+            vm.PicViewer.EffectConfig.Value ??= new ImageEffectConfig();
             UpdateEffectConfig(vm, config => config.Solarize = new Percentage(e.NewValue));
             HideCancelBtn();
         };
         BlurSlider.ValueChanged += (s, e) =>
         {
-            vm.PicViewer.EffectConfig ??= new ImageEffectConfig();
+            vm.PicViewer.EffectConfig.Value ??= new ImageEffectConfig();
             UpdateEffectConfig(vm, config => config.BlurLevel = e.NewValue);
             HideCancelBtn();
         };
@@ -147,21 +147,21 @@ public partial class EffectsView : UserControl
         {
             var isBlackAndWhite = BlackAndWhiteToggleButton.IsChecked.HasValue &&
                                   BlackAndWhiteToggleButton.IsChecked.Value;
-            vm.PicViewer.EffectConfig ??= new ImageEffectConfig();
+            vm.PicViewer.EffectConfig.Value ??= new ImageEffectConfig();
             await UpdateToggleEffect(vm, BlackAndWhiteToggleButton, config => config.BlackAndWhite = isBlackAndWhite);
             HideCancelBtn();
         };
         NegativeToggleButton.Click += async delegate
         {
             var isNegative = NegativeToggleButton.IsChecked.HasValue && NegativeToggleButton.IsChecked.Value;
-            vm.PicViewer.EffectConfig ??= new ImageEffectConfig();
+            vm.PicViewer.EffectConfig.Value ??= new ImageEffectConfig();
             await UpdateToggleEffect(vm, NegativeToggleButton, config => config.Negative = isNegative);
             HideCancelBtn();
         };
         OldMovieToggleButton.Click += async delegate
         {
             var isOldMovie = OldMovieToggleButton.IsChecked.HasValue && OldMovieToggleButton.IsChecked.Value;
-            vm.PicViewer.EffectConfig ??= new ImageEffectConfig();
+            vm.PicViewer.EffectConfig.Value ??= new ImageEffectConfig();
             await UpdateToggleEffect(vm, OldMovieToggleButton, config => config.OldMovie = isOldMovie);
             HideCancelBtn();
         };
@@ -245,11 +245,11 @@ public partial class EffectsView : UserControl
         try
         {
             var magick = await ImageEffectsHelper
-                .ApplyEffects(vm.PicViewer.FileInfo, vm.PicViewer.EffectConfig, _cancellationTokenSource.Token)
+                .ApplyEffects(vm.PicViewer.FileInfo.CurrentValue, vm.PicViewer.EffectConfig.CurrentValue, _cancellationTokenSource.Token)
                 .ConfigureAwait(false);
             if (magick is not null)
             {
-                vm.PicViewer.ImageSource = magick.ToWriteableBitmap();
+                vm.PicViewer.ImageSource.Value = magick.ToWriteableBitmap();
             }
         }
         finally
@@ -293,7 +293,7 @@ public partial class EffectsView : UserControl
         BlurSlider.Value = 0;
         if (DataContext is MainViewModel vm)
         {
-            vm.PicViewer.EffectConfig = new ImageEffectConfig();
+            vm.PicViewer.EffectConfig.Value = new ImageEffectConfig();
         }
     }
 
@@ -335,7 +335,7 @@ public partial class EffectsView : UserControl
     /// <param name="updateAction">An action that updates the effect configuration.</param>
     private void UpdateEffectConfig(MainViewModel vm, Action<ImageEffectConfig> updateAction)
     {
-        updateAction(vm.PicViewer.EffectConfig);
+        updateAction(vm.PicViewer.EffectConfig.CurrentValue);
         DebounceSliderChange();
     }
 
@@ -356,7 +356,7 @@ public partial class EffectsView : UserControl
             return;
         }
 
-        updateAction(vm.PicViewer.EffectConfig);
+        updateAction(vm.PicViewer.EffectConfig.CurrentValue);
         await ApplyEffectsDebounced();
     }
 

+ 27 - 27
src/PicView.Avalonia/Views/ImageInfoView.axaml

@@ -64,7 +64,7 @@
             <!--  Print  -->
             <MenuItem
                 Command="{CompiledBinding PrintCommand}"
-                CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                    FallbackValue=''}"
                 Header="{CompiledBinding Translation.Print,
                                          Mode=OneWay}"
@@ -84,7 +84,7 @@
             <!--  Open with  -->
             <MenuItem
                 Command="{CompiledBinding OpenWithCommand}"
-                CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                    FallbackValue=''}"
                 Header="{CompiledBinding Translation.OpenWith,
                                          Mode=OneWay}"
@@ -104,7 +104,7 @@
             <!--  Locate on disk  -->
             <MenuItem
                 Command="{CompiledBinding LocateOnDiskCommand}"
-                CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                    FallbackValue=''}"
                 Header="{CompiledBinding Translation.ShowInFolder,
                                          Mode=OneWay}"
@@ -139,7 +139,7 @@
                 <!--  Set as wallpaper filled  -->
                 <MenuItem
                     Command="{CompiledBinding SetAsWallpaperFilledCommand}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     Header="{CompiledBinding Translation.Fill,
                                              Mode=OneWay}"
@@ -158,7 +158,7 @@
                 <!--  Set as wallpaper fit  -->
                 <MenuItem
                     Command="{CompiledBinding SetAsWallpaperFittedTask}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     Header="{CompiledBinding Translation.Fit,
                                              Mode=OneWay}"
@@ -177,7 +177,7 @@
                 <!--  Set as wallpaper stretched  -->
                 <MenuItem
                     Command="{CompiledBinding SetAsWallpaperStretchedCommand}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     Header="{CompiledBinding Translation.Stretch,
                                              Mode=OneWay}"
@@ -198,7 +198,7 @@
                 <!--  Set as wallpaper centered  -->
                 <MenuItem
                     Command="{CompiledBinding SetAsWallpaperCenteredCommand}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     Header="{CompiledBinding Translation.Center,
                                              Mode=OneWay}"
@@ -217,7 +217,7 @@
                 <!--  Set as wallpaper tiled  -->
                 <MenuItem
                     Command="{CompiledBinding SetAsWallpaperTiledCommand}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     Header="{CompiledBinding Translation.Tile,
                                              Mode=OneWay}"
@@ -237,7 +237,7 @@
             <!--  File properties  -->
             <MenuItem
                 Command="{CompiledBinding FilePropertiesCommand}"
-                CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                    FallbackValue=''}"
                 Header="{CompiledBinding Translation.FileProperties,
                                          Mode=OneWay}"
@@ -273,7 +273,7 @@
             <!--  Delete file  -->
             <MenuItem
                 Command="{CompiledBinding RecycleFileCommand}"
-                CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                    FallbackValue=''}"
                 Header="{CompiledBinding Translation.DeleteFile,
                                          Mode=OneWay}"
@@ -294,7 +294,7 @@
             <!--  Copy  -->
             <MenuItem
                 Command="{CompiledBinding CopyFileCommand}"
-                CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                    FallbackValue=''}"
                 Header="{CompiledBinding Translation.Copy,
                                          Mode=OneWay}">
@@ -310,7 +310,7 @@
                 <!--  Copy file  -->
                 <MenuItem
                     Command="{CompiledBinding CopyFileCommand}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     Header="{CompiledBinding Translation.CopyFile,
                                              Mode=OneWay}"
@@ -346,7 +346,7 @@
                 <!--  Copy file path  -->
                 <MenuItem
                     Command="{CompiledBinding CopyFilePathCommand}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     Header="{Binding Translation.FileCopyPath, Mode=OneWay}"
                     IsEnabled="{CompiledBinding PicViewer.FileInfo,
@@ -364,7 +364,7 @@
                 <!--  Copy base64  -->
                 <MenuItem
                     Command="{CompiledBinding CopyBase64Command}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     IsEnabled="{CompiledBinding PicViewer.FileInfo,
                                                 Converter={x:Static ObjectConverters.IsNotNull}}">
@@ -389,7 +389,7 @@
                 <!--  Duplicate file  -->
                 <MenuItem
                     Command="{CompiledBinding DuplicateFileCommand}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     Header="{CompiledBinding Translation.DuplicateFile,
                                              Mode=OneWay}"
@@ -465,11 +465,11 @@
                             Background="{DynamicResource SecondaryBackgroundColor}"
                             Classes="hover TStyle x"
                             Foreground="{StaticResource SecondaryTextColor}"
-                            Text="{Binding PicViewer.FileInfo.Name, FallbackValue=''}"
+                            Text="{Binding PicViewer.FileInfo.CurrentValue.Name, FallbackValue=''}"
                             x:Name="FileNameTextBox" />
                         <customControls:CopyButton
                             Classes="altHover BorderStyle"
-                            CopyText="{CompiledBinding PicViewer.FileInfo.Name,
+                            CopyText="{CompiledBinding PicViewer.FileInfo.CurrentValue.Name,
                                                        FallbackValue=''}"
                             ToolTip.Tip="{CompiledBinding Translation.Copy,
                                                           Mode=OneWay}">
@@ -494,12 +494,12 @@
                             Background="{DynamicResource SecondaryBackgroundColor}"
                             Classes="hover TStyle x"
                             Foreground="{StaticResource SecondaryTextColor}"
-                            Text="{CompiledBinding PicViewer.FileInfo.DirectoryName,
+                            Text="{CompiledBinding PicViewer.FileInfo.CurrentValue.DirectoryName,
                                                    FallbackValue=''}"
                             x:Name="DirectoryNameTextBox" />
                         <customControls:CopyButton
                             Classes="altHover BorderStyle"
-                            CopyText="{CompiledBinding PicViewer.FileInfo.DirectoryName,
+                            CopyText="{CompiledBinding PicViewer.FileInfo.CurrentValue.DirectoryName,
                                                        FallbackValue=''}"
                             ToolTip.Tip="{CompiledBinding Translation.Copy}">
                             <Path
@@ -523,12 +523,12 @@
                             Background="{DynamicResource SecondaryBackgroundColor}"
                             Classes="hover TStyle x"
                             Foreground="{StaticResource SecondaryTextColor}"
-                            Text="{CompiledBinding PicViewer.FileInfo.FullName,
+                            Text="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                    FallbackValue=''}"
                             x:Name="FullPathTextBox" />
                         <customControls:CopyButton
                             Classes="altHover BorderStyle"
-                            CopyText="{CompiledBinding PicViewer.FileInfo.FullName,
+                            CopyText="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                             ToolTip.Tip="{CompiledBinding Translation.Copy,
                                                           Mode=OneWay}">
@@ -552,11 +552,11 @@
                         <customControls:FuncTextBox
                             Classes=" TStyle x"
                             IsReadOnly="True"
-                            Text="{CompiledBinding PicViewer.FileInfo.CreationTime,
+                            Text="{CompiledBinding PicViewer.FileInfo.CurrentValue.CreationTime,
                                                    FallbackValue=''}" />
                         <customControls:CopyButton
                             Classes="altHover BorderStyle"
-                            CopyText="{CompiledBinding PicViewer.FileInfo.CreationTime,
+                            CopyText="{CompiledBinding PicViewer.FileInfo.CurrentValue.CreationTime,
                                                        FallbackValue=''}"
                             ToolTip.Tip="{CompiledBinding Translation.Copy,
                                                           Mode=OneWay}">
@@ -580,11 +580,11 @@
                         <customControls:FuncTextBox
                             Classes=" TStyle x"
                             IsReadOnly="True"
-                            Text="{CompiledBinding PicViewer.FileInfo.LastWriteTime,
+                            Text="{CompiledBinding PicViewer.FileInfo.CurrentValue.LastWriteTime,
                                                    FallbackValue=''}" />
                         <customControls:CopyButton
                             Classes="altHover BorderStyle"
-                            CopyText="{CompiledBinding PicViewer.FileInfo.LastWriteTime,
+                            CopyText="{CompiledBinding PicViewer.FileInfo.CurrentValue.LastWriteTime,
                                                        FallbackValue=''}"
                             ToolTip.Tip="{CompiledBinding Translation.Copy}">
                             <Path
@@ -607,11 +607,11 @@
                         <customControls:FuncTextBox
                             Classes=" TStyle x"
                             IsReadOnly="True"
-                            Text="{CompiledBinding PicViewer.FileInfo.LastAccessTime,
+                            Text="{CompiledBinding PicViewer.FileInfo.CurrentValue.LastAccessTime,
                                                    FallbackValue=''}" />
                         <customControls:CopyButton
                             Classes="altHover BorderStyle"
-                            CopyText="{CompiledBinding PicViewer.FileInfo.LastAccessTime,
+                            CopyText="{CompiledBinding PicViewer.FileInfo.CurrentValue.LastAccessTime,
                                                        FallbackValue=''}"
                             ToolTip.Tip="{CompiledBinding Translation.Copy,
                                                           Mode=OneWay}">

+ 19 - 19
src/PicView.Avalonia/Views/ImageInfoView.axaml.cs

@@ -74,9 +74,9 @@ public partial class ImageInfoView : UserControl
                     ExifHandling.UpdateExifValues(vm);
                     Dispatcher.UIThread.InvokeAsync(() =>
                     {
-                        if (DirectoryNameTextBox.Text != vm.PicViewer.FileInfo.DirectoryName)
+                        if (DirectoryNameTextBox.Text != vm.PicViewer.FileInfo.CurrentValue.DirectoryName)
                         {
-                            DirectoryNameTextBox.Text = vm.PicViewer.FileInfo.DirectoryName;
+                            DirectoryNameTextBox.Text = vm.PicViewer.FileInfo.CurrentValue.DirectoryName;
                         }
                     });
                 });
@@ -85,9 +85,9 @@ public partial class ImageInfoView : UserControl
                 PixelWidthTextBox.Text = vm.PicViewer.PixelWidth.ToString();
                 PixelHeightTextBox.Text = vm.PicViewer.PixelHeight.ToString();
                 AdjustAspectRatio(PixelWidthTextBox);
-                FullPathTextBox.Text = vm.PicViewer.FileInfo?.FullName ?? "";
-                DirectoryNameTextBox.Text = vm.PicViewer.FileInfo?.DirectoryName ?? "";
-                FileNameTextBox.Text = vm.PicViewer.FileInfo?.Name ?? "";
+                FullPathTextBox.Text = vm.PicViewer.FileInfo?.CurrentValue.FullName ?? "";
+                DirectoryNameTextBox.Text = vm.PicViewer.FileInfo?.CurrentValue.DirectoryName ?? "";
+                FileNameTextBox.Text = vm.PicViewer.FileInfo?.CurrentValue.Name ?? "";
             };
 
             SaveButton.Click += async (_, _) =>
@@ -95,22 +95,22 @@ public partial class ImageInfoView : UserControl
                 var ext = GetExtension();
                 var location = FullPathTextBox.Text; // TODO check if this is a valid path
                 // and sync with file name/directory text boxes
-                await SendToImageSaver(vm.PicViewer.FileInfo?.FullName, location, PixelWidthTextBox.Text,
+                await SendToImageSaver(vm.PicViewer.FileInfo?.CurrentValue.FullName, location, PixelWidthTextBox.Text,
                     PixelHeightTextBox.Text, ext).ConfigureAwait(false);
             };
 
             SaveAsButton.Click += async (_, _) =>
             {
-                var fileInfoFullName = vm.PicViewer.FileInfo.FullName;
+                var fileInfoFullName = vm.PicViewer.FileInfo.CurrentValue.FullName;
                 var ext = DetermineFileExtension(vm, ref fileInfoFullName);
 
-                var file = await FilePicker.PickFileForSavingAsync(vm.PicViewer.FileInfo?.FullName, ext);
+                var file = await FilePicker.PickFileForSavingAsync(vm.PicViewer.FileInfo?.CurrentValue.FullName, ext);
                 if (file is null)
                 {
                     return;
                 }
 
-                await SendToImageSaver(vm.PicViewer.FileInfo?.FullName, file, PixelWidthTextBox.Text,
+                await SendToImageSaver(vm.PicViewer.FileInfo?.CurrentValue.FullName, file, PixelWidthTextBox.Text,
                     PixelHeightTextBox.Text, ext).ConfigureAwait(false);
             };
             FileNameTextBox.KeyDown += async (_, e) =>
@@ -120,8 +120,8 @@ public partial class ImageInfoView : UserControl
                     return;
                 }
 
-                var newPath = Path.Combine(vm.PicViewer.FileInfo.DirectoryName, FileNameTextBox.Text);
-                var oldPath = vm.PicViewer.FileInfo.FullName;
+                var newPath = Path.Combine(vm.PicViewer.FileInfo.CurrentValue.DirectoryName, FileNameTextBox.Text);
+                var oldPath = vm.PicViewer.FileInfo.CurrentValue.FullName;
                 var renamed = await FileRenamer.AttemptRenameAsync(oldPath, newPath, vm).ConfigureAwait(false);
                 if (renamed)
                 {
@@ -136,7 +136,7 @@ public partial class ImageInfoView : UserControl
                 }
 
                 var newPath = FullPathTextBox.Text;
-                var oldPath = vm.PicViewer.FileInfo.FullName;
+                var oldPath = vm.PicViewer.FileInfo.CurrentValue.FullName;
                 var renamed = await FileRenamer.AttemptRenameAsync(oldPath, newPath, vm).ConfigureAwait(false);
                 if (renamed)
                 {
@@ -150,10 +150,10 @@ public partial class ImageInfoView : UserControl
                     return;
                 }
 
-                var oldDirectory = vm.PicViewer.FileInfo.DirectoryName;
+                var oldDirectory = vm.PicViewer.FileInfo.CurrentValue.DirectoryName;
                 var newDirectory = DirectoryNameTextBox.Text;
 
-                var oldPath = vm.PicViewer.FileInfo.FullName;
+                var oldPath = vm.PicViewer.FileInfo.CurrentValue.FullName;
                 var newPath = oldPath.Replace(oldDirectory, newDirectory);
 
                 await FileRenamer.AttemptRenameAsync(oldPath, newPath, vm).ConfigureAwait(false);
@@ -197,7 +197,7 @@ public partial class ImageInfoView : UserControl
             return;
         }
 
-        var aspectRatio = (double)vm.PicViewer.PixelWidth / vm.PicViewer.PixelHeight;
+        var aspectRatio = (double)vm.PicViewer.PixelWidth.CurrentValue / vm.PicViewer.PixelHeight.CurrentValue;
         AspectRatioHelper.SetAspectRatioForTextBox(PixelWidthTextBox, PixelHeightTextBox, sender == PixelWidthTextBox,
             aspectRatio, DataContext as MainViewModel);
 
@@ -219,7 +219,7 @@ public partial class ImageInfoView : UserControl
 
         var gcd = ImageTitleFormatter.GCD(width, height);
         AspectRatioTextBox.Text =
-            AspectRatioHelper.GetFormattedAspectRatio(gcd, vm.PicViewer.PixelWidth, vm.PicViewer.PixelHeight);
+            AspectRatioHelper.GetFormattedAspectRatio(gcd, vm.PicViewer.PixelWidth.CurrentValue, vm.PicViewer.PixelHeight.CurrentValue);
     }
 
     private static async Task DoResize(MainViewModel vm, bool isWidth, object width, object height)
@@ -233,7 +233,7 @@ public partial class ImageInfoView : UserControl
 
             if (widthValue > 0)
             {
-                var success = await ConversionHelper.ResizeByWidth(vm.PicViewer.FileInfo, widthValue)
+                var success = await ConversionHelper.ResizeByWidth(vm.PicViewer.FileInfo.CurrentValue, widthValue)
                     .ConfigureAwait(false);
                 if (success)
                 {
@@ -250,7 +250,7 @@ public partial class ImageInfoView : UserControl
 
             if (heightValue > 0)
             {
-                var success = await ConversionHelper.ResizeByHeight(vm.PicViewer.FileInfo, heightValue)
+                var success = await ConversionHelper.ResizeByHeight(vm.PicViewer.FileInfo.CurrentValue, heightValue)
                     .ConfigureAwait(false);
                 if (success)
                 {
@@ -276,7 +276,7 @@ public partial class ImageInfoView : UserControl
 
     private string DetermineFileExtension(MainViewModel vm, ref string destination)
     {
-        var ext = vm.PicViewer.FileInfo.Extension;
+        var ext = vm.PicViewer.FileInfo.CurrentValue.Extension;
         if (NoConversion.IsSelected)
         {
             return ext;

+ 11 - 11
src/PicView.Avalonia/Views/ImageViewer.axaml

@@ -17,33 +17,33 @@
     <LayoutTransformControl x:Name="ImageLayoutTransformControl">
         <customControls:AutoScrollViewer
             Focusable="False"
-            Height="{CompiledBinding PicViewer.ScrollViewerHeight,
+            Height="{CompiledBinding PicViewer.ScrollViewerHeight.Value,
                                      Mode=OneWay}"
             ScrollChanged="ImageScrollViewer_OnScrollChanged"
             Theme="{StaticResource Main}"
             VerticalScrollBarVisibility="{CompiledBinding ToggleScrollBarVisibility}"
-            Width="{CompiledBinding PicViewer.ScrollViewerWidth,
+            Width="{CompiledBinding PicViewer.ScrollViewerWidth.Value,
                                     Mode=OneWay}"
             x:Name="ImageScrollViewer">
-            <Border     
+            <Border
                 Background="{CompiledBinding ConstrainedImageBackground,
-                                 Mode=OneWay}"
-                Height="{CompiledBinding PicViewer.ImageHeight,
+                                             Mode=OneWay}"
+                Height="{CompiledBinding PicViewer.ImageHeight.Value,
                                          Mode=OneWay}"
-                Width="{CompiledBinding PicViewer.ImageWidth,
+                Width="{CompiledBinding PicViewer.ImageWidth.Value,
                                         Mode=OneWay}"
                 x:Name="MainBorder">
-                <customControls:PicBox 
-                    ImageType="{CompiledBinding PicViewer.ImageType,
+                <customControls:PicBox
+                    ImageType="{CompiledBinding PicViewer.ImageType.Value,
                                                 Mode=OneWay}"
                     PointerMoved="MainImage_OnPointerMoved"
                     PointerPressed="MainImage_OnPointerPressed"
                     PointerReleased="MainImage_OnPointerReleased"
-                    SecondaryImageWidth="{CompiledBinding PicViewer.SecondaryImageWidth,
+                    SecondaryImageWidth="{CompiledBinding PicViewer.SecondaryImageWidth.Value,
                                                           Mode=OneWay}"
-                    SecondarySource="{CompiledBinding PicViewer.SecondaryImageSource,
+                    SecondarySource="{CompiledBinding PicViewer.SecondaryImageSource.Value,
                                                       Mode=OneWay}"
-                    Source="{CompiledBinding PicViewer.ImageSource,
+                    Source="{CompiledBinding PicViewer.ImageSource.Value,
                                              Mode=OneWay}"
                     UseLayoutRounding="True"
                     x:Name="MainImage" />

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

@@ -80,7 +80,7 @@
             <!--  Print  -->
             <MenuItem
                 Command="{CompiledBinding PrintCommand}"
-                CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                    FallbackValue=''}"
                 Header="{CompiledBinding Translation.Print,
                                          Mode=OneWay}"
@@ -100,7 +100,7 @@
             <!--  Open with  -->
             <MenuItem
                 Command="{CompiledBinding OpenWithCommand}"
-                CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                    FallbackValue=''}"
                 Header="{CompiledBinding Translation.OpenWith,
                                          Mode=OneWay}"
@@ -120,7 +120,7 @@
             <!--  Locate on disk  -->
             <MenuItem
                 Command="{CompiledBinding LocateOnDiskCommand}"
-                CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                    FallbackValue=''}"
                 Header="{CompiledBinding Translation.ShowInFolder,
                                          Mode=OneWay}"
@@ -682,7 +682,7 @@
             <!--  Set as wallpaper  -->
             <MenuItem
                 Command="{CompiledBinding SetAsWallpaperCommand}"
-                CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                    FallbackValue=''}"
                 Header="{CompiledBinding Translation.SetAsWallpaper,
                                          Mode=OneWay}"
@@ -724,7 +724,7 @@
                 <!--  File properties  -->
                 <MenuItem
                     Command="{CompiledBinding FilePropertiesCommand}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     Header="{CompiledBinding Translation.FileProperties,
                                              Mode=OneWay}"
@@ -862,7 +862,7 @@
             <!--  Delete file  -->
             <MenuItem
                 Command="{CompiledBinding RecycleFileCommand}"
-                CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                    FallbackValue=''}"
                 Header="{CompiledBinding Translation.DeleteFile,
                                          Mode=OneWay}"
@@ -895,7 +895,7 @@
             <!--  Copy  -->
             <MenuItem
                 Command="{CompiledBinding CopyFileCommand}"
-                CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                    FallbackValue=''}"
                 Header="{CompiledBinding Translation.Copy,
                                          Mode=OneWay}">
@@ -928,7 +928,7 @@
                 <!--  Copy file path  -->
                 <MenuItem
                     Command="{CompiledBinding CopyFilePathCommand}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     Header="{Binding Translation.FileCopyPath, Mode=OneWay}"
                     IsEnabled="{CompiledBinding PicViewer.FileInfo,
@@ -946,7 +946,7 @@
                 <!--  Copy base64  -->
                 <MenuItem
                     Command="{CompiledBinding CopyBase64Command}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     IsEnabled="{CompiledBinding PicViewer.FileInfo,
                                                 Converter={x:Static ObjectConverters.IsNotNull}}">
@@ -971,7 +971,7 @@
                 <!--  Duplicate file  -->
                 <MenuItem
                     Command="{CompiledBinding DuplicateFileCommand}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     Header="{CompiledBinding Translation.DuplicateFile,
                                              Mode=OneWay}"
@@ -1010,7 +1010,7 @@
             <!--  Copy file  -->
             <MenuItem
                 Command="{CompiledBinding CopyFileCommand}"
-                CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                    FallbackValue=''}"
                 Header="{CompiledBinding Translation.CopyFile,
                                          Mode=OneWay}"

+ 2 - 4
src/PicView.Avalonia/Views/SingleImageResizeView.axaml

@@ -1,13 +1,11 @@
 <UserControl
     d:DesignHeight="450"
     d:DesignWidth="800"
-    mc:Ignorable="d"
     x:Class="PicView.Avalonia.Views.SingleImageResizeView"
     x:DataType="viewModels:MainViewModel"
     xmlns="https://github.com/avaloniaui"
     xmlns:customControls="clr-namespace:PicView.Avalonia.CustomControls"
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
     xmlns:uc="clr-namespace:PicView.Avalonia.Views.UC"
     xmlns:viewModels="clr-namespace:PicView.Avalonia.ViewModels;assembly=PicView.Avalonia"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml">
@@ -33,7 +31,7 @@
                             Foreground="{StaticResource SecondaryTextColor}"
                             Height="35"
                             Margin="0"
-                            Text="{CompiledBinding PicViewer.PixelWidth,
+                            Text="{CompiledBinding PicViewer.PixelWidth.Value,
                                                    Mode=OneWay}"
                             ToolTip.Tip="{CompiledBinding Translation.SizeTooltip,
                                                           Mode=OneWay}"
@@ -80,7 +78,7 @@
                             Foreground="{StaticResource SecondaryTextColor}"
                             Height="35"
                             Margin="0"
-                            Text="{CompiledBinding PicViewer.PixelHeight,
+                            Text="{CompiledBinding PicViewer.PixelHeight.Value,
                                                    Mode=OneWay}"
                             ToolTip.Tip="{CompiledBinding Translation.SizeTooltip,
                                                           Mode=OneWay}"

+ 13 - 16
src/PicView.Avalonia/Views/SingleImageResizeView.axaml.cs

@@ -1,5 +1,4 @@
-using System.Reactive.Linq;
-using Avalonia;
+using Avalonia;
 using Avalonia.Controls;
 using Avalonia.Controls.ApplicationLifetimes;
 using Avalonia.Input;
@@ -12,7 +11,7 @@ using PicView.Avalonia.UI;
 using PicView.Avalonia.ViewModels;
 using PicView.Core.ImageDecoding;
 using PicView.Core.Localization;
-using ReactiveUI;
+using R3;
 
 namespace PicView.Avalonia.Views;
 
@@ -41,13 +40,11 @@ public partial class SingleImageResizeView : UserControl
             BgPanel.Background = Brushes.Transparent;
         }
 
-        _aspectRatio = (double)vm.PicViewer.PixelWidth / vm.PicViewer.PixelHeight;
+        _aspectRatio = (double)vm.PicViewer.PixelWidth.CurrentValue / vm.PicViewer.PixelHeight.CurrentValue;
 
         RegisterEventHandlers(vm);
 
-        _imageUpdateSubscription = vm.PicViewer.WhenAnyValue(x => x.FileInfo)
-            .ObserveOn(RxApp.MainThreadScheduler)
-            .Select(x => x is not null)
+        Observable.EveryValueChanged(vm.PicViewer, x => x.FileInfo.Value, UIHelper.GetFrameProvider)
             .Subscribe(_ =>
             {
                 UpdateQualitySliderState();
@@ -126,10 +123,10 @@ public partial class SingleImageResizeView : UserControl
                 QualitySlider.IsEnabled = true;
                 QualitySlider.Value = 75;
             }
-            else if (IsOriginalFileQualityFormat(vm.PicViewer.FileInfo.Extension))
+            else if (IsOriginalFileQualityFormat(vm.PicViewer.FileInfo.CurrentValue.Extension))
             {
                 QualitySlider.IsEnabled = true;
-                QualitySlider.Value = ImageAnalyzer.GetCompressionQuality(vm.PicViewer.FileInfo.FullName);
+                QualitySlider.Value = ImageAnalyzer.GetCompressionQuality(vm.PicViewer.FileInfo.CurrentValue.FullName);
             }
             else
             {
@@ -168,10 +165,10 @@ public partial class SingleImageResizeView : UserControl
             return;
         }
 
-        var fileInfoFullName = vm.PicViewer.FileInfo.FullName;
+        var fileInfoFullName = vm.PicViewer.FileInfo.CurrentValue.FullName;
         var ext = GetSelectedFileExtension(vm, ref fileInfoFullName);
 
-        var file = await FilePicker.PickFileForSavingAsync(vm.PicViewer.FileInfo?.FullName, ext);
+        var file = await FilePicker.PickFileForSavingAsync(vm.PicViewer.FileInfo?.CurrentValue.FullName, ext);
         if (file is null)
         {
             return;
@@ -182,7 +179,7 @@ public partial class SingleImageResizeView : UserControl
 
     private async Task SaveImage(MainViewModel vm)
     {
-        await DoSaveImage(vm, vm.PicViewer.FileInfo.FullName).ConfigureAwait(false);
+        await DoSaveImage(vm, vm.PicViewer.FileInfo.CurrentValue.FullName).ConfigureAwait(false);
     }
 
     private async Task DoSaveImage(MainViewModel vm, string destination)
@@ -197,7 +194,7 @@ public partial class SingleImageResizeView : UserControl
 
         const int rotationAngle = 0; // TODO: Add rotation control
 
-        var file = vm.PicViewer.FileInfo.FullName;
+        var file = vm.PicViewer.FileInfo.CurrentValue.FullName;
         var ext = GetSelectedFileExtension(vm, ref destination);
         destination = Path.ChangeExtension(destination, ext);
         var sameFile = file.Equals(destination, StringComparison.OrdinalIgnoreCase);
@@ -233,7 +230,7 @@ public partial class SingleImageResizeView : UserControl
 
     private string GetSelectedFileExtension(MainViewModel vm, ref string destination)
     {
-        var ext = vm.PicViewer.FileInfo.Extension;
+        var ext = vm.PicViewer.FileInfo.CurrentValue.Extension;
         if (NoConversion.IsSelected)
         {
             return ext;
@@ -309,10 +306,10 @@ public partial class SingleImageResizeView : UserControl
         PixelWidthTextBox.Text = vm.PicViewer.PixelWidth.ToString();
         PixelHeightTextBox.Text = vm.PicViewer.PixelHeight.ToString();
 
-        if (IsOriginalFileQualityFormat(vm.PicViewer.FileInfo.Extension))
+        if (IsOriginalFileQualityFormat(vm.PicViewer.FileInfo.CurrentValue.Extension))
         {
             QualitySlider.IsEnabled = true;
-            QualitySlider.Value = ImageAnalyzer.GetCompressionQuality(vm.PicViewer.FileInfo.FullName);
+            QualitySlider.Value = ImageAnalyzer.GetCompressionQuality(vm.PicViewer.FileInfo.CurrentValue.FullName);
         }
         else
         {

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

@@ -1,7 +1,7 @@
 <UserControl
     Height="{CompiledBinding TitlebarHeight,
                              Mode=OneWay}"
-    ToolTip.Tip="{Binding PicViewer.TitleTooltip, Mode=OneWay}"
+    ToolTip.Tip="{Binding PicViewer.TitleTooltip.Value, Mode=OneWay}"
     Width="{CompiledBinding TitleMaxWidth,
                             Mode=OneWay}"
     d:DesignHeight="450"
@@ -25,7 +25,7 @@
             IsTabStop="False"
             IsVisible="{CompiledBinding !IsEditableTitlebarOpen}"
             Padding="0,7,0,5"
-            Text="{CompiledBinding PicViewer.Title,
+            Text="{CompiledBinding PicViewer.Title.Value,
                                    Mode=OneWay}"
             TextAlignment="Center"
             TextTrimming="CharacterEllipsis"

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

@@ -4,7 +4,6 @@ using Avalonia.Interactivity;
 using Avalonia.Threading;
 using PicView.Avalonia.FileSystem;
 using PicView.Avalonia.Input;
-using PicView.Avalonia.Navigation;
 using PicView.Avalonia.UI;
 using PicView.Avalonia.ViewModels;
 using PicView.Core.Localization;
@@ -60,7 +59,7 @@ public partial class EditableTitlebar : UserControl
         vm.IsEditableTitlebarOpen = false;
         Cursor = new Cursor(StandardCursorType.Arrow);
         MainKeyboardShortcuts.IsKeysEnabled = true;
-        TextBlock.Text = vm.PicViewer.Title;
+        TextBlock.Text = vm.PicViewer.Title.CurrentValue;
     }
 
     protected override void OnKeyDown(KeyEventArgs e)
@@ -109,8 +108,8 @@ public partial class EditableTitlebar : UserControl
 
         if (e.Key == Key.Enter)
         {
-            var oldPath = vm.PicViewer.FileInfo.FullName;
-            var newPath = Path.Combine(vm.PicViewer.FileInfo.DirectoryName, TextBox.Text);
+            var oldPath = vm.PicViewer.FileInfo.CurrentValue.FullName;
+            var newPath = Path.Combine(vm.PicViewer.FileInfo.CurrentValue.DirectoryName, TextBox.Text);
             Task.Run(async () =>
             {
                 if (newPath == oldPath)
@@ -154,7 +153,7 @@ public partial class EditableTitlebar : UserControl
             return;
         }
 
-        var filename = vm.PicViewer.FileInfo.Name;
+        var filename = vm.PicViewer.FileInfo.CurrentValue.Name;
         TextBox.Text = filename;
 
         var start = TextBox.Text.Length - filename.Length;

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

@@ -63,7 +63,7 @@
                     Canvas.Left="45"
                     Classes="hover btn"
                     Command="{CompiledBinding PrintCommand}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     Data="{StaticResource PrintGeometry}"
                     IconHeight="17"
@@ -78,7 +78,7 @@
                     Canvas.Left="90"
                     Classes="hover btn"
                     Command="{CompiledBinding RecycleFileCommand}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     Data="{StaticResource RecycleGeometry}"
                     IconHeight="17"
@@ -104,7 +104,7 @@
                     Canvas.Left="180"
                     Classes="hover btn"
                     Command="{CompiledBinding CopyFileCommand}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     Data="{StaticResource CopyGeometry}"
                     IconHeight="17"
@@ -118,7 +118,7 @@
                     Canvas.Left="225"
                     Classes="hover btn"
                     Command="{CompiledBinding DuplicateFileCommand}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     Data="{StaticResource DuplicateGeometry}"
                     IconHeight="17"
@@ -168,7 +168,7 @@
                     Canvas.Top="104"
                     Classes="ButtonBorder altHover"
                     Command="{CompiledBinding OpenWithCommand}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     Height="46"
                     IsEnabled="{CompiledBinding PicViewer.FileInfo,
@@ -194,7 +194,7 @@
                     Canvas.Top="53"
                     Classes="ButtonBorder altHover"
                     Command="{CompiledBinding LocateOnDiskCommand}"
-                    CommandParameter="{CompiledBinding PicViewer.FileInfo.FullName,
+                    CommandParameter="{CompiledBinding PicViewer.FileInfo.CurrentValue.FullName,
                                                        FallbackValue=''}"
                     Height="46"
                     IsEnabled="{CompiledBinding PicViewer.FileInfo,

+ 4 - 4
src/PicView.Avalonia/WindowBehavior/WindowFunctions.cs

@@ -50,12 +50,12 @@ public static class WindowFunctions
             }
             else
             {
-                lastFile = vm?.PicViewer.FileInfo?.FullName ?? FileHistoryManager.GetLastEntry();
+                lastFile = vm?.PicViewer.FileInfo?.CurrentValue.FullName ?? FileHistoryManager.GetLastEntry();
             }
         }
         else
         {
-            var url = vm?.PicViewer.Title.GetURL();
+            var url = vm?.PicViewer.Title.CurrentValue.GetURL();
             lastFile = !string.IsNullOrWhiteSpace(url) ? url : FileHistoryManager.GetLastEntry();
         }
 
@@ -76,7 +76,7 @@ public static class WindowFunctions
         {
             if (Settings.WindowProperties.AutoFit)
             {
-                if (vm.PicViewer.PixelWidth > UIHelper.GetMainView.Bounds.Width || vm.PicViewer.PixelHeight > UIHelper.GetMainView.Bounds.Height)
+                if (vm.PicViewer.PixelWidth.Value > UIHelper.GetMainView.Bounds.Width || vm.PicViewer.PixelHeight.Value > UIHelper.GetMainView.Bounds.Height)
                 {
                     vm.ImageViewer.MainBorder.Height = double.NaN;
                     vm.ImageViewer.MainBorder.Width = double.NaN;
@@ -109,7 +109,7 @@ public static class WindowFunctions
                 }
                 else
                 {
-                    if (vm.PicViewer.PixelWidth > UIHelper.GetMainView.Bounds.Width || vm.PicViewer.PixelHeight > UIHelper.GetMainView.Bounds.Height)
+                    if (vm.PicViewer.PixelWidth.CurrentValue > UIHelper.GetMainView.Bounds.Width || vm.PicViewer.PixelHeight.CurrentValue > UIHelper.GetMainView.Bounds.Height)
                     {
                         Dispatcher.UIThread.Post(() => WindowResizing.SetSize(vm), DispatcherPriority.Render);
                     }

+ 13 - 13
src/PicView.Avalonia/WindowBehavior/WindowResizing.cs

@@ -99,13 +99,13 @@ public static class WindowResizing
     public static void SetSize(ImageSize size, MainViewModel vm)
     {
         vm.TitleMaxWidth = size.TitleMaxWidth;
-        vm.PicViewer.ImageWidth = size.Width;
-        vm.PicViewer.SecondaryImageWidth = size.SecondaryWidth;
-        vm.PicViewer.ImageHeight = size.Height;
+        vm.PicViewer.ImageWidth.Value = size.Width;
+        vm.PicViewer.SecondaryImageWidth.Value = size.SecondaryWidth;
+        vm.PicViewer.ImageHeight.Value = size.Height;
         vm.GalleryMargin = new Thickness(0, 0, 0, size.Margin);
 
-        vm.PicViewer.ScrollViewerWidth = size.ScrollViewerWidth;
-        vm.PicViewer.ScrollViewerHeight = size.ScrollViewerHeight;
+        vm.PicViewer.ScrollViewerWidth.Value = size.ScrollViewerWidth;
+        vm.PicViewer.ScrollViewerHeight.Value = size.ScrollViewerHeight;
 
         if (Settings.WindowProperties.AutoFit)
         {
@@ -127,7 +127,7 @@ public static class WindowResizing
             vm.GalleryWidth = double.NaN;
         }
 
-        vm.PicViewer.AspectRatio = size.AspectRatio;
+        vm.PicViewer.AspectRatio.Value = size.AspectRatio;
     }
 
     public static ImageSize? GetSize(MainViewModel vm)
@@ -138,7 +138,7 @@ public static class WindowResizing
         {
             if (vm.PicViewer.FileInfo is null)
             {
-                if (vm.PicViewer.ImageSource is Bitmap bitmap)
+                if (vm.PicViewer.ImageSource.CurrentValue is Bitmap bitmap)
                 {
                     firstWidth = bitmap.PixelSize.Width;
                     firstHeight = bitmap.PixelSize.Height;
@@ -148,12 +148,12 @@ public static class WindowResizing
                     return null;
                 }
             }
-            else if (vm.PicViewer.FileInfo?.Exists != null)
+            else if (vm.PicViewer.FileInfo?.CurrentValue?.Exists != null)
             {
                 try
                 {
                     var magickImage = new MagickImage();
-                    magickImage.Ping(vm.PicViewer.FileInfo);
+                    magickImage.Ping(vm.PicViewer.FileInfo.CurrentValue);
                     firstWidth = magickImage.Width;
                     firstHeight = magickImage.Height;
                 }
@@ -170,8 +170,8 @@ public static class WindowResizing
         }
         else
         {
-            firstWidth = preloadValue.ImageModel?.PixelWidth ?? vm.PicViewer.ImageWidth;
-            firstHeight = preloadValue.ImageModel?.PixelHeight ?? vm.PicViewer.ImageHeight;
+            firstWidth = preloadValue.ImageModel?.PixelWidth ?? vm.PicViewer.ImageWidth.CurrentValue;
+            firstHeight = preloadValue.ImageModel?.PixelHeight ?? vm.PicViewer.ImageHeight.CurrentValue;
         }
 
         if (!Settings.ImageScaling.ShowImageSideBySide)
@@ -207,8 +207,8 @@ public static class WindowResizing
         double rotation,
         MainViewModel vm)
     {
-        width = width == 0 ? vm.PicViewer.ImageWidth : width;
-        height = height == 0 ? vm.PicViewer.ImageHeight : height;
+        width = width == 0 ? vm.PicViewer.ImageWidth.CurrentValue : width;
+        height = height == 0 ? vm.PicViewer.ImageHeight.CurrentValue : height;
         if (Application.Current?.ApplicationLifetime is not IClassicDesktopStyleApplicationLifetime desktop)
         {
             return null;

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

@@ -26,7 +26,8 @@
   </PropertyGroup>
   <ItemGroup>
     <PackageReference Include="Magick.NET-Q8-OpenMP-x64" Version="14.6.0" />
-    <PackageReference Include="ReactiveUI" Version="20.3.1" />
+    <PackageReference Include="R3" Version="1.3.0" />
+    <PackageReference Include="ReactiveUI" Version="20.4.1" />
     <PackageReference Include="SharpCompress" Version="0.40.0" />
     <PackageReference Include="ZLinq" Version="1.4.12" />
     <PackageReference Include="ZLinq.FileSystem" Version="1.4.12" />

+ 26 - 98
src/PicView.Core/ViewModels/PicViewerModel.cs

@@ -1,136 +1,64 @@
 using PicView.Core.ImageDecoding;
 using PicView.Core.ImageEffects;
-using ReactiveUI;
+using R3;
 
 namespace PicView.Core.ViewModels;
 
-public class PicViewerModel : ReactiveObject
+public class PicViewerModel : IDisposable
 {
-    public FileInfo? FileInfo
+    public void Dispose()
     {
-        get;
-        set => this.RaiseAndSetIfChanged(ref field, value);
+        Disposable.Dispose(FileInfo);
     }
     
+    public BindableReactiveProperty<FileInfo?> FileInfo { get; } = new();
+
     /// <summary>
     /// The image's pixel width
     /// </summary>
-    public int PixelWidth
-    {
-        get;
-        set => this.RaiseAndSetIfChanged(ref field, value);
-    }
+    public BindableReactiveProperty<int> PixelWidth { get; } = new(0);
 
     /// <summary>
     /// The image's pixel height
     /// </summary>
-    public int PixelHeight
-    {
-        get;
-        set => this.RaiseAndSetIfChanged(ref field, value);
-    }
+    public BindableReactiveProperty<int> PixelHeight { get; } = new(0);
     
-    
-    public object? ImageSource
-    {
-        get;
-        set => this.RaiseAndSetIfChanged(ref field, value);
-    }
+    public BindableReactiveProperty<object?> ImageSource  { get; } = new();
 
-    public object? SecondaryImageSource
-    {
-        get;
-        set => this.RaiseAndSetIfChanged(ref field, value);
-    }
+    public BindableReactiveProperty<object?> SecondaryImageSource { get; } = new();
 
-    public ImageType ImageType
-    {
-        get;
-        set => this.RaiseAndSetIfChanged(ref field, value);
-    }
+    public BindableReactiveProperty<ImageType> ImageType { get; } = new();
 
     /// <summary>
     /// The width to scale the image to
     /// </summary>
-    public double ImageWidth
-    {
-        get;
-        set => this.RaiseAndSetIfChanged(ref field, value);
-    }
+    public BindableReactiveProperty<double> ImageWidth { get; } = new(0);
 
     /// <summary>
     /// The height to scale the image to
     /// </summary>
-    public double ImageHeight
-    {
-        get;
-        set => this.RaiseAndSetIfChanged(ref field, value);
-    }
+    public BindableReactiveProperty<double> ImageHeight { get; } = new(0);
 
-    public double SecondaryImageWidth
-    {
-        get;
-        set => this.RaiseAndSetIfChanged(ref field, value);
-    }
+    public BindableReactiveProperty<double> SecondaryImageWidth { get; } = new(0);
 
-    public bool IsShowingSideBySide
-    {
-        get;
-        set => this.RaiseAndSetIfChanged(ref field, value);
-    }
+    public BindableReactiveProperty<bool> IsShowingSideBySide { get; } = new();
 
-    public double ScrollViewerWidth
-    {
-        get;
-        set => this.RaiseAndSetIfChanged(ref field, value);
-    } = double.NaN;
+    public BindableReactiveProperty<double> ScrollViewerWidth { get; } = new(0);
 
-    public double ScrollViewerHeight
-    {
-        get;
-        set => this.RaiseAndSetIfChanged(ref field, value);
-    } = double.NaN;
+    public BindableReactiveProperty<double> ScrollViewerHeight { get; } = new(0);
 
-    public double AspectRatio
-    {
-        get;
-        set => this.RaiseAndSetIfChanged(ref field, value);
-    }
+    public BindableReactiveProperty<double> AspectRatio { get; } = new();
 
-    public ImageEffectConfig? EffectConfig
-    {
-        get;
-        set => this.RaiseAndSetIfChanged(ref field, value);
-    }
+    public BindableReactiveProperty<ImageEffectConfig?> EffectConfig { get; } = new();
+
+    public BindableReactiveProperty<EXIFHelper.EXIFOrientation?> ExifOrientation { get; } = new();
 
-    public EXIFHelper.EXIFOrientation? ExifOrientation
-    {
-        get;
-        set => this.RaiseAndSetIfChanged(ref field, value);
-    }
-    
     // Used to flip the flip button
-    public int ScaleX
-    {
-        get;
-        set => this.RaiseAndSetIfChanged(ref field, value);
-    } = 1;
-    
-    public string? Title
-    {
-        get;
-        set => this.RaiseAndSetIfChanged(ref field, value);
-    } = "Loading...";
+    public BindableReactiveProperty<int> ScaleX { get; } = new();
 
-    public string? TitleTooltip
-    {
-        get;
-        set => this.RaiseAndSetIfChanged(ref field, value);
-    } = "Loading...";
+    public BindableReactiveProperty<string?> Title { get; } = new();
 
-    public string? WindowTitle
-    {
-        get;
-        set => this.RaiseAndSetIfChanged(ref field, value);
-    } = "PicView";
+    public BindableReactiveProperty<string?> TitleTooltip { get; } = new();
+
+    public BindableReactiveProperty<string?> WindowTitle { get; } = new();
 }