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