diff --git a/.gitignore b/.gitignore index 13219c40..d0650bd4 100644 --- a/.gitignore +++ b/.gitignore @@ -13,3 +13,6 @@ src/Snap.Hutao/Snap.Hutao/Snap.Hutao_TemporaryKey.pfx src/Snap.Hutao/Snap.Hutao.SourceGeneration/bin/ src/Snap.Hutao/Snap.Hutao.SourceGeneration/obj/ + +src/Snap.Hutao/Snap.Hutao.Win32/bin/ +src/Snap.Hutao/Snap.Hutao.Win32/obj/ \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.json b/src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.json new file mode 100644 index 00000000..1694d869 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.json @@ -0,0 +1,5 @@ +{ + "$schema": "https://aka.ms/CsWin32.schema.json", + "allowMarshaling": true, + "public": true +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.txt b/src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.txt new file mode 100644 index 00000000..b128e994 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao.Win32/NativeMethods.txt @@ -0,0 +1,16 @@ +// ComCtl32 +SetWindowSubclass +RemoveWindowSubclass +DefSubclassProc + +// User32 +SetWindowText +GetDpiForWindow +GetWindowPlacement +SetWindowPlacement + +// Type definition +MINMAXINFO + +// Const value +WM_GETMINMAXINFO \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao.Win32/Snap.Hutao.Win32.csproj b/src/Snap.Hutao/Snap.Hutao.Win32/Snap.Hutao.Win32.csproj new file mode 100644 index 00000000..0d9fe8b5 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao.Win32/Snap.Hutao.Win32.csproj @@ -0,0 +1,15 @@ + + + + net6.0 + enable + enable + + + + + all + + + + diff --git a/src/Snap.Hutao/Snap.Hutao.sln b/src/Snap.Hutao/Snap.Hutao.sln index c5c47ce6..77abf5cc 100644 --- a/src/Snap.Hutao/Snap.Hutao.sln +++ b/src/Snap.Hutao/Snap.Hutao.sln @@ -14,6 +14,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "SettingsUI", "SettingsUI\Se EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Snap.Hutao.SourceGeneration", "Snap.Hutao.SourceGeneration\Snap.Hutao.SourceGeneration.csproj", "{8B96721E-5604-47D2-9B72-06FEBAD0CE00}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Snap.Hutao.Win32", "Snap.Hutao.Win32\Snap.Hutao.Win32.csproj", "{29209B14-A6E1-442E-9287-2C65B03C96CD}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -82,6 +84,22 @@ Global {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x64.Build.0 = Release|x64 {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.ActiveCfg = Release|Any CPU {8B96721E-5604-47D2-9B72-06FEBAD0CE00}.Release|x86.Build.0 = Release|Any CPU + {29209B14-A6E1-442E-9287-2C65B03C96CD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {29209B14-A6E1-442E-9287-2C65B03C96CD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {29209B14-A6E1-442E-9287-2C65B03C96CD}.Debug|arm64.ActiveCfg = Debug|Any CPU + {29209B14-A6E1-442E-9287-2C65B03C96CD}.Debug|arm64.Build.0 = Debug|Any CPU + {29209B14-A6E1-442E-9287-2C65B03C96CD}.Debug|x64.ActiveCfg = Debug|Any CPU + {29209B14-A6E1-442E-9287-2C65B03C96CD}.Debug|x64.Build.0 = Debug|Any CPU + {29209B14-A6E1-442E-9287-2C65B03C96CD}.Debug|x86.ActiveCfg = Debug|Any CPU + {29209B14-A6E1-442E-9287-2C65B03C96CD}.Debug|x86.Build.0 = Debug|Any CPU + {29209B14-A6E1-442E-9287-2C65B03C96CD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {29209B14-A6E1-442E-9287-2C65B03C96CD}.Release|Any CPU.Build.0 = Release|Any CPU + {29209B14-A6E1-442E-9287-2C65B03C96CD}.Release|arm64.ActiveCfg = Release|Any CPU + {29209B14-A6E1-442E-9287-2C65B03C96CD}.Release|arm64.Build.0 = Release|Any CPU + {29209B14-A6E1-442E-9287-2C65B03C96CD}.Release|x64.ActiveCfg = Release|Any CPU + {29209B14-A6E1-442E-9287-2C65B03C96CD}.Release|x64.Build.0 = Release|Any CPU + {29209B14-A6E1-442E-9287-2C65B03C96CD}.Release|x86.ActiveCfg = Release|Any CPU + {29209B14-A6E1-442E-9287-2C65B03C96CD}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/src/Snap.Hutao/Snap.Hutao/App.xaml.cs b/src/Snap.Hutao/Snap.Hutao/App.xaml.cs index cf45d67b..84626a97 100644 --- a/src/Snap.Hutao/Snap.Hutao/App.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/App.xaml.cs @@ -28,7 +28,6 @@ public partial class App : Application /// public App() { - AppInstance.GetCurrent().Activated += OnActivated; // load app resource InitializeComponent(); InitializeDependencyInjection(); @@ -79,14 +78,10 @@ public partial class App : Application AppActivationArguments activatedEventArgs = AppInstance.GetCurrent().GetActivatedEventArgs(); AppInstance firstInstance = AppInstance.FindOrRegisterForKey("main"); - if (!firstInstance.IsCurrent) - { - // Redirect the activation (and args) to the "main" instance, and exit. - await firstInstance.RedirectActivationToAsync(activatedEventArgs); - Process.GetCurrentProcess().Kill(); - } - else + if (firstInstance.IsCurrent) { + firstInstance.Activated += OnActivated; + Window = Ioc.Default.GetRequiredService(); Window.Activate(); @@ -94,10 +89,16 @@ public partial class App : Application Ioc.Default .GetRequiredService() - .As()? + .ImplictAs()? .InitializeInternalAsync() .SafeForget(logger); } + else + { + // Redirect the activation (and args) to the "main" instance, and exit. + await firstInstance.RedirectActivationToAsync(activatedEventArgs); + Process.GetCurrentProcess().Kill(); + } } private static void InitializeDependencyInjection() diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Backdroping/SystemBackdrop.cs b/src/Snap.Hutao/Snap.Hutao/Control/Backdroping/SystemBackdrop.cs index 42c6e645..cc096891 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Backdroping/SystemBackdrop.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Backdroping/SystemBackdrop.cs @@ -30,12 +30,6 @@ public class SystemBackdrop this.window = window; } - private enum BackDropType - { - None, - Mica, - } - /// /// 尝试设置背景 /// @@ -63,8 +57,7 @@ public class SystemBackdrop backdropController = new MicaController(); - ICompositionSupportsSystemBackdrop target = window.As(); - backdropController.AddSystemBackdropTarget(target); + backdropController.AddSystemBackdropTarget(window.As()); backdropController.SetSystemBackdropConfiguration(configurationSource); return true; diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs b/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs index 36bae9f7..caef3ac6 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Image/CompositionImage.cs @@ -64,18 +64,11 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control /// 文件 /// 取消令牌 /// 加载的图像表面 - protected virtual async Task LoadImageSurfaceAsync(StorageFile storageFile, CancellationToken token) + protected virtual async Task LoadImageSurfaceAsync(StorageFile storageFile, CancellationToken token) { - try + using (IRandomAccessStream imageStream = await storageFile.OpenAsync(FileAccessMode.Read).AsTask(token)) { - using (IRandomAccessStream imageStream = await storageFile.OpenAsync(FileAccessMode.Read).AsTask(token)) - { - return LoadedImageSurface.StartLoadFromStream(imageStream); - } - } - catch (COMException ex) when (ex.Is(COMError.WINCODEC_ERR_COMPONENTNOTFOUND)) - { - return null; + return LoadedImageSurface.StartLoadFromStream(imageStream); } } @@ -130,7 +123,19 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control Compositor compositor = ElementCompositionPreview.GetElementVisual(this).Compositor; - if (await LoadImageSurfaceAsync(storageFile, token) is LoadedImageSurface imageSurface) + LoadedImageSurface? imageSurface = null; + + try + { + imageSurface = await LoadImageSurfaceAsync(storageFile, token); + } + catch (COMException ex) when (ex.Is(COMError.WINCODEC_ERR_COMPONENTNOTFOUND)) + { + // Image is broken, remove it + await imageCache.RemoveAsync(uri.Enumerate()); + } + + if (imageSurface != null) { spriteVisual = CompositeSpriteVisual(compositor, imageSurface); OnUpdateVisual(spriteVisual); @@ -139,11 +144,6 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control await AnimationBuilder.Create().Opacity(1d).StartAsync(this, token); } - else - { - // Image is broken, remove it - await imageCache.RemoveAsync(uri.Enumerate()); - } } } diff --git a/src/Snap.Hutao/Snap.Hutao/Control/Image/Gradient.cs b/src/Snap.Hutao/Snap.Hutao/Control/Image/Gradient.cs index c94d282f..b46d0fc6 100644 --- a/src/Snap.Hutao/Snap.Hutao/Control/Image/Gradient.cs +++ b/src/Snap.Hutao/Snap.Hutao/Control/Image/Gradient.cs @@ -30,7 +30,7 @@ public class Gradient : CompositionImage } /// - protected override async Task LoadImageSurfaceAsync(StorageFile storageFile, CancellationToken token) + protected override async Task LoadImageSurfaceAsync(StorageFile storageFile, CancellationToken token) { using (IRandomAccessStream imageStream = await storageFile.OpenAsync(FileAccessMode.Read).AsTask(token)) { diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Win32/POINT.cs b/src/Snap.Hutao/Snap.Hutao/Core/Win32/POINT.cs deleted file mode 100644 index b69a64c9..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Core/Win32/POINT.cs +++ /dev/null @@ -1,30 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using System.Runtime.InteropServices; - -namespace Snap.Hutao.Core.Win32; - -[SuppressMessage("", "SA1600")] -[StructLayout(LayoutKind.Sequential)] -public struct POINT -{ - public int X; - public int Y; - - public POINT(int x, int y) - { - X = x; - Y = y; - } - - public static implicit operator System.Drawing.Point(POINT p) - { - return new System.Drawing.Point(p.X, p.Y); - } - - public static implicit operator POINT(System.Drawing.Point p) - { - return new POINT(p.X, p.Y); - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Win32/RECT.cs b/src/Snap.Hutao/Snap.Hutao/Core/Win32/RECT.cs deleted file mode 100644 index 91ae720e..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Core/Win32/RECT.cs +++ /dev/null @@ -1,134 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using System.Globalization; -using System.Runtime.InteropServices; - -namespace Snap.Hutao.Core.Win32; - -[SuppressMessage("", "SA1132")] -[SuppressMessage("", "SA1600")] -[StructLayout(LayoutKind.Sequential)] -public struct RECT -{ - public int Left, Top, Right, Bottom; - - public RECT(int left, int top, int right, int bottom) - { - Left = left; - Top = top; - Right = right; - Bottom = bottom; - } - - public RECT(System.Drawing.Rectangle r) - : this(r.Left, r.Top, r.Right, r.Bottom) - { - } - - public int X - { - get => Left; - set - { - Right -= Left - value; - Left = value; - } - } - - public int Y - { - get => Top; - set - { - Bottom -= Top - value; - Top = value; - } - } - - public int Height - { - get => Bottom - Top; - set => Bottom = value + Top; - } - - public int Width - { - get => Right - Left; - set => Right = value + Left; - } - - public long Area - { - get => Math.BigMul(Width, Height); - } - - public System.Drawing.Point Location - { - get => new(Left, Top); - set - { - X = value.X; - Y = value.Y; - } - } - - public System.Drawing.Size Size - { - get => new(Width, Height); - set - { - Width = value.Width; - Height = value.Height; - } - } - - public static implicit operator System.Drawing.Rectangle(RECT r) - { - return new System.Drawing.Rectangle(r.Left, r.Top, r.Width, r.Height); - } - - public static implicit operator RECT(System.Drawing.Rectangle r) - { - return new RECT(r); - } - - public static bool operator ==(RECT r1, RECT r2) - { - return r1.Equals(r2); - } - - public static bool operator !=(RECT r1, RECT r2) - { - return !r1.Equals(r2); - } - - public bool Equals(RECT r) - { - return r.Left == Left && r.Top == Top && r.Right == Right && r.Bottom == Bottom; - } - - public override bool Equals(object? obj) - { - if (obj is RECT rect) - { - return Equals(rect); - } - else if (obj is System.Drawing.Rectangle rectangle) - { - return Equals(new RECT(rectangle)); - } - - return false; - } - - public override int GetHashCode() - { - return ((System.Drawing.Rectangle)this).GetHashCode(); - } - - public override string ToString() - { - return string.Format(CultureInfo.CurrentCulture, "{{Left={0},Top={1},Right={2},Bottom={3}}}", Left, Top, Right, Bottom); - } -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Win32/ShowWindowCommand.cs b/src/Snap.Hutao/Snap.Hutao/Core/Win32/ShowWindowCommand.cs deleted file mode 100644 index a0821434..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Core/Win32/ShowWindowCommand.cs +++ /dev/null @@ -1,89 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -namespace Snap.Hutao.Core.Win32; - -[SuppressMessage("", "SA1600")] -public enum ShowWindowCommand -{ - /// - /// Hides the window and activates another window. - /// - Hide = 0, - - /// - /// Activates and displays a window. If the window is minimized or - /// maximized, the system restores it to its original size and position. - /// An application should specify this flag when displaying the window - /// for the first time. - /// - Normal = 1, - - /// - /// Activates the window and displays it as a minimized window. - /// - ShowMinimized = 2, - - /// - /// Maximizes the specified window. - /// - Maximize = 3, // is this the right value? - - /// - /// Activates the window and displays it as a maximized window. - /// - ShowMaximized = 3, - - /// - /// Displays a window in its most recent size and position. This value - /// is similar to , except - /// the window is not activated. - /// - ShowNoActivate = 4, - - /// - /// Activates the window and displays it in its current size and position. - /// - Show = 5, - - /// - /// Minimizes the specified window and activates the next top-level - /// window in the Z order. - /// - Minimize = 6, - - /// - /// Displays the window as a minimized window. This value is similar to - /// , except the - /// window is not activated. - /// - ShowMinNoActive = 7, - - /// - /// Displays the window in its current size and position. This value is - /// similar to , except the - /// window is not activated. - /// - ShowNA = 8, - - /// - /// Activates and displays the window. If the window is minimized or - /// maximized, the system restores it to its original size and position. - /// An application should specify this flag when restoring a minimized window. - /// - Restore = 9, - - /// - /// Sets the show state based on the SW_* value specified in the - /// STARTUPINFO structure passed to the CreateProcess function by the - /// program that started the application. - /// - ShowDefault = 10, - - /// - /// Windows 2000/XP: Minimizes a window, even if the thread - /// that owns the window is not responding. This flag should only be - /// used when minimizing windows from a different thread. - /// - ForceMinimize = 11, -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Win32/User32.cs b/src/Snap.Hutao/Snap.Hutao/Core/Win32/User32.cs deleted file mode 100644 index 69ff9d9f..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Core/Win32/User32.cs +++ /dev/null @@ -1,65 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using System.Runtime.InteropServices; - -namespace Snap.Hutao.Core.Win32; - -/// -/// 包含 user32.dll 平台调用的代码 -/// -internal static class User32 -{ - [SuppressMessage("", "SA1600")] - [DllImport("user32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool GetWindowPlacement(IntPtr hWnd, ref WINDOWPLACEMENT lpwndpl); - - /// - /// Sets the show state and the restored, minimized, and maximized positions of the specified window. - /// - /// - /// A handle to the window. - /// - /// - /// A pointer to a WINDOWPLACEMENT structure that specifies the new show state and window positions. - /// - /// Before calling SetWindowPlacement, set the length member of the WINDOWPLACEMENT structure to sizeof(WINDOWPLACEMENT). SetWindowPlacement fails if the length member is not set correctly. - /// - /// - /// - /// If the function succeeds, the return value is nonzero. - /// - /// If the function fails, the return value is zero. To get extended error information, call GetLastError. - /// - /// - [DllImport("user32.dll", SetLastError = true)] - [return: MarshalAs(UnmanagedType.Bool)] - public static extern bool SetWindowPlacement(IntPtr hWnd, [In] ref WINDOWPLACEMENT lpwndpl); - - /// - /// Changes the text of the specified window's title bar (if it has one). If the specified window is a control, the - /// text of the control is changed. However, SetWindowText cannot change the text of a control in another application. - /// - /// Go to for more - /// information - /// - /// - /// C++ ( hWnd [in]. Type: HWND )
A handle to the window or control whose text is to be changed. - /// C++ ( lpString [in, optional]. Type: LPCTSTR )
The new title or control text. - /// - /// If the function succeeds, the return value is nonzero. If the function fails, the return value is zero.
- /// To get extended error information, call GetLastError. - ///
- /// - /// If the target window is owned by the current process, causes a WM_SETTEXT message to - /// be sent to the specified window or control. If the control is a list box control created with the WS_CAPTION style, - /// however, sets the text for the control, not for the list box entries.
To set the - /// text of a control in another process, send the WM_SETTEXT message directly instead of calling - /// . The function does not expand tab characters (ASCII code - /// 0x09). Tab characters are displayed as vertical bar(|) characters.
For an example go to - /// Sending a Message. - ///
- [DllImport("user32.dll", SetLastError = true, CharSet = CharSet.Auto)] - public static extern bool SetWindowText(IntPtr hwnd, string lpString); -} diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Win32/WINDOWPLACEMENT.cs b/src/Snap.Hutao/Snap.Hutao/Core/Win32/WINDOWPLACEMENT.cs deleted file mode 100644 index 0d10be88..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Core/Win32/WINDOWPLACEMENT.cs +++ /dev/null @@ -1,78 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using System.Runtime.InteropServices; - -namespace Snap.Hutao.Core.Win32; - -/// -/// Contains information about the placement of a window on the screen. -/// -[Serializable] -[StructLayout(LayoutKind.Sequential)] -internal struct WINDOWPLACEMENT -{ - /// - /// The length of the structure, in bytes. Before calling the GetWindowPlacement or SetWindowPlacement functions, set this member to sizeof(WINDOWPLACEMENT). - /// - /// GetWindowPlacement and SetWindowPlacement fail if this member is not set correctly. - /// - /// - public int Length; - - /// - /// Specifies flags that control the position of the minimized window and the method by which the window is restored. - /// - public int Flags; - - /// - /// The current show state of the window. - /// - public ShowWindowCommand ShowCmd; - - /// - /// The coordinates of the window's upper-left corner when the window is minimized. - /// - public POINT MinPosition; - - /// - /// The coordinates of the window's upper-left corner when the window is maximized. - /// - public POINT MaxPosition; - - /// - /// The window's coordinates when the window is in the restored position. - /// - public RECT NormalPosition; - - /// - /// Gets the default (empty) value. - /// - public static WINDOWPLACEMENT Default - { - get - { - WINDOWPLACEMENT result = default(WINDOWPLACEMENT); - result.Length = Marshal.SizeOf(result); - return result; - } - } - - /// - /// 构造一个新的 - /// - /// 最大点 - /// 正常位置 - /// 显示命令 - /// 窗体位置 - public static WINDOWPLACEMENT Create(POINT max, RECT normal, ShowWindowCommand command) - { - WINDOWPLACEMENT result = Default; - - result.MaxPosition = max; - result.NormalPosition = normal; - result.ShowCmd = command; - - return result; - } -} diff --git a/src/Snap.Hutao/Snap.Hutao/Core/WindowManager.cs b/src/Snap.Hutao/Snap.Hutao/Core/WindowManager.cs index ecaddc7a..74c36215 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/WindowManager.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/WindowManager.cs @@ -5,21 +5,33 @@ using Microsoft.UI.Xaml; using Snap.Hutao.Control.HostBackdrop; using Snap.Hutao.Core.Logging; using Snap.Hutao.Core.Setting; -using Snap.Hutao.Core.Win32; +using System.Runtime.InteropServices; +using Windows.Win32; +using Windows.Win32.Foundation; +using Windows.Win32.UI.Shell; +using Windows.Win32.UI.WindowsAndMessaging; using WinRT.Interop; namespace Snap.Hutao.Core; /// /// 窗口状态管理器 +/// 主要包含了各类 P/Inoke 代码 /// internal class WindowManager { - private readonly IntPtr handle; + private const int MinWidth = 800; + private const int MinHeight = 600; + + private readonly HWND handle; private readonly Window window; private readonly UIElement titleBar; private readonly ILogger logger; + // We have to explictly hold a reference to the SUBCLASSPROC + // otherwise will casuse System.ExecutionEngineException + private SUBCLASSPROC? subClassProc; + /// /// 构造一个新的窗口状态管理器 /// @@ -30,9 +42,7 @@ internal class WindowManager this.window = window; this.titleBar = titleBar; logger = Ioc.Default.GetRequiredService>(); - - handle = WindowNative.GetWindowHandle(window); - + handle = (HWND)WindowNative.GetWindowHandle(window); InitializeWindow(); } @@ -43,18 +53,22 @@ internal class WindowManager int right = LocalSetting.GetValueType(SettingKeys.WindowRight); int bottom = LocalSetting.GetValueType(SettingKeys.WindowBottom); - return new RECT(left, top, right, bottom); + return new() { left = left, top = top, right = right, bottom = bottom }; } - private static void SaveWindowRect(IntPtr handle) + private static void SaveWindowRect(HWND handle) { - WINDOWPLACEMENT windowPlacement = WINDOWPLACEMENT.Default; - User32.GetWindowPlacement(handle, ref windowPlacement); + WINDOWPLACEMENT windowPlacement = new() + { + length = (uint)Marshal.SizeOf(), + }; - LocalSetting.Set(SettingKeys.WindowLeft, windowPlacement.NormalPosition.Left); - LocalSetting.Set(SettingKeys.WindowTop, windowPlacement.NormalPosition.Top); - LocalSetting.Set(SettingKeys.WindowRight, windowPlacement.NormalPosition.Right); - LocalSetting.Set(SettingKeys.WindowBottom, windowPlacement.NormalPosition.Bottom); + PInvoke.GetWindowPlacement(handle, ref windowPlacement); + + LocalSetting.Set(SettingKeys.WindowLeft, windowPlacement.rcNormalPosition.left); + LocalSetting.Set(SettingKeys.WindowTop, windowPlacement.rcNormalPosition.top); + LocalSetting.Set(SettingKeys.WindowRight, windowPlacement.rcNormalPosition.right); + LocalSetting.Set(SettingKeys.WindowBottom, windowPlacement.rcNormalPosition.bottom); } private void InitializeWindow() @@ -63,20 +77,52 @@ internal class WindowManager window.SetTitleBar(titleBar); window.Closed += OnWindowClosed; - User32.SetWindowText(handle, "胡桃"); + PInvoke.SetWindowText(handle, "胡桃"); RECT rect = RetriveWindowRect(); - if (rect.Area > 0) + if ((rect.right - rect.left) * (rect.bottom - rect.top) > 0) { - WINDOWPLACEMENT windowPlacement = WINDOWPLACEMENT.Create(new POINT(-1, -1), rect, ShowWindowCommand.Normal); - User32.SetWindowPlacement(handle, ref windowPlacement); + WINDOWPLACEMENT windowPlacement = new() + { + length = (uint)Marshal.SizeOf(), + showCmd = SHOW_WINDOW_CMD.SW_SHOWNORMAL, + ptMaxPosition = new() { x = -1, y = -1 }, + rcNormalPosition = rect, + }; + + PInvoke.SetWindowPlacement(handle, in windowPlacement); } bool micaApplied = new SystemBackdrop(window).TrySetBackdrop(); logger.LogInformation(EventIds.BackdropState, "Apply {name} : {result}", nameof(SystemBackdrop), micaApplied ? "succeed" : "failed"); + + subClassProc = new(OnWindowProcedure); + _ = PInvoke.SetWindowSubclass(handle, subClassProc, 101, 0); } private void OnWindowClosed(object sender, WindowEventArgs args) { + PInvoke.RemoveWindowSubclass(handle, subClassProc, 101); + subClassProc = null; SaveWindowRect(handle); } + + private LRESULT OnWindowProcedure(HWND hwnd, uint uMsg, WPARAM wParam, LPARAM lParam, nuint uIdSubclass, nuint dwRefData) + { + switch (uMsg) + { + case PInvoke.WM_GETMINMAXINFO: + { + uint dpi = PInvoke.GetDpiForWindow(handle); + float scalingFactor = dpi / 96f; + + MINMAXINFO minMaxInfo = Marshal.PtrToStructure(lParam); + minMaxInfo.ptMinTrackSize.x = (int)Math.Max(MinWidth * scalingFactor, minMaxInfo.ptMinTrackSize.x); + minMaxInfo.ptMinTrackSize.y = (int)Math.Max(MinHeight * scalingFactor, minMaxInfo.ptMinTrackSize.y); + Marshal.StructureToPtr(minMaxInfo, lParam, true); + break; + } + } + + return PInvoke.DefSubclassProc(hwnd, uMsg, wParam, lParam); + } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Extension/ObjectExtensions.cs b/src/Snap.Hutao/Snap.Hutao/Extension/ObjectExtensions.cs index 3440d8aa..abe6a04f 100644 --- a/src/Snap.Hutao/Snap.Hutao/Extension/ObjectExtensions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Extension/ObjectExtensions.cs @@ -14,7 +14,7 @@ public static class ObjectExtensions /// 目标转换类型 /// 对象 /// 转换类型后的对象 - public static T? As(this object? obj) + public static T? ImplictAs(this object? obj) where T : class { return obj as T; diff --git a/src/Snap.Hutao/Snap.Hutao/Model/Entity/User.cs b/src/Snap.Hutao/Snap.Hutao/Model/Entity/User.cs index aa4aea83..e2420325 100644 --- a/src/Snap.Hutao/Snap.Hutao/Model/Entity/User.cs +++ b/src/Snap.Hutao/Snap.Hutao/Model/Entity/User.cs @@ -28,15 +28,4 @@ public class User /// 用户的Cookie /// public string? Cookie { get; set; } - - /// - /// 设置用户的选中状态 - /// 同时更新用户选择的角色信息 - /// - /// 用户 - /// 是否选中 - public static void SetSelectionState(User user, bool isSelected) - { - user!.IsSelected = isSelected; - } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest index 2c886fd3..937607a1 100644 --- a/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest +++ b/src/Snap.Hutao/Snap.Hutao/Package.appxmanifest @@ -9,7 +9,7 @@ + Version="1.0.20.0" /> 胡桃 diff --git a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs index c91f656c..87fc1297 100644 --- a/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs +++ b/src/Snap.Hutao/Snap.Hutao/Service/User/UserService.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Snap.Hutao.Context.Database; +using Snap.Hutao.Model.Binding; using Snap.Hutao.Service.Abstraction; using Snap.Hutao.Web.Hoyolab.Bbs.User; using Snap.Hutao.Web.Hoyolab.Takumi.Binding; @@ -22,8 +23,8 @@ internal class UserService : IUserService private readonly UserClient userClient; private readonly UserGameRoleClient userGameRoleClient; - private Model.Binding.User? currentUser; - private ObservableCollection? userCollection = null; + private User? currentUser; + private ObservableCollection? userCollection = null; /// /// 构造一个新的用户服务 @@ -39,7 +40,7 @@ internal class UserService : IUserService } /// - public Model.Binding.User? CurrentUser + public User? CurrentUser { get => currentUser; set @@ -68,12 +69,12 @@ internal class UserService : IUserService } /// - public async Task TryAddUserAsync(Model.Binding.User newUser, string uid) + public async Task TryAddUserAsync(User newUser, string uid) { Must.NotNull(userCollection!); // 查找是否有相同的uid - if (userCollection.SingleOrDefault(u => u.UserInfo!.Uid == uid) is Model.Binding.User userWithSameUid) + if (userCollection.SingleOrDefault(u => u.UserInfo!.Uid == uid) is User userWithSameUid) { // Prevent users from adding a completely same cookie. if (userWithSameUid.Cookie == newUser.Cookie) @@ -94,36 +95,39 @@ internal class UserService : IUserService { Verify.Operation(newUser.IsInitialized, "该用户尚未初始化"); + // Sync cache + userCollection.Add(newUser); + // Sync database appDbContext.Users.Add(newUser.Entity); await appDbContext.SaveChangesAsync().ConfigureAwait(false); - // Sync cache - userCollection.Add(newUser); - return UserAddResult.Added; } } /// - public Task RemoveUserAsync(Model.Binding.User user) + public Task RemoveUserAsync(User user) { + // Sync cache userCollection!.Remove(user); + + // Sync database appDbContext.Users.Remove(user.Entity); return appDbContext.SaveChangesAsync(); } /// - public async Task> GetUserCollectionAsync() + public async Task> GetUserCollectionAsync() { if (userCollection == null) { - List users = new(); + List users = new(); - foreach (Model.Entity.User user in appDbContext.Users) + foreach (Model.Entity.User entity in appDbContext.Users) { - Model.Binding.User? initialized = await Model.Binding.User - .CreateAsync(user, userClient, userGameRoleClient) + User? initialized = await User + .CreateAsync(entity, userClient, userGameRoleClient) .ConfigureAwait(false); if (initialized != null) @@ -133,23 +137,22 @@ internal class UserService : IUserService else { // User is unable to be initialized, remove it. - appDbContext.Users.Remove(user); + appDbContext.Users.Remove(entity); await appDbContext.SaveChangesAsync().ConfigureAwait(false); } } - CurrentUser = users.SingleOrDefault(user => user.IsSelected); - userCollection = new(users); + CurrentUser = users.SingleOrDefault(user => user.IsSelected); } return userCollection; } /// - public Task CreateUserAsync(string cookie) + public Task CreateUserAsync(string cookie) { - return Model.Binding.User.CreateAsync(new() { Cookie = cookie }, userClient, userGameRoleClient); + return User.CreateAsync(new() { Cookie = cookie }, userClient, userGameRoleClient); } /// diff --git a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj index af073e01..30498d74 100644 --- a/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj +++ b/src/Snap.Hutao/Snap.Hutao/Snap.Hutao.csproj @@ -95,6 +95,7 @@ +