From b72b5ddf9159b21575101ca8c845fcc683acfcd7 Mon Sep 17 00:00:00 2001
From: DismissedLight <1686188646@qq.com>
Date: Thu, 21 Sep 2023 22:45:40 +0800
Subject: [PATCH] refactor window controller
---
.../Snap.Hutao/Core/Windowing/CompactRect.cs | 45 +++++++
.../Snap.Hutao/Core/Windowing/HotKey.cs | 27 ++++
.../Snap.Hutao/Core/Windowing/Persistence.cs | 113 ----------------
...{ExtendedWindow.cs => WindowController.cs} | 123 ++++++++++--------
.../Core/Windowing/WindowExtension.cs | 19 +++
.../Core/Windowing/WindowSubclass.cs | 28 ++--
src/Snap.Hutao/Snap.Hutao/GuideWindow.xaml.cs | 2 +-
.../Snap.Hutao/LaunchGameWindow.xaml.cs | 2 +-
src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs | 2 +-
.../Message/FlyoutStateChangedMessage.cs | 29 -----
src/Snap.Hutao/Snap.Hutao/NativeMethods.txt | 4 +
11 files changed, 178 insertions(+), 216 deletions(-)
create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Windowing/CompactRect.cs
create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey.cs
delete mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Windowing/Persistence.cs
rename src/Snap.Hutao/Snap.Hutao/Core/Windowing/{ExtendedWindow.cs => WindowController.cs} (64%)
create mode 100644 src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowExtension.cs
delete mode 100644 src/Snap.Hutao/Snap.Hutao/Message/FlyoutStateChangedMessage.cs
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/CompactRect.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/CompactRect.cs
new file mode 100644
index 00000000..9996fa4e
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/CompactRect.cs
@@ -0,0 +1,45 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using System.Runtime.CompilerServices;
+using Windows.Graphics;
+
+namespace Snap.Hutao.Core.Windowing;
+
+internal readonly struct CompactRect
+{
+ private readonly short x;
+ private readonly short y;
+ private readonly short width;
+ private readonly short height;
+
+ private CompactRect(int x, int y, int width, int height)
+ {
+ this.x = (short)x;
+ this.y = (short)y;
+ this.width = (short)width;
+ this.height = (short)height;
+ }
+
+ public static implicit operator RectInt32(CompactRect rect)
+ {
+ return new(rect.x, rect.y, rect.width, rect.height);
+ }
+
+ public static explicit operator CompactRect(RectInt32 rect)
+ {
+ return new(rect.X, rect.Y, rect.Width, rect.Height);
+ }
+
+ public static unsafe explicit operator CompactRect(ulong value)
+ {
+ Unsafe.SkipInit(out CompactRect rect);
+ *(ulong*)&rect = value;
+ return rect;
+ }
+
+ public static unsafe implicit operator ulong(CompactRect rect)
+ {
+ return *(ulong*)▭
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey.cs
new file mode 100644
index 00000000..e42d2cb6
--- /dev/null
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/HotKey.cs
@@ -0,0 +1,27 @@
+// Copyright (c) DGP Studio. All rights reserved.
+// Licensed under the MIT license.
+
+using Windows.Win32.Foundation;
+using static Windows.Win32.PInvoke;
+
+namespace Snap.Hutao.Core.Windowing;
+
+internal static class HotKey
+{
+ private const int DefaultId = 100000;
+
+ public static bool Register(in HWND hwnd)
+ {
+ return RegisterHotKey(hwnd, DefaultId, default, (uint)Windows.Win32.UI.Input.KeyboardAndMouse.VIRTUAL_KEY.VK_F8);
+ }
+
+ public static bool Unregister(in HWND hwnd)
+ {
+ return UnregisterHotKey(hwnd, DefaultId);
+ }
+
+ public static bool OnHotKeyPressed()
+ {
+ return true;
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Persistence.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Persistence.cs
deleted file mode 100644
index e2575923..00000000
--- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Persistence.cs
+++ /dev/null
@@ -1,113 +0,0 @@
-// Copyright (c) DGP Studio. All rights reserved.
-// Licensed under the MIT license.
-
-using Microsoft.UI.Windowing;
-using Microsoft.UI.Xaml;
-using Snap.Hutao.Core.Setting;
-using Snap.Hutao.Win32;
-using System.Runtime.CompilerServices;
-using Windows.Graphics;
-using Windows.Win32.UI.WindowsAndMessaging;
-using static Windows.Win32.PInvoke;
-
-namespace Snap.Hutao.Core.Windowing;
-
-///
-/// 窗体持久化
-///
-[HighQuality]
-internal static class Persistence
-{
- ///
- /// 设置窗体位置
- ///
- /// 窗体类型
- /// 选项窗口
- public static void RecoverOrInit(TWindow window)
- where TWindow : Window, IWindowOptionsSource
- {
- WindowOptions options = window.WindowOptions;
-
- // Set first launch size
- double scale = options.GetWindowScale();
- SizeInt32 transformedSize = options.InitSize.Scale(scale);
- RectInt32 rect = StructMarshal.RectInt32(transformedSize);
-
- if (options.PersistSize)
- {
- RectInt32 persistedRect = (CompactRect)LocalSetting.Get(SettingKeys.WindowRect, (CompactRect)rect);
- if (persistedRect.Size() >= options.InitSize.Size())
- {
- rect = persistedRect;
- }
- }
-
- TransformToCenterScreen(ref rect);
- window.AppWindow.MoveAndResize(rect);
- }
-
- ///
- /// 保存窗体的位置
- ///
- /// 窗口
- /// 窗体类型
- public static void Save(TWindow window)
- where TWindow : Window, IWindowOptionsSource
- {
- WINDOWPLACEMENT windowPlacement = StructMarshal.WINDOWPLACEMENT();
- GetWindowPlacement(window.WindowOptions.Hwnd, ref windowPlacement);
-
- // prevent save value when we are maximized.
- if (!windowPlacement.showCmd.HasFlag(SHOW_WINDOW_CMD.SW_SHOWMAXIMIZED))
- {
- LocalSetting.Set(SettingKeys.WindowRect, (CompactRect)window.AppWindow.GetRect());
- }
- }
-
- private static void TransformToCenterScreen(ref RectInt32 rect)
- {
- DisplayArea displayArea = DisplayArea.GetFromRect(rect, DisplayAreaFallback.Primary);
- RectInt32 workAreaRect = displayArea.WorkArea;
-
- rect.X = workAreaRect.X + ((workAreaRect.Width - rect.Width) / 2);
- rect.Y = workAreaRect.Y + ((workAreaRect.Height - rect.Height) / 2);
- }
-
- private readonly struct CompactRect
- {
- private readonly short x;
- private readonly short y;
- private readonly short width;
- private readonly short height;
-
- private CompactRect(int x, int y, int width, int height)
- {
- this.x = (short)x;
- this.y = (short)y;
- this.width = (short)width;
- this.height = (short)height;
- }
-
- public static implicit operator RectInt32(CompactRect rect)
- {
- return new(rect.x, rect.y, rect.width, rect.height);
- }
-
- public static explicit operator CompactRect(RectInt32 rect)
- {
- return new(rect.X, rect.Y, rect.Width, rect.Height);
- }
-
- public static unsafe explicit operator CompactRect(ulong value)
- {
- Unsafe.SkipInit(out CompactRect rect);
- *(ulong*)&rect = value;
- return rect;
- }
-
- public static unsafe implicit operator ulong(CompactRect rect)
- {
- return *(ulong*)▭
- }
- }
-}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/ExtendedWindow.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowController.cs
similarity index 64%
rename from src/Snap.Hutao/Snap.Hutao/Core/Windowing/ExtendedWindow.cs
rename to src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowController.cs
index 8647fee6..0afda214 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/ExtendedWindow.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowController.cs
@@ -1,13 +1,12 @@
// Copyright (c) DGP Studio. All rights reserved.
// Licensed under the MIT license.
-using CommunityToolkit.Mvvm.Messaging;
using Microsoft.UI;
using Microsoft.UI.Composition.SystemBackdrops;
using Microsoft.UI.Windowing;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media;
-using Snap.Hutao.Message;
+using Snap.Hutao.Core.Setting;
using Snap.Hutao.Service;
using Snap.Hutao.Win32;
using System.IO;
@@ -15,59 +14,48 @@ using Windows.Graphics;
using Windows.UI;
using Windows.Win32.Foundation;
using Windows.Win32.Graphics.Dwm;
+using Windows.Win32.UI.WindowsAndMessaging;
using static Windows.Win32.PInvoke;
namespace Snap.Hutao.Core.Windowing;
-///
-/// 扩展窗口
-///
-/// 窗体类型
[SuppressMessage("", "CA1001")]
-internal sealed class ExtendedWindow : IRecipient
- where TWindow : Window, IWindowOptionsSource
+internal sealed class WindowController
{
- private readonly TWindow window;
+ private readonly Window window;
+ private readonly WindowOptions options;
private readonly IServiceProvider serviceProvider;
- private readonly WindowSubclass subclass;
+ private readonly WindowSubclass subclass;
- private ExtendedWindow(TWindow window, IServiceProvider serviceProvider)
+ public WindowController(Window window, in WindowOptions options, IServiceProvider serviceProvider)
{
this.window = window;
+ this.options = options;
this.serviceProvider = serviceProvider;
- subclass = new(window);
+ subclass = new(window, options);
- InitializeWindow();
+ InitializeCore();
}
- ///
- /// 初始化
- ///
- /// 窗口
- /// 服务提供器
- /// 实例
- public static ExtendedWindow Initialize(TWindow window, IServiceProvider serviceProvider)
+ private static void TransformToCenterScreen(ref RectInt32 rect)
{
- return new(window, serviceProvider);
+ DisplayArea displayArea = DisplayArea.GetFromRect(rect, DisplayAreaFallback.Primary);
+ RectInt32 workAreaRect = displayArea.WorkArea;
+
+ rect.X = workAreaRect.X + ((workAreaRect.Width - rect.Width) / 2);
+ rect.Y = workAreaRect.Y + ((workAreaRect.Height - rect.Height) / 2);
}
- ///
- public void Receive(FlyoutStateChangedMessage message)
- {
- UpdateDragRectangles(message.IsOpen);
- }
-
- private void InitializeWindow()
+ private void InitializeCore()
{
RuntimeOptions hutaoOptions = serviceProvider.GetRequiredService();
- WindowOptions options = window.WindowOptions;
window.AppWindow.Title = SH.AppNameAndVersion.Format(hutaoOptions.Version);
window.AppWindow.SetIcon(Path.Combine(hutaoOptions.InstalledLocation, "Assets/Logo.ico"));
ExtendsContentIntoTitleBar();
- Persistence.RecoverOrInit(window);
+ RecoverOrInitWindowSize();
UpdateImmersiveDarkMode(options.TitleBar, default!);
// appWindow.Show(true);
@@ -81,12 +69,47 @@ internal sealed class ExtendedWindow : IRecipient().Register(this);
-
window.Closed += OnWindowClosed;
options.TitleBar.ActualThemeChanged += UpdateImmersiveDarkMode;
}
+ private void RecoverOrInitWindowSize()
+ {
+ // Set first launch size
+ double scale = options.GetWindowScale();
+ SizeInt32 transformedSize = options.InitSize.Scale(scale);
+ RectInt32 rect = StructMarshal.RectInt32(transformedSize);
+
+ if (options.PersistSize)
+ {
+ RectInt32 persistedRect = (CompactRect)LocalSetting.Get(SettingKeys.WindowRect, (CompactRect)rect);
+ if (persistedRect.Size() >= options.InitSize.Size())
+ {
+ rect = persistedRect;
+ }
+ }
+
+ TransformToCenterScreen(ref rect);
+ window.AppWindow.MoveAndResize(rect);
+ }
+
+ private void SaveOrSkipWindowSize()
+ {
+ if (!options.PersistSize)
+ {
+ return;
+ }
+
+ WINDOWPLACEMENT windowPlacement = StructMarshal.WINDOWPLACEMENT();
+ GetWindowPlacement(options.Hwnd, ref windowPlacement);
+
+ // prevent save value when we are maximized.
+ if (!windowPlacement.showCmd.HasFlag(SHOW_WINDOW_CMD.SW_SHOWMAXIMIZED))
+ {
+ LocalSetting.Set(SettingKeys.WindowRect, (CompactRect)window.AppWindow.GetRect());
+ }
+ }
+
private void OnOptionsPropertyChanged(object? sender, PropertyChangedEventArgs e)
{
if (e.PropertyName == nameof(AppOptions.BackdropType))
@@ -98,17 +121,12 @@ internal sealed class ExtendedWindow : IRecipient : IRecipient WindowControllers = new();
+
+ public static void InitializeController(this TWindow window, IServiceProvider serviceProvider)
+ where TWindow : Window, IWindowOptionsSource
+ {
+ WindowController windowController = new(window, window.WindowOptions, serviceProvider);
+ WindowControllers.Add(window, windowController);
+ }
+}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclass.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclass.cs
index bcc2e814..9a951714 100644
--- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclass.cs
+++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowSubclass.cs
@@ -12,27 +12,23 @@ namespace Snap.Hutao.Core.Windowing;
///
/// 窗体子类管理器
///
-/// 窗体类型
[HighQuality]
-internal sealed class WindowSubclass : IDisposable
- where TWindow : Window, IWindowOptionsSource
+internal sealed class WindowSubclass : IDisposable
{
private const int WindowSubclassId = 101;
private const int DragBarSubclassId = 102;
- private readonly TWindow window;
+ private readonly Window window;
+ private readonly WindowOptions options;
// We have to explicitly hold a reference to SUBCLASSPROC
private SUBCLASSPROC? windowProc;
private SUBCLASSPROC? legacyDragBarProc;
- ///
- /// 构造一个新的窗体子类管理器
- ///
- /// 窗口
- public WindowSubclass(TWindow window)
+ public WindowSubclass(Window window, in WindowOptions options)
{
this.window = window;
+ this.options = options;
}
///
@@ -41,10 +37,9 @@ internal sealed class WindowSubclass : IDisposable
/// 是否设置成功
public bool Initialize()
{
- WindowOptions options = window.WindowOptions;
-
windowProc = OnSubclassProcedure;
bool windowHooked = SetWindowSubclass(options.Hwnd, windowProc, WindowSubclassId, 0);
+ HotKey.Register(options.Hwnd);
bool titleBarHooked = true;
@@ -71,8 +66,6 @@ internal sealed class WindowSubclass : IDisposable
///
public void Dispose()
{
- WindowOptions options = window.WindowOptions;
-
RemoveWindowSubclass(options.Hwnd, windowProc, WindowSubclassId);
windowProc = null;
@@ -81,6 +74,7 @@ internal sealed class WindowSubclass : IDisposable
return;
}
+ HotKey.Unregister(options.Hwnd);
RemoveWindowSubclass(options.Hwnd, legacyDragBarProc, DragBarSubclassId);
legacyDragBarProc = null;
}
@@ -94,7 +88,7 @@ internal sealed class WindowSubclass : IDisposable
{
uint dpi = GetDpiForWindow(hwnd);
double scalingFactor = Math.Round(dpi / 96D, 2, MidpointRounding.AwayFromZero);
- window.ProcessMinMaxInfo((MINMAXINFO*)lParam.Value, scalingFactor);
+ ((IWindowOptionsSource)window).ProcessMinMaxInfo((MINMAXINFO*)lParam.Value, scalingFactor);
break;
}
@@ -103,6 +97,12 @@ internal sealed class WindowSubclass : IDisposable
{
return (LRESULT)(nint)WM_NULL;
}
+
+ case WM_HOTKEY:
+ {
+ System.Diagnostics.Debug.WriteLine($"Hot key pressed, wParam: {wParam.Value}, lParam: {lParam.Value}");
+ break;
+ }
}
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
diff --git a/src/Snap.Hutao/Snap.Hutao/GuideWindow.xaml.cs b/src/Snap.Hutao/Snap.Hutao/GuideWindow.xaml.cs
index 28746e63..e3473d62 100644
--- a/src/Snap.Hutao/Snap.Hutao/GuideWindow.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/GuideWindow.xaml.cs
@@ -25,7 +25,7 @@ internal sealed partial class GuideWindow : Window, IWindowOptionsSource
{
InitializeComponent();
windowOptions = new(this, DragableGrid, new(MinWidth, MinHeight));
- ExtendedWindow.Initialize(this, Ioc.Default);
+ this.InitializeController(serviceProvider);
}
WindowOptions IWindowOptionsSource.WindowOptions { get => windowOptions; }
diff --git a/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml.cs b/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml.cs
index 735e4e27..cba90226 100644
--- a/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml.cs
@@ -34,7 +34,7 @@ internal sealed partial class LaunchGameWindow : Window, IDisposable, IWindowOpt
scope = serviceProvider.CreateScope();
windowOptions = new(this, DragableGrid, new(MaxWidth, MaxHeight));
- ExtendedWindow.Initialize(this, scope.ServiceProvider);
+ this.InitializeController(serviceProvider);
RootGrid.DataContext = scope.ServiceProvider.GetRequiredService();
}
diff --git a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs
index fb7927b9..693b9e8e 100644
--- a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs
+++ b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs
@@ -31,7 +31,7 @@ internal sealed partial class MainWindow : Window, IWindowOptionsSource
{
InitializeComponent();
windowOptions = new(this, TitleBarView.DragArea, new(1200, 741), true);
- ExtendedWindow.Initialize(this, serviceProvider);
+ this.InitializeController(serviceProvider);
logger = serviceProvider.GetRequiredService>();
closedEventHander = OnClosed;
diff --git a/src/Snap.Hutao/Snap.Hutao/Message/FlyoutStateChangedMessage.cs b/src/Snap.Hutao/Snap.Hutao/Message/FlyoutStateChangedMessage.cs
deleted file mode 100644
index 40fba6fc..00000000
--- a/src/Snap.Hutao/Snap.Hutao/Message/FlyoutStateChangedMessage.cs
+++ /dev/null
@@ -1,29 +0,0 @@
-// Copyright (c) DGP Studio. All rights reserved.
-// Licensed under the MIT license.
-
-namespace Snap.Hutao.Message;
-
-///
-/// Flyout开启关闭消息
-///
-[HighQuality]
-internal sealed class FlyoutStateChangedMessage
-{
- ///
- /// 构造一个新的Flyout开启关闭消息
- ///
- /// 是否为开启状态
- public FlyoutStateChangedMessage(bool isOpen)
- {
- IsOpen = isOpen;
- }
-
- public static FlyoutStateChangedMessage Open { get; } = new(true);
-
- public static FlyoutStateChangedMessage Close { get; } = new(false);
-
- ///
- /// 是否为开启状态
- ///
- public bool IsOpen { get; }
-}
\ No newline at end of file
diff --git a/src/Snap.Hutao/Snap.Hutao/NativeMethods.txt b/src/Snap.Hutao/Snap.Hutao/NativeMethods.txt
index ff73b855..8679af3c 100644
--- a/src/Snap.Hutao/Snap.Hutao/NativeMethods.txt
+++ b/src/Snap.Hutao/Snap.Hutao/NativeMethods.txt
@@ -39,7 +39,10 @@ GetForegroundWindow
GetWindowPlacement
GetWindowThreadProcessId
ReleaseDC
+RegisterHotKey
+SendInput
SetForegroundWindow
+UnregisterHotKey
// COM
IPersistFile
@@ -53,6 +56,7 @@ IMemoryBufferByteAccess
// Const value
INFINITE
WM_GETMINMAXINFO
+WM_HOTKEY
WM_NCRBUTTONDOWN
WM_NCRBUTTONUP
WM_NULL