فهرست منبع

Architecture update

Ruben 1 سال پیش
والد
کامیت
b2e13e31e3
51فایلهای تغییر یافته به همراه523 افزوده شده و 476 حذف شده
  1. 6 0
      src/Directory.Build.props
  2. 4 2
      src/PicView.Avalonia.MacOS/App.axaml
  3. 28 0
      src/PicView.Avalonia.MacOS/App.axaml.cs
  4. 25 0
      src/PicView.Avalonia.MacOS/PicView.Avalonia.MacOS.csproj
  5. 28 0
      src/PicView.Avalonia.MacOS/Program.cs
  6. 8 3
      src/PicView.Avalonia.MacOS/Views/MainWindow.axaml
  7. 11 0
      src/PicView.Avalonia.MacOS/Views/MainWindow.axaml.cs
  8. 1 1
      src/PicView.Avalonia.MacOS/app.manifest
  9. 1 0
      src/PicView.Avalonia.MacOS/readme.md
  10. 10 0
      src/PicView.Avalonia.Win32/App.axaml
  11. 28 0
      src/PicView.Avalonia.Win32/App.axaml.cs
  12. 29 0
      src/PicView.Avalonia.Win32/PicView.Avalonia.Win32.csproj
  13. 12 5
      src/PicView.Avalonia.Win32/Program.cs
  14. 16 0
      src/PicView.Avalonia.Win32/Views/MainWindow.axaml
  15. 11 0
      src/PicView.Avalonia.Win32/Views/MainWindow.axaml.cs
  16. 18 0
      src/PicView.Avalonia.Win32/app.manifest
  17. 1 0
      src/PicView.Avalonia.Win32/readme.md
  18. 0 37
      src/PicView.Avalonia/App.axaml.cs
  19. BIN
      src/PicView.Avalonia/Assets/avalonia-logo.ico
  20. BIN
      src/PicView.Avalonia/Assets/icon.ico
  21. BIN
      src/PicView.Avalonia/Assets/icon.png
  22. 0 16
      src/PicView.Avalonia/Models/LoadSettings.cs
  23. 0 84
      src/PicView.Avalonia/Models/SkiaExtensions.cs
  24. 7 56
      src/PicView.Avalonia/PicView.Avalonia.csproj
  25. 6 0
      src/PicView.Avalonia/ViewModels/MainViewModel.cs
  26. 7 0
      src/PicView.Avalonia/ViewModels/ViewModelBase.cs
  27. 0 23
      src/PicView.Avalonia/Views/MacMainWindow.axaml
  28. 0 13
      src/PicView.Avalonia/Views/MacMainWindow.axaml.cs
  29. 16 0
      src/PicView.Avalonia/Views/MainView.axaml
  30. 11 0
      src/PicView.Avalonia/Views/MainView.axaml.cs
  31. 0 24
      src/PicView.Avalonia/Views/MainWindow.axaml
  32. 0 52
      src/PicView.Avalonia/Views/MainWindow.axaml.cs
  33. 0 11
      src/PicView.Avalonia/Views/UserControls/BottomMenu.axaml.cs
  34. 1 0
      src/PicView.Avalonia/readme.md
  35. 11 0
      src/PicView.Core/PicView.Core.csproj
  36. 7 0
      src/PicView.MacOS/Class1.cs
  37. 9 0
      src/PicView.MacOS/PicView.MacOS.csproj
  38. 1 1
      src/PicView.WPF/PicGallery/GalleryLoad.cs
  39. 1 0
      src/PicView.WPF/PicView.WPF.csproj
  40. 0 24
      src/PicView.WPF/SystemIntegration/LockScreenImage.cs
  41. 0 6
      src/PicView.WPF/SystemIntegration/NativeMethods.cs
  42. 9 88
      src/PicView.WPF/SystemIntegration/Wallpaper.cs
  43. 2 1
      src/PicView.WPF/UILogic/Loading/LoadContextMenus.cs
  44. 14 14
      src/PicView.WPF/UILogic/Loading/StartLoading.cs
  45. 2 1
      src/PicView.WPF/Views/UserControls/Gallery/PicGalleryItem.xaml.cs
  46. 6 5
      src/PicView.WPF/Views/Windows/EffectsWindows.xaml.cs
  47. 31 0
      src/PicView.Windows/Lockscreen/LockscreenHelper.cs
  48. 24 0
      src/PicView.Windows/PicView.Windows.csproj
  49. 91 0
      src/PicView.Windows/Wallpaper/WallpaperHelper.cs
  50. 29 5
      src/PicView.sln
  51. 1 4
      src/XamlAnimatedGif/Decoding/GifDecoderException.cs

+ 6 - 0
src/Directory.Build.props

@@ -0,0 +1,6 @@
+<Project>
+  <PropertyGroup>
+    <Nullable>enable</Nullable>
+    <AvaloniaVersion>11.0.2</AvaloniaVersion>
+  </PropertyGroup>
+</Project>

+ 4 - 2
src/PicView.Avalonia/App.axaml → src/PicView.Avalonia.MacOS/App.axaml

@@ -1,9 +1,11 @@
 <Application
-    x:Class="PicView.Avalonia.App"
+    x:Class="PicView.Avalonia.MacOS.App"
     xmlns="https://github.com/avaloniaui"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     RequestedThemeVariant="Default">
+    <!--  "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options.  -->
+
     <Application.Styles>
-        <SimpleTheme />
+        <FluentTheme />
     </Application.Styles>
 </Application>

+ 28 - 0
src/PicView.Avalonia.MacOS/App.axaml.cs

@@ -0,0 +1,28 @@
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Markup.Xaml;
+using PicView.Avalonia.ViewModels;
+using MainWindow = PicView.Avalonia.MacOS.Views.MainWindow;
+
+namespace PicView.Avalonia.MacOS;
+
+public partial class App : Application
+{
+    public override void Initialize()
+    {
+        AvaloniaXamlLoader.Load(this);
+    }
+
+    public override void OnFrameworkInitializationCompleted()
+    {
+        if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+        {
+            desktop.MainWindow = new MainWindow
+            {
+                DataContext = new MainViewModel()
+            };
+        }
+
+        base.OnFrameworkInitializationCompleted();
+    }
+}

+ 25 - 0
src/PicView.Avalonia.MacOS/PicView.Avalonia.MacOS.csproj

@@ -0,0 +1,25 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>WinExe</OutputType>
+    <!--If you are willing to use Windows/MacOS native APIs you will need to create 3 projects.
+    One for Windows with net7.0-windows TFM, one for MacOS with net7.0-macos and one with net7.0 TFM for Linux.-->
+    <TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
+    <Nullable>enable</Nullable>
+    <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
+    <ApplicationManifest>app.manifest</ApplicationManifest>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Avalonia.Desktop" Version="$(AvaloniaVersion)" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\PicView.Avalonia\PicView.Avalonia.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Compile Update="App.axaml.cs">
+      <DependentUpon>App.axaml</DependentUpon>
+    </Compile>
+  </ItemGroup>
+</Project>

+ 28 - 0
src/PicView.Avalonia.MacOS/Program.cs

@@ -0,0 +1,28 @@
+using System;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.ReactiveUI;
+
+namespace PicView.Avalonia.MacOS;
+
+internal class Program
+{
+    // Initialization code. Don't use any Avalonia, third-party APIs or any
+    // SynchronizationContext-reliant code before AppMain is called: things aren't initialized
+    // yet and stuff might break.
+    [STAThread]
+    public static void Main(string[] args) => BuildAvaloniaApp()
+        .StartWithClassicDesktopLifetime(args, ShutdownMode.OnMainWindowClose);
+
+    // Avalonia configuration, don't remove; also used by visual designer.
+    public static AppBuilder BuildAvaloniaApp()
+    {
+        return AppBuilder.Configure<App>()
+#if DEBUG
+            .LogToTrace()
+#endif
+            .UseReactiveUI()
+            .UsePlatformDetect()
+            .UseSkia();
+    }
+}

+ 8 - 3
src/PicView.Avalonia/Views/UserControls/BottomMenu.axaml → src/PicView.Avalonia.MacOS/Views/MainWindow.axaml

@@ -1,9 +1,14 @@
-<UserControl
-    x:Class="PicView.Avalonia.Views.UserControls.BottomMenu"
+<Window
+    x:Class="PicView.Avalonia.MacOS.Views.MainWindow"
     xmlns="https://github.com/avaloniaui"
     xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
     xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
     xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:views="clr-namespace:PicView.Avalonia.Views;assembly=PicView.Avalonia"
+    xmlns:vm="using:PicView.Avalonia.ViewModels"
+    Title="PicView MacOS"
     d:DesignHeight="450"
     d:DesignWidth="800"
-    mc:Ignorable="d" />
+    mc:Ignorable="d">
+    <views:MainView />
+</Window>

+ 11 - 0
src/PicView.Avalonia.MacOS/Views/MainWindow.axaml.cs

@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace PicView.Avalonia.MacOS.Views;
+
+public partial class MainWindow : Window
+{
+    public MainWindow()
+    {
+        InitializeComponent();
+    }
+}

+ 1 - 1
src/PicView.Avalonia/app.manifest → src/PicView.Avalonia.MacOS/app.manifest

@@ -3,7 +3,7 @@
   <!-- This manifest is used on Windows only.
        Don't remove it as it might cause problems with window transparency and embeded controls.
        For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
-  <assemblyIdentity version="1.0.0.0" name="PicView.Avalonia.Desktop"/>
+  <assemblyIdentity version="1.0.0.0" name="AvaloniaTest.Desktop"/>
 
   <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
     <application>

+ 1 - 0
src/PicView.Avalonia.MacOS/readme.md

@@ -0,0 +1 @@
+This project is for containing the views specific for the MacOS platform.

+ 10 - 0
src/PicView.Avalonia.Win32/App.axaml

@@ -0,0 +1,10 @@
+<Application xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             x:Class="PicView.Avalonia.Win32.App"
+             RequestedThemeVariant="Default">
+             <!-- "Default" ThemeVariant follows system theme variant. "Dark" or "Light" are other available options. -->
+
+    <Application.Styles>
+        <FluentTheme />
+    </Application.Styles>
+</Application>

+ 28 - 0
src/PicView.Avalonia.Win32/App.axaml.cs

@@ -0,0 +1,28 @@
+using Avalonia;
+using Avalonia.Controls.ApplicationLifetimes;
+using Avalonia.Markup.Xaml;
+using PicView.Avalonia.ViewModels;
+using MainWindow = PicView.Avalonia.Win32.Views.MainWindow;
+
+namespace PicView.Avalonia.Win32;
+
+public partial class App : Application
+{
+    public override void Initialize()
+    {
+        AvaloniaXamlLoader.Load(this);
+    }
+
+    public override void OnFrameworkInitializationCompleted()
+    {
+        if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
+        {
+            desktop.MainWindow = new MainWindow
+            {
+                DataContext = new MainViewModel()
+            };
+        }
+
+        base.OnFrameworkInitializationCompleted();
+    }
+}

+ 29 - 0
src/PicView.Avalonia.Win32/PicView.Avalonia.Win32.csproj

@@ -0,0 +1,29 @@
+<Project Sdk="Microsoft.NET.Sdk">
+  <PropertyGroup>
+    <OutputType>WinExe</OutputType>
+    <!--If you are willing to use Windows/MacOS native APIs you will need to create 3 projects.
+    One for Windows with net7.0-windows TFM, one for MacOS with net7.0-macos and one with net7.0 TFM for Linux.-->
+    <TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
+    <Nullable>enable</Nullable>
+    <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
+    <ApplicationManifest>app.manifest</ApplicationManifest>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <PackageReference Include="Avalonia.Desktop" Version="$(AvaloniaVersion)" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <ProjectReference Include="..\PicView.Avalonia\PicView.Avalonia.csproj" />
+    <ProjectReference Include="..\PicView.Windows\PicView.Windows.csproj" />
+  </ItemGroup>
+
+  <ItemGroup>
+    <Compile Update="App.axaml.cs">
+      <DependentUpon>%(Filename)</DependentUpon>
+    </Compile>
+    <Compile Update="Views\MainWindow.axaml.cs">
+      <DependentUpon>%(Filename)</DependentUpon>
+    </Compile>
+  </ItemGroup>
+</Project>

+ 12 - 5
src/PicView.Avalonia/Program.cs → src/PicView.Avalonia.Win32/Program.cs

@@ -1,6 +1,9 @@
-using Avalonia;
+using System;
+using Avalonia;
+using Avalonia.Controls;
+using Avalonia.ReactiveUI;
 
-namespace PicView.Avalonia;
+namespace PicView.Avalonia.Win32;
 
 internal class Program
 {
@@ -9,13 +12,17 @@ internal class Program
     // yet and stuff might break.
     [STAThread]
     public static void Main(string[] args) => BuildAvaloniaApp()
-        .StartWithClassicDesktopLifetime(args);
+        .StartWithClassicDesktopLifetime(args, ShutdownMode.OnMainWindowClose);
 
     // Avalonia configuration, don't remove; also used by visual designer.
     public static AppBuilder BuildAvaloniaApp()
-        => AppBuilder.Configure<App>()
+    {
+        return AppBuilder.Configure<App>()
 #if DEBUG
             .LogToTrace()
 #endif
-            .UsePlatformDetect();
+            .UseReactiveUI()
+            .UseWin32()
+            .UseSkia();
+    }
 }

+ 16 - 0
src/PicView.Avalonia.Win32/Views/MainWindow.axaml

@@ -0,0 +1,16 @@
+<Window
+    x:Class="PicView.Avalonia.Win32.Views.MainWindow"
+    xmlns="https://github.com/avaloniaui"
+    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+    xmlns:views="clr-namespace:PicView.Avalonia.Views"
+    xmlns:views1="clr-namespace:PicView.Avalonia.Win32.Views"
+    xmlns:views2="clr-namespace:PicView.Avalonia.Views;assembly=PicView.Avalonia"
+    xmlns:vm="using:PicView.Avalonia.ViewModels"
+    Title="PicView.Avalonia"
+    d:DesignHeight="450"
+    d:DesignWidth="800"
+    mc:Ignorable="d">
+    <views2:MainView />
+</Window>

+ 11 - 0
src/PicView.Avalonia.Win32/Views/MainWindow.axaml.cs

@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace PicView.Avalonia.Win32.Views;
+
+public partial class MainWindow : Window
+{
+    public MainWindow()
+    {
+        InitializeComponent();
+    }
+}

+ 18 - 0
src/PicView.Avalonia.Win32/app.manifest

@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<assembly manifestVersion="1.0" xmlns="urn:schemas-microsoft-com:asm.v1">
+  <!-- This manifest is used on Windows only.
+       Don't remove it as it might cause problems with window transparency and embeded controls.
+       For more details visit https://learn.microsoft.com/en-us/windows/win32/sbscs/application-manifests -->
+  <assemblyIdentity version="1.0.0.0" name="AvaloniaTest.Desktop"/>
+
+  <compatibility xmlns="urn:schemas-microsoft-com:compatibility.v1">
+    <application>
+      <!-- A list of the Windows versions that this application has been tested on
+           and is designed to work with. Uncomment the appropriate elements
+           and Windows will automatically select the most compatible environment. -->
+
+      <!-- Windows 10 -->
+      <supportedOS Id="{8e0f7a12-bfb3-4fe8-b9a5-48fd50a15a9a}" />
+    </application>
+  </compatibility>
+</assembly>

+ 1 - 0
src/PicView.Avalonia.Win32/readme.md

@@ -0,0 +1 @@
+This project is for containing the views specific for the Windows platform.

+ 0 - 37
src/PicView.Avalonia/App.axaml.cs

@@ -1,37 +0,0 @@
-using Avalonia;
-using Avalonia.Controls.ApplicationLifetimes;
-using Avalonia.Markup.Xaml;
-using PicView.Avalonia.Views;
-using PicView.Core.Config;
-using System.Runtime;
-using System.Runtime.InteropServices;
-
-namespace PicView.Avalonia;
-
-public partial class App : Application
-{
-    public override void Initialize()
-    {
-        ProfileOptimization.SetProfileRoot(AppDomain.CurrentDomain.BaseDirectory);
-        ProfileOptimization.StartProfile("ProfileOptimization");
-        AvaloniaXamlLoader.Load(this);
-    }
-
-    public override void OnFrameworkInitializationCompleted()
-    {
-        if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
-        {
-            // Run MacOS specific style
-            if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX))
-            {
-                desktop.MainWindow = new MacMainWindow();
-            }
-            else
-            {
-                desktop.MainWindow = new MainWindow();
-            }
-        }
-
-        base.OnFrameworkInitializationCompleted();
-    }
-}

BIN
src/PicView.Avalonia/Assets/avalonia-logo.ico


BIN
src/PicView.Avalonia/Assets/icon.ico


BIN
src/PicView.Avalonia/Assets/icon.png


+ 0 - 16
src/PicView.Avalonia/Models/LoadSettings.cs

@@ -1,16 +0,0 @@
-using PicView.Core.Config;
-using PicView.Core.Localization;
-
-namespace PicView.Avalonia.Models;
-
-internal static class LoadSettings
-{
-    internal static async Task StartLoadingAsync()
-    {
-        await Task.Run(async () =>
-        {
-            await SettingsHelper.LoadSettingsAsync().ConfigureAwait(false);
-            await TranslationHelper.LoadLanguage(SettingsHelper.Settings.UIProperties.UserLanguage).ConfigureAwait(false);
-        }).ConfigureAwait(false);
-    }
-}

+ 0 - 84
src/PicView.Avalonia/Models/SkiaExtensions.cs

@@ -1,84 +0,0 @@
-using Avalonia;
-using Avalonia.Media;
-using Avalonia.Platform;
-using Avalonia.Rendering.SceneGraph;
-using Avalonia.Skia;
-using SkiaSharp;
-
-namespace PicView.Avalonia.Models;
-
-internal static class SkiaExtensions
-{
-    private record SKBitmapDrawOperation : ICustomDrawOperation
-    {
-        public Rect Bounds { get; set; }
-
-        public SKBitmap? Bitmap { get; init; }
-
-        public void Dispose()
-        {
-            Bitmap?.Dispose();
-        }
-
-        public bool Equals(ICustomDrawOperation? other) => false;
-
-        public bool HitTest(Point p) => Bounds.Contains(p);
-
-        public void Render(ImmediateDrawingContext context)
-        {
-            if (Bitmap is null || context.PlatformImpl.GetFeature<ISkiaSharpApiLeaseFeature>() is not { } leaseFeature)
-            {
-                return;
-            }
-
-            var lease = leaseFeature.Lease();
-            using (lease)
-            {
-                lease.SkCanvas.DrawBitmap(Bitmap, SKRect.Create((float)Bounds.X, (float)Bounds.Y, (float)Bounds.Width, (float)Bounds.Height));
-            }
-        }
-    }
-
-    private class AvaloniaImage : IImage, IDisposable
-    {
-        private readonly SKBitmap? _source;
-        private SKBitmapDrawOperation? _drawImageOperation;
-
-        public AvaloniaImage(SKBitmap? source)
-        {
-            _source = source;
-            if (source?.Info.Size is { } size)
-            {
-                Size = new Size(size.Width, size.Height);
-            }
-        }
-
-        public Size Size { get; }
-
-        public void Dispose()
-        {
-            _source?.Dispose();
-            _drawImageOperation?.Dispose();
-        }
-
-        public void Draw(DrawingContext context, Rect sourceRect, Rect destRect)
-        {
-            _drawImageOperation ??= new SKBitmapDrawOperation()
-            {
-                Bitmap = _source,
-            }; ;
-            _drawImageOperation.Bounds = sourceRect;
-            context.Custom(_drawImageOperation);
-        }
-    }
-
-    public static SKBitmap? ToSKBitmap(this Stream? stream)
-    {
-        return stream == null ? null : SKBitmap.Decode(stream);
-    }
-
-    public static IImage? ToAvaloniaImage(this SKBitmap? bitmap)
-    {
-        return bitmap is not null ? new AvaloniaImage(bitmap) : default(IImage?);
-    }
-}

+ 7 - 56
src/PicView.Avalonia/PicView.Avalonia.csproj

@@ -1,71 +1,22 @@
 <Project Sdk="Microsoft.NET.Sdk">
   <PropertyGroup>
-    <OutputType>WinExe</OutputType>
     <TargetFramework>net8.0</TargetFramework>
     <Nullable>enable</Nullable>
-    <BuiltInComInteropSupport>true</BuiltInComInteropSupport>
-    <ApplicationManifest>app.manifest</ApplicationManifest>
+    <LangVersion>latest</LangVersion>
     <AvaloniaUseCompiledBindingsByDefault>true</AvaloniaUseCompiledBindingsByDefault>
-    <ApplicationIcon>Assets\icon.ico</ApplicationIcon>
-    <ImplicitUsings>enable</ImplicitUsings>
-    <Copyright>Ruben Hyldgaard Negendahl</Copyright>
-    <PackageProjectUrl>picview.org</PackageProjectUrl>
-    <RepositoryUrl>https://github.com/Ruben2776/PicView/</RepositoryUrl>
-    <PublishAot>False</PublishAot>
-    <PublishTrimmed>False</PublishTrimmed>
-  </PropertyGroup>
-  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
-    <DebugType>none</DebugType>
   </PropertyGroup>
 
+  
   <ItemGroup>
     <AvaloniaResource Include="Assets\**" />
   </ItemGroup>
 
   <ItemGroup>
-    <AvaloniaResource Remove="Assets\icon.ico" />
-    <AvaloniaResource Remove="Assets\icon.png" />
-  </ItemGroup>
-
-  <ItemGroup>
-    <None Remove="Assets\icon.ico" />
-    <None Remove="Assets\icon.png" />
-  </ItemGroup>
-
-  <ItemGroup>
-    <Content Include="Assets\icon.ico" />
-  </ItemGroup>
-
-
-  <ItemGroup>
-    <PackageReference Include="Avalonia" Version="11.0.6" />
-    <PackageReference Include="Avalonia.Desktop" Version="11.0.6" />
+    <PackageReference Include="Avalonia" Version="$(AvaloniaVersion)" />
+    <PackageReference Include="Avalonia.Themes.Fluent" Version="$(AvaloniaVersion)" />
+    <PackageReference Include="Avalonia.Fonts.Inter" Version="$(AvaloniaVersion)" />
+    <PackageReference Include="Avalonia.ReactiveUI" Version="$(AvaloniaVersion)" />
     <!--Condition below is needed to remove Avalonia.Diagnostics package from build output in Release configuration.-->
-    <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="11.0.6" />
-    <PackageReference Include="Avalonia.Themes.Simple" Version="11.0.6" />
-    <PackageReference Include="Magick.NET-Q8-OpenMP-x64" Version="13.5.0" />
-  </ItemGroup>
-
-
-  <ItemGroup>
-    <ProjectReference Include="..\PicView.Core\PicView.Core.csproj" />
-  </ItemGroup>
-
-
-  <ItemGroup>
-    <Resource Include="Assets\icon.png" />
-    <Resource Include="Assets\icon.ico" />
-  </ItemGroup>
-
-
-  <ItemGroup>
-    <Compile Update="Views\MacMainWindow.axaml.cs">
-      <DependentUpon>MacMainWindow.axaml</DependentUpon>
-    </Compile>
-  </ItemGroup>
-
-
-  <ItemGroup>
-    <Using Include="System.Diagnostics" />
+    <PackageReference Condition="'$(Configuration)' == 'Debug'" Include="Avalonia.Diagnostics" Version="$(AvaloniaVersion)" />
   </ItemGroup>
 </Project>

+ 6 - 0
src/PicView.Avalonia/ViewModels/MainViewModel.cs

@@ -0,0 +1,6 @@
+namespace PicView.Avalonia.ViewModels;
+
+public class MainViewModel : ViewModelBase
+{
+    public string Greeting => "Welcome to Avalonia!";
+}

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

@@ -0,0 +1,7 @@
+using ReactiveUI;
+
+namespace PicView.Avalonia.ViewModels;
+
+public class ViewModelBase : ReactiveObject
+{
+}

+ 0 - 23
src/PicView.Avalonia/Views/MacMainWindow.axaml

@@ -1,23 +0,0 @@
-<Window
-    x:Class="PicView.Avalonia.Views.MacMainWindow"
-    xmlns="https://github.com/avaloniaui"
-    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-    Title="Loading..."
-    MinWidth="462"
-    MinHeight="400"
-    d:DesignHeight="450"
-    d:DesignWidth="800"
-    Background="Transparent"
-    ExtendClientAreaChromeHints="PreferSystemChrome"
-    ExtendClientAreaTitleBarHeightHint="-1"
-    ExtendClientAreaToDecorationsHint="True"
-    SizeToContent="WidthAndHeight"
-    TransparencyLevelHint="AcrylicBlur"
-    WindowStartupLocation="CenterScreen"
-    mc:Ignorable="d">
-    <Border>
-        <DockPanel />
-    </Border>
-</Window>

+ 0 - 13
src/PicView.Avalonia/Views/MacMainWindow.axaml.cs

@@ -1,13 +0,0 @@
-using Avalonia.Controls;
-using System;
-
-namespace PicView.Avalonia.Views;
-
-public partial class MacMainWindow : Window
-{
-    public MacMainWindow()
-    {
-        InitializeComponent();
-        Models.LoadSettings.StartLoadingAsync();
-    }
-}

+ 16 - 0
src/PicView.Avalonia/Views/MainView.axaml

@@ -0,0 +1,16 @@
+<UserControl xmlns="https://github.com/avaloniaui"
+             xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
+             xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
+             xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
+             xmlns:vm="clr-namespace:PicView.Avalonia.ViewModels"
+             mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
+             x:Class="PicView.Avalonia.Views.MainView"
+             x:DataType="vm:MainViewModel">
+  <Design.DataContext>
+    <!-- This only sets the DataContext for the previewer in an IDE,
+         to set the actual DataContext for runtime, set the DataContext property in code (look at App.axaml.cs) -->
+    <vm:MainViewModel />
+  </Design.DataContext>
+
+  <TextBlock Text="{Binding Greeting}" HorizontalAlignment="Center" VerticalAlignment="Center"/>
+</UserControl>

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

@@ -0,0 +1,11 @@
+using Avalonia.Controls;
+
+namespace PicView.Avalonia.Views;
+
+public partial class MainView : UserControl
+{
+    public MainView()
+    {
+        InitializeComponent();
+    }
+}

+ 0 - 24
src/PicView.Avalonia/Views/MainWindow.axaml

@@ -1,24 +0,0 @@
-<Window
-    x:Class="PicView.Avalonia.Views.MainWindow"
-    xmlns="https://github.com/avaloniaui"
-    xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
-    xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
-    xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
-    Title="Loading..."
-    MinWidth="462"
-    MinHeight="400"
-    d:DesignHeight="450"
-    d:DesignWidth="800"
-    Background="Transparent"
-    ExtendClientAreaChromeHints="PreferSystemChrome"
-    ExtendClientAreaTitleBarHeightHint="-1"
-    ExtendClientAreaToDecorationsHint="True"
-    SizeToContent="WidthAndHeight"
-    TransparencyLevelHint="AcrylicBlur"
-    WindowStartupLocation="CenterScreen"
-    mc:Ignorable="d">
-    <Grid>
-
-        <Image x:Name="MainImage" Stretch="Uniform" />
-    </Grid>
-</Window>

+ 0 - 52
src/PicView.Avalonia/Views/MainWindow.axaml.cs

@@ -1,52 +0,0 @@
-using Avalonia.Controls;
-using Avalonia.Threading;
-using PicView.Avalonia.Models;
-using PicView.Core.Calculations;
-using PicView.Core.Config;
-
-namespace PicView.Avalonia.Views;
-
-public partial class MainWindow : Window
-{
-    public MainWindow()
-    {
-        InitializeComponent();
-
-        Loaded += async delegate
-        {
-            await LoadSettings.StartLoadingAsync().ConfigureAwait(false);
-            var args = Environment.GetCommandLineArgs();
-            if (args.Length > 1)
-            {
-                var skBitmap = await Core.ImageDecoding.ImageDecoder.GetSKBitmapAsync(new FileInfo(args[1])).ConfigureAwait(false);
-                var avaloniaPic = skBitmap.ToAvaloniaImage();
-                await Dispatcher.UIThread.InvokeAsync(() =>
-                {
-                    var size = ImageSizeCalculationHelper.GetImageSize(
-                        width: avaloniaPic.Size.Width,
-                        height: avaloniaPic.Size.Height,
-                        monitorWidth: 2560,
-                        monitorHeight: 1440,
-                        rotationAngle: 0,
-                        stretch: SettingsHelper.Settings.ImageScaling.StretchImage,
-                        padding: 0,
-                        dpiScaling: 1,
-                        fullscreen: SettingsHelper.Settings.ImageScaling.StretchImage,
-                        uiTopSize: 0,
-                        uiBottomSize: 0,
-                        galleryHeight: SettingsHelper.Settings.Gallery.IsBottomGalleryShown
-                            ? SettingsHelper.Settings.Gallery.BottomGalleryItemSize
-                            : 0,
-                        autoFit: SettingsHelper.Settings.WindowProperties.AutoFit,
-                        containerWidth: Width,
-                        containerHeight: Height,
-                        SettingsHelper.Settings.Zoom.ScrollEnabled
-                    );
-                    MainImage.Source = avaloniaPic;
-                    MainImage.Width = size.Width;
-                    MainImage.Height = size.Height;
-                });
-            }
-        };
-    }
-}

+ 0 - 11
src/PicView.Avalonia/Views/UserControls/BottomMenu.axaml.cs

@@ -1,11 +0,0 @@
-using Avalonia.Controls;
-
-namespace PicView.Avalonia.Views.UserControls;
-
-public partial class BottomMenu : UserControl
-{
-    public BottomMenu()
-    {
-        InitializeComponent();
-    }
-}

+ 1 - 0
src/PicView.Avalonia/readme.md

@@ -0,0 +1 @@
+Shared project for UI components and UI related logic in Avalonia, that is not platform specific. 

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

@@ -4,6 +4,17 @@
     <TargetFramework>net8.0</TargetFramework>
     <ImplicitUsings>enable</ImplicitUsings>
     <Nullable>enable</Nullable>
+    <PlatformTarget>x64</PlatformTarget>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <IsAotCompatible>False</IsAotCompatible>
+    <IsTrimmable>False</IsTrimmable>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <IsAotCompatible>False</IsAotCompatible>
+    <IsTrimmable>False</IsTrimmable>
   </PropertyGroup>
 
   <ItemGroup>

+ 7 - 0
src/PicView.MacOS/Class1.cs

@@ -0,0 +1,7 @@
+namespace PicView.MacOS
+{
+    public class Class1
+    {
+
+    }
+}

+ 9 - 0
src/PicView.MacOS/PicView.MacOS.csproj

@@ -0,0 +1,9 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+  </PropertyGroup>
+
+</Project>

+ 1 - 1
src/PicView.WPF/PicGallery/GalleryLoad.cs

@@ -121,7 +121,7 @@ internal static class GalleryLoad
             ParallelOptions options = new()
             {
                 // Don't slow the system down too much
-                MaxDegreeOfParallelism = 4
+                MaxDegreeOfParallelism = Math.Max(Environment.ProcessorCount - 2, 4)
             };
             ParallelOptions secondOptions = new()
             {

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

@@ -292,6 +292,7 @@
   </ItemGroup>
   <ItemGroup>
     <ProjectReference Include="..\PicView.Core\PicView.Core.csproj" />
+    <ProjectReference Include="..\PicView.Windows\PicView.Windows.csproj" />
     <ProjectReference Include="..\XamlAnimatedGif\XamlAnimatedGif.csproj" />
   </ItemGroup>
   <ItemGroup>

+ 0 - 24
src/PicView.WPF/SystemIntegration/LockScreenImage.cs

@@ -124,28 +124,4 @@ public static class LockScreenHelper
 
         return true;
     }
-
-    [DllImport("kernel32.dll", SetLastError = true)]
-    public static extern bool Wow64DisableWow64FsRedirection(ref IntPtr ptr); //If on 64 bit, C# will replace "System32" with "SysWOW64". This disables that.
-
-    public static bool SetLockScreenImage(string path)
-    {
-        const string registryKey =
-            @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\PersonalizationCSP";
-        var ptr = new IntPtr();
-        Wow64DisableWow64FsRedirection(ref ptr);
-
-        try
-        {
-            Registry.SetValue(registryKey, "LockScreenImageStatus", 1, RegistryValueKind.DWord);
-            Registry.SetValue(registryKey, "LockScreenImagePath", path, RegistryValueKind.String);
-            Registry.SetValue(registryKey, "LockScreenImageUrl", path, RegistryValueKind.String);
-        }
-        catch
-        {
-            return false;
-        }
-
-        return true;
-    }
 }

+ 0 - 6
src/PicView.WPF/SystemIntegration/NativeMethods.cs

@@ -37,12 +37,6 @@ internal static partial class NativeMethods
     [return: MarshalAs(UnmanagedType.Bool)]
     internal static partial bool SetCursorPos(int x, int y);
 
-    // Used to check for wallpaper support
-    [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
-    [return: MarshalAs(UnmanagedType.Bool)]
-    internal static extern bool SystemParametersInfo(uint uiAction, uint uiParam,
-        string pvParam, uint fWinIni);
-
     #region Disable Screensaver and Power options
 
     internal const uint ES_CONTINUOUS = 0x80000000;

+ 9 - 88
src/PicView.WPF/SystemIntegration/Wallpaper.cs

@@ -1,31 +1,20 @@
-using System.ComponentModel;
-using System.IO;
-using System.Windows;
-using System.Windows.Input;
-using System.Windows.Media.Imaging;
-using Microsoft.Win32;
-using PicView.Core.FileHandling;
+using PicView.Core.FileHandling;
+using PicView.Windows.Wallpaper;
 using PicView.WPF.ChangeImage;
 using PicView.WPF.ChangeTitlebar;
-using PicView.WPF.FileHandling;
 using PicView.WPF.ImageHandling;
 using PicView.WPF.UILogic;
+using System.IO;
+using System.Windows;
+using System.Windows.Input;
+using System.Windows.Media.Imaging;
 using Rotation = PicView.WPF.UILogic.TransformImage.Rotation;
 
 namespace PicView.WPF.SystemIntegration;
 
 public static class Wallpaper // Taken from a Microsoft sample...
 {
-    public enum WallpaperStyle
-    {
-        Tile,
-        Center,
-        Stretch,
-        Fit,
-        Fill
-    }
-
-    internal static async Task SetWallpaperAsync(WallpaperStyle style, string? path = null)
+    internal static async Task SetWallpaperAsync(WallpaperHelper.WallpaperStyle style, string? path = null)
     {
         var url = string.Empty;
         await ConfigureWindows.GetMainWindow.Dispatcher.InvokeAsync(() =>
@@ -83,11 +72,11 @@ public static class Wallpaper // Taken from a Microsoft sample...
 
             await SaveImages.SaveImageAsync(rotationAngle, isFlipped, bitmapSource, imagePath, destinationPath,
                 null, effectApplied).ConfigureAwait(false);
-            SetDesktopWallpaper(destinationPath, style);
+            WallpaperHelper.SetDesktopWallpaper(destinationPath, style);
         }
         else
         {
-            SetDesktopWallpaper(Navigation.Pics[Navigation.FolderIndex], style);
+            WallpaperHelper.SetDesktopWallpaper(Navigation.Pics[Navigation.FolderIndex], style);
         }
 
         await ConfigureWindows.GetMainWindow.Dispatcher.InvokeAsync(() =>
@@ -106,72 +95,4 @@ public static class Wallpaper // Taken from a Microsoft sample...
             Application.Current.MainWindow!.Cursor = Cursors.Arrow;
         });
     }
-
-    /// <summary>
-    /// Set the desktop wallpaper.
-    /// </summary>
-    /// <param name="path">Path of the wallpaper</param>
-    /// <param name="style">Wallpaper style</param>
-    private static void SetDesktopWallpaper(string path, WallpaperStyle style)
-    {
-        // Set the wallpaper style and tile.
-        // Two registry values are set in the Control Panel\Desktop key.
-        // TileWallpaper
-        //  0: The wallpaper picture should not be tiled
-        //  1: The wallpaper picture should be tiled
-        // WallpaperStyle
-        //  0:  The image is centered if TileWallpaper=0 or tiled if TileWallpaper=1
-        //  2:  The image is stretched to fill the screen
-        //  6:  The image is resized to fit the screen while maintaining the aspect
-        //      ratio. (Windows 7 and later)
-        //  10: The image is resized and cropped to fill the screen while
-        //      maintaining the aspect ratio. (Windows 7 and later)
-#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
-        var key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Desktop", true);
-#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
-
-        switch (style)
-        {
-            case WallpaperStyle.Tile:
-                key.SetValue(@"WallpaperStyle", "0");
-                key.SetValue(@"TileWallpaper", "1");
-                break;
-
-            case WallpaperStyle.Center:
-                key.SetValue(@"WallpaperStyle", "0");
-                key.SetValue(@"TileWallpaper", "0");
-                break;
-
-            case WallpaperStyle.Stretch:
-                key.SetValue(@"WallpaperStyle", "2");
-                key.SetValue(@"TileWallpaper", "0");
-                break;
-
-            case WallpaperStyle.Fit: // (Windows 7 and later)
-                key.SetValue(@"WallpaperStyle", "6");
-                key.SetValue(@"TileWallpaper", "0");
-                break;
-
-            default:
-            case WallpaperStyle.Fill: // (Windows 7 and later)
-                key.SetValue(@"WallpaperStyle", "10");
-                key.SetValue(@"TileWallpaper", "0");
-                break;
-        }
-
-        key.Close();
-
-        // Set the desktop wallpaper by calling the Win32 API SystemParametersInfo
-        // with the SPI_SETDESKWALLPAPER desktop parameter. The changes should
-        // persist, and also be immediately visible.
-        if (!NativeMethods.SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, path,
-                SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE))
-        {
-            throw new Win32Exception();
-        }
-    }
-
-    private const uint SPI_SETDESKWALLPAPER = 20;
-    private const uint SPIF_UPDATEINIFILE = 0x01;
-    private const uint SPIF_SENDWININICHANGE = 0x02;
 }

+ 2 - 1
src/PicView.WPF/UILogic/Loading/LoadContextMenus.cs

@@ -12,6 +12,7 @@ using System.Windows.Controls;
 using System.Windows.Input;
 using PicView.Core.Localization;
 using PicView.Core.Navigation;
+using PicView.Windows.Wallpaper;
 using static PicView.WPF.ChangeImage.Navigation;
 using static PicView.WPF.FileHandling.OpenSave;
 using static PicView.WPF.UILogic.ConfigureWindows;
@@ -282,7 +283,7 @@ internal static class LoadContextMenus
         SetWallpaperCm.Click += async (_, _) =>
         {
             MainContextMenu.IsOpen = false;
-            await Wallpaper.SetWallpaperAsync(Wallpaper.WallpaperStyle.Fill).ConfigureAwait(false);
+            await Wallpaper.SetWallpaperAsync(WallpaperHelper.WallpaperStyle.Fill).ConfigureAwait(false);
         };
         SetWallpaperCm.Header = TranslationHelper.GetTranslation("SetAsWallpaper");
         var SetLockCm = (MenuItem)SetAsCm.Items[1];

+ 14 - 14
src/PicView.WPF/UILogic/Loading/StartLoading.cs

@@ -41,18 +41,6 @@ internal static class StartLoading
         }
 
         var args = Environment.GetCommandLineArgs();
-        if (args.Length > 1)
-        {
-            // Apply lockscreen and close
-            var arg = args[1];
-            if (arg.StartsWith("lockscreen"))
-            {
-                var path = arg[(arg.LastIndexOf(',') + 1)..];
-                path = Path.GetFullPath(path);
-                LockScreenHelper.SetLockScreenImage(path);
-                Environment.Exit(0);
-            }
-        }
 
         if (SettingsHelper.Settings.UIProperties.OpenInSameWindow)
         {
@@ -77,6 +65,19 @@ internal static class StartLoading
             }
         }
 
+        if (args.Length > 1)
+        {
+            // Apply lockscreen and close
+            var arg = args[1];
+            if (arg.StartsWith("lockscreen"))
+            {
+                var path = arg[(arg.LastIndexOf(',') + 1)..];
+                path = Path.GetFullPath(path);
+                Windows.Lockscreen.LockscreenHelper.SetLockScreenImage(path);
+                Environment.Exit(0);
+            }
+        }
+
         MainWindow? mainWindow = null;
         var language = SettingsHelper.Settings.UIProperties.UserLanguage;
         await Core.Localization.TranslationHelper.LoadLanguage(language).ConfigureAwait(false);
@@ -182,6 +183,7 @@ internal static class StartLoading
         try
         {
             await mainWindow.Dispatcher.InvokeAsync(startupWindow.Close);
+            ConfigColors.UpdateColor(); // Try catch to prevent task canceled exception
         }
         catch (Exception e)
         {
@@ -189,8 +191,6 @@ internal static class StartLoading
             Trace.WriteLine($"{nameof(LoadedEvent)}: {nameof(startupWindow)} exception,\n{e.Message}");
 #endif
         }
-
-        ConfigColors.UpdateColor();
     }
 
     internal static void AddDictionaries()

+ 2 - 1
src/PicView.WPF/Views/UserControls/Gallery/PicGalleryItem.xaml.cs

@@ -12,6 +12,7 @@ using PicView.Core.Localization;
 using PicView.WPF.SystemIntegration;
 using static PicView.WPF.PicGallery.GalleryNavigation;
 using PicView.Core.FileHandling;
+using PicView.Windows.Wallpaper;
 
 namespace PicView.WPF.Views.UserControls.Gallery;
 
@@ -141,7 +142,7 @@ public partial class PicGalleryItem
             }
         };
         setAsWallpaperMenu.Click += async (_, _) =>
-            await Wallpaper.SetWallpaperAsync(Wallpaper.WallpaperStyle.Fill, FilePath)
+            await Wallpaper.SetWallpaperAsync(WallpaperHelper.WallpaperStyle.Fill, FilePath)
                 .ConfigureAwait(false);
         cm.Items.Add(setAsWallpaperMenu);
 

+ 6 - 5
src/PicView.WPF/Views/Windows/EffectsWindows.xaml.cs

@@ -6,6 +6,7 @@ using PicView.WPF.ConfigureSettings;
 using PicView.WPF.FileHandling;
 using PicView.Core.Config;
 using PicView.Core.Localization;
+using PicView.Windows.Wallpaper;
 using PicView.WPF.Shortcuts;
 using PicView.WPF.SystemIntegration;
 using PicView.WPF.UILogic;
@@ -177,25 +178,25 @@ public partial class EffectsWindow
         SetButtonIconMouseOverAnimations(SetAsWallpaperButton, SetAsWallpaperButtonBrush, SetAsWallpaperText);
         SetAsWallpaperButton.Click += async delegate
         {
-            var x = WallpaperStyle.Fill;
+            var x = WallpaperHelper.WallpaperStyle.Fill;
             if (Fit.IsSelected)
             {
-                x = WallpaperStyle.Fit;
+                x = WallpaperHelper.WallpaperStyle.Fit;
             }
 
             if (Center.IsSelected)
             {
-                x = WallpaperStyle.Center;
+                x = WallpaperHelper.WallpaperStyle.Center;
             }
 
             if (Tile.IsSelected)
             {
-                x = WallpaperStyle.Tile;
+                x = WallpaperHelper.WallpaperStyle.Tile;
             }
 
             if (Fit.IsSelected)
             {
-                x = WallpaperStyle.Fit;
+                x = WallpaperHelper.WallpaperStyle.Fit;
             }
 
             await SetWallpaperAsync(x).ConfigureAwait(false);

+ 31 - 0
src/PicView.Windows/Lockscreen/LockscreenHelper.cs

@@ -0,0 +1,31 @@
+using Microsoft.Win32;
+using System.Runtime.InteropServices;
+
+namespace PicView.Windows.Lockscreen;
+
+public static class LockscreenHelper
+{
+    [DllImport("kernel32.dll", SetLastError = true)]
+    public static extern bool Wow64DisableWow64FsRedirection(ref IntPtr ptr); //If on 64 bit, C# will replace "System32" with "SysWOW64". This disables that.
+
+    public static bool SetLockScreenImage(string path)
+    {
+        const string registryKey =
+            @"HKEY_LOCAL_MACHINE\SOFTWARE\Microsoft\Windows\CurrentVersion\PersonalizationCSP";
+        var ptr = new IntPtr();
+        Wow64DisableWow64FsRedirection(ref ptr);
+
+        try
+        {
+            Registry.SetValue(registryKey, "LockScreenImageStatus", 1, RegistryValueKind.DWord);
+            Registry.SetValue(registryKey, "LockScreenImagePath", path, RegistryValueKind.String);
+            Registry.SetValue(registryKey, "LockScreenImageUrl", path, RegistryValueKind.String);
+        }
+        catch
+        {
+            return false;
+        }
+
+        return true;
+    }
+}

+ 24 - 0
src/PicView.Windows/PicView.Windows.csproj

@@ -0,0 +1,24 @@
+<Project Sdk="Microsoft.NET.Sdk">
+
+  <PropertyGroup>
+    <TargetFramework>net8.0-windows10.0.22621.0</TargetFramework>
+    <ImplicitUsings>enable</ImplicitUsings>
+    <Nullable>enable</Nullable>
+    <PlatformTarget>x64</PlatformTarget>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|AnyCPU'">
+    <IsTrimmable>True</IsTrimmable>
+    <IsAotCompatible>True</IsAotCompatible>
+  </PropertyGroup>
+
+  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
+    <IsTrimmable>True</IsTrimmable>
+    <IsAotCompatible>True</IsAotCompatible>
+  </PropertyGroup>
+
+  <ItemGroup>
+    <Folder Include="Copy\" />
+  </ItemGroup>
+
+</Project>

+ 91 - 0
src/PicView.Windows/Wallpaper/WallpaperHelper.cs

@@ -0,0 +1,91 @@
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+using Microsoft.Win32;
+
+namespace PicView.Windows.Wallpaper;
+
+public static class WallpaperHelper
+{
+    public enum WallpaperStyle
+    {
+        Tile,
+        Center,
+        Stretch,
+        Fit,
+        Fill
+    }
+
+    // Used to check for wallpaper support
+    [DllImport("user32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
+    [return: MarshalAs(UnmanagedType.Bool)]
+    internal static extern bool SystemParametersInfo(uint uiAction, uint uiParam,
+        string pvParam, uint fWinIni);
+
+    /// <summary>
+    /// Set the desktop wallpaper.
+    /// </summary>
+    /// <param name="path">Path of the wallpaper</param>
+    /// <param name="style">Wallpaper style</param>
+    public static void SetDesktopWallpaper(string path, WallpaperStyle style)
+    {
+        // Set the wallpaper style and tile.
+        // Two registry values are set in the Control Panel\Desktop key.
+        // TileWallpaper
+        //  0: The wallpaper picture should not be tiled
+        //  1: The wallpaper picture should be tiled
+        // WallpaperStyle
+        //  0:  The image is centered if TileWallpaper=0 or tiled if TileWallpaper=1
+        //  2:  The image is stretched to fill the screen
+        //  6:  The image is resized to fit the screen while maintaining the aspect
+        //      ratio. (Windows 7 and later)
+        //  10: The image is resized and cropped to fill the screen while
+        //      maintaining the aspect ratio. (Windows 7 and later)
+#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type.
+        var key = Registry.CurrentUser.OpenSubKey(@"Control Panel\Desktop", true);
+#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type.
+
+        switch (style)
+        {
+            case WallpaperStyle.Tile:
+                key.SetValue(@"WallpaperStyle", "0");
+                key.SetValue(@"TileWallpaper", "1");
+                break;
+
+            case WallpaperStyle.Center:
+                key.SetValue(@"WallpaperStyle", "0");
+                key.SetValue(@"TileWallpaper", "0");
+                break;
+
+            case WallpaperStyle.Stretch:
+                key.SetValue(@"WallpaperStyle", "2");
+                key.SetValue(@"TileWallpaper", "0");
+                break;
+
+            case WallpaperStyle.Fit: // (Windows 7 and later)
+                key.SetValue(@"WallpaperStyle", "6");
+                key.SetValue(@"TileWallpaper", "0");
+                break;
+
+            default:
+            case WallpaperStyle.Fill: // (Windows 7 and later)
+                key.SetValue(@"WallpaperStyle", "10");
+                key.SetValue(@"TileWallpaper", "0");
+                break;
+        }
+
+        key.Close();
+
+        // Set the desktop wallpaper by calling the Win32 API SystemParametersInfo
+        // with the SPI_SETDESKWALLPAPER desktop parameter. The changes should
+        // persist, and also be immediately visible.
+        if (!SystemParametersInfo(SPI_SETDESKWALLPAPER, 0, path,
+                SPIF_UPDATEINIFILE | SPIF_SENDWININICHANGE))
+        {
+            throw new Win32Exception();
+        }
+    }
+
+    private const uint SPI_SETDESKWALLPAPER = 20;
+    private const uint SPIF_UPDATEINIFILE = 0x01;
+    private const uint SPIF_SENDWININICHANGE = 0x02;
+}

+ 29 - 5
src/PicView.sln

@@ -9,7 +9,15 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "XamlAnimatedGif", "XamlAnim
 EndProject
 Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PicView.Core", "PicView.Core\PicView.Core.csproj", "{17C701BC-1F25-4995-A7E5-1360EBC5904B}"
 EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PicView.Avalonia", "PicView.Avalonia\PicView.Avalonia.csproj", "{E36E03B2-6CAB-407F-AD72-411F43CFC5A8}"
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PicView.Avalonia", "PicView.Avalonia\PicView.Avalonia.csproj", "{83A0AA39-97B2-453B-BDAB-3DB4D8684D3B}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PicView.Avalonia.Win32", "PicView.Avalonia.Win32\PicView.Avalonia.Win32.csproj", "{CDE55370-A53E-43C1-9FAB-B2973462A3C6}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PicView.Avalonia.MacOS", "PicView.Avalonia.MacOS\PicView.Avalonia.MacOS.csproj", "{79402A1F-0EDE-49C8-970A-E7AC0065FFB4}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PicView.Windows", "PicView.Windows\PicView.Windows.csproj", "{D63E2F56-02CC-4E23-AF78-F5218E83B2E6}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PicView.MacOS", "PicView.MacOS\PicView.MacOS.csproj", "{FEAC727E-1102-4F11-A747-5A5789FBBF81}"
 EndProject
 Global
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
@@ -29,10 +37,26 @@ Global
 		{17C701BC-1F25-4995-A7E5-1360EBC5904B}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{17C701BC-1F25-4995-A7E5-1360EBC5904B}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{17C701BC-1F25-4995-A7E5-1360EBC5904B}.Release|Any CPU.Build.0 = Release|Any CPU
-		{E36E03B2-6CAB-407F-AD72-411F43CFC5A8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{E36E03B2-6CAB-407F-AD72-411F43CFC5A8}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{E36E03B2-6CAB-407F-AD72-411F43CFC5A8}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{E36E03B2-6CAB-407F-AD72-411F43CFC5A8}.Release|Any CPU.Build.0 = Release|Any CPU
+		{83A0AA39-97B2-453B-BDAB-3DB4D8684D3B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{83A0AA39-97B2-453B-BDAB-3DB4D8684D3B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{83A0AA39-97B2-453B-BDAB-3DB4D8684D3B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{83A0AA39-97B2-453B-BDAB-3DB4D8684D3B}.Release|Any CPU.Build.0 = Release|Any CPU
+		{CDE55370-A53E-43C1-9FAB-B2973462A3C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{CDE55370-A53E-43C1-9FAB-B2973462A3C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{CDE55370-A53E-43C1-9FAB-B2973462A3C6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{CDE55370-A53E-43C1-9FAB-B2973462A3C6}.Release|Any CPU.Build.0 = Release|Any CPU
+		{79402A1F-0EDE-49C8-970A-E7AC0065FFB4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{79402A1F-0EDE-49C8-970A-E7AC0065FFB4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{79402A1F-0EDE-49C8-970A-E7AC0065FFB4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{79402A1F-0EDE-49C8-970A-E7AC0065FFB4}.Release|Any CPU.Build.0 = Release|Any CPU
+		{D63E2F56-02CC-4E23-AF78-F5218E83B2E6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{D63E2F56-02CC-4E23-AF78-F5218E83B2E6}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{D63E2F56-02CC-4E23-AF78-F5218E83B2E6}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{D63E2F56-02CC-4E23-AF78-F5218E83B2E6}.Release|Any CPU.Build.0 = Release|Any CPU
+		{FEAC727E-1102-4F11-A747-5A5789FBBF81}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+		{FEAC727E-1102-4F11-A747-5A5789FBBF81}.Debug|Any CPU.Build.0 = Debug|Any CPU
+		{FEAC727E-1102-4F11-A747-5A5789FBBF81}.Release|Any CPU.ActiveCfg = Release|Any CPU
+		{FEAC727E-1102-4F11-A747-5A5789FBBF81}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 1 - 4
src/XamlAnimatedGif/Decoding/GifDecoderException.cs

@@ -13,10 +13,7 @@ public abstract class GifDecoderException : Exception
     {
     }
 
-    protected GifDecoderException(
-        SerializationInfo info,
-        StreamingContext context)
-        : base(info, context)
+    protected GifDecoderException(SerializationInfo info, StreamingContext context)
     {
     }
 }