diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Abstraction/IXamlWindowContentAsFrameworkElement.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Abstraction/IXamlWindowContentAsFrameworkElement.cs new file mode 100644 index 00000000..d62ca3e4 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Abstraction/IXamlWindowContentAsFrameworkElement.cs @@ -0,0 +1,11 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.UI.Xaml; + +namespace Snap.Hutao.Core.Windowing.Abstraction; + +internal interface IXamlWindowContentAsFrameworkElement +{ + FrameworkElement ContentAccess { get; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Abstraction/IXamlWindowExtendContentIntoTitleBar.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Abstraction/IXamlWindowExtendContentIntoTitleBar.cs new file mode 100644 index 00000000..f36cbb57 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Abstraction/IXamlWindowExtendContentIntoTitleBar.cs @@ -0,0 +1,11 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Microsoft.UI.Xaml; + +namespace Snap.Hutao.Core.Windowing.Abstraction; + +internal interface IXamlWindowExtendContentIntoTitleBar +{ + FrameworkElement TitleBarAccess { get; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Abstraction/IXamlWindowHasInitSize.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Abstraction/IXamlWindowHasInitSize.cs new file mode 100644 index 00000000..1aad4d24 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Abstraction/IXamlWindowHasInitSize.cs @@ -0,0 +1,11 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Windows.Graphics; + +namespace Snap.Hutao.Core.Windowing.Abstraction; + +internal interface IXamlWindowHasInitSize +{ + SizeInt32 InitSize { get; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/IXamlWindowOptionsSource.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Abstraction/IXamlWindowOptionsSource.cs similarity index 86% rename from src/Snap.Hutao/Snap.Hutao/Core/Windowing/IXamlWindowOptionsSource.cs rename to src/Snap.Hutao/Snap.Hutao/Core/Windowing/Abstraction/IXamlWindowOptionsSource.cs index fa9a060b..265b1f44 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/IXamlWindowOptionsSource.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Abstraction/IXamlWindowOptionsSource.cs @@ -1,7 +1,7 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -namespace Snap.Hutao.Core.Windowing; +namespace Snap.Hutao.Core.Windowing.Abstraction; /// /// 为扩展窗体提供必要的选项 diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Abstraction/IXamlWindowRectPersisted.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Abstraction/IXamlWindowRectPersisted.cs new file mode 100644 index 00000000..47bc6c33 --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Abstraction/IXamlWindowRectPersisted.cs @@ -0,0 +1,9 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +namespace Snap.Hutao.Core.Windowing.Abstraction; + +internal interface IXamlWindowRectPersisted : IXamlWindowHasInitSize +{ + string PersistRectKey { get; } +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Abstraction/IXamlWindowSubclassMinMaxInfoHandler.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Abstraction/IXamlWindowSubclassMinMaxInfoHandler.cs new file mode 100644 index 00000000..feb6931a --- /dev/null +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/Abstraction/IXamlWindowSubclassMinMaxInfoHandler.cs @@ -0,0 +1,11 @@ +// Copyright (c) DGP Studio. All rights reserved. +// Licensed under the MIT license. + +using Snap.Hutao.Win32.UI.WindowsAndMessaging; + +namespace Snap.Hutao.Core.Windowing.Abstraction; + +internal interface IXamlWindowSubclassMinMaxInfoHandler +{ + unsafe void HandleMinMaxInfo(ref MINMAXINFO info, double scalingFactor); +} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/IMinMaxInfoHandler.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/IMinMaxInfoHandler.cs deleted file mode 100644 index 0d447049..00000000 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/IMinMaxInfoHandler.cs +++ /dev/null @@ -1,16 +0,0 @@ -// Copyright (c) DGP Studio. All rights reserved. -// Licensed under the MIT license. - -using Snap.Hutao.Win32.UI.WindowsAndMessaging; - -namespace Snap.Hutao.Core.Windowing; - -internal interface IMinMaxInfoHandler -{ - /// - /// 处理最大最小信息 - /// - /// 信息 - /// 缩放比 - unsafe void HandleMinMaxInfo(ref MINMAXINFO info, double scalingFactor); -} \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconXamlHostWindow.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconXamlHostWindow.cs index 1535464d..7a2deb84 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconXamlHostWindow.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/NotifyIcon/NotifyIconXamlHostWindow.cs @@ -23,7 +23,7 @@ internal sealed class NotifyIconXamlHostWindow : Window, IDisposable, IWindowNee { Content = new Border(); - this.SetLayeredWindow(); + this.SetLayered(); AppWindow.Title = "SnapHutaoNotifyIconXamlHost"; AppWindow.IsShownInSwitchers = false; @@ -36,8 +36,7 @@ internal sealed class NotifyIconXamlHostWindow : Window, IDisposable, IWindowNee presenter.SetBorderAndTitleBar(false, false); } - XamlWindowOptions options = new(this, default!, default); - subclass = new(this, options); + subclass = new(this); subclass.Initialize(); Activate(); diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowExtension.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowExtension.cs index f411ce83..78327050 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowExtension.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowExtension.cs @@ -1,8 +1,10 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. +using Microsoft.UI.Input; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Hosting; +using Snap.Hutao.Core.Windowing.Abstraction; using Snap.Hutao.Core.Windowing.Backdrop; using Snap.Hutao.Win32.Foundation; using Snap.Hutao.Win32.UI.WindowsAndMessaging; @@ -18,9 +20,9 @@ internal static class WindowExtension private static readonly ConditionalWeakTable WindowControllers = []; public static void InitializeController(this TWindow window, IServiceProvider serviceProvider) - where TWindow : Window, IXamlWindowOptionsSource + where TWindow : Window { - XamlWindowController windowController = new(window, window.WindowOptions, serviceProvider); + XamlWindowController windowController = new(window, serviceProvider); WindowControllers.Add(window, windowController); } @@ -30,25 +32,6 @@ internal static class WindowExtension return WindowControllers.TryGetValue(window, out _); } - public static void SetLayeredWindow(this Window window) - { - HWND hwnd = (HWND)WindowNative.GetWindowHandle(window); - nint style = GetWindowLongPtrW(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE); - style |= (nint)WINDOW_EX_STYLE.WS_EX_LAYERED; - SetWindowLongPtrW(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, style); - SetLayeredWindowAttributes(hwnd, RGB(0, 0, 0), 0, LAYERED_WINDOW_ATTRIBUTES_FLAGS.LWA_COLORKEY | LAYERED_WINDOW_ATTRIBUTES_FLAGS.LWA_ALPHA); - } - - public static void Show(this Window window) - { - ShowWindow(GetWindowHandle(window), SHOW_WINDOW_CMD.SW_NORMAL); - } - - public static void Hide(this Window window) - { - ShowWindow(GetWindowHandle(window), SHOW_WINDOW_CMD.SW_HIDE); - } - public static DesktopWindowXamlSource? GetDesktopWindowXamlSource(this Window window) { if (window.SystemBackdrop is SystemBackdropDesktopWindowXamlSourceAccess access) @@ -59,10 +42,64 @@ internal static class WindowExtension return default; } + public static InputNonClientPointerSource GetInputNonClientPointerSource(this Window window) + { + return InputNonClientPointerSource.GetForWindowId(window.AppWindow.Id); + } + public static HWND GetWindowHandle(this Window? window) { - return window is IXamlWindowOptionsSource optionsSource - ? optionsSource.WindowOptions.Hwnd - : WindowNative.GetWindowHandle(window); + return WindowNative.GetWindowHandle(window); + } + + public static void Show(this Window window) + { + ShowWindow(GetWindowHandle(window), SHOW_WINDOW_CMD.SW_NORMAL); + } + + public static void Hide(this Window window) + { + ShowWindow(GetWindowHandle(window), SHOW_WINDOW_CMD.SW_HIDE); + } + + public static void SetLayered(this Window window) + { + HWND hwnd = (HWND)WindowNative.GetWindowHandle(window); + nint style = GetWindowLongPtrW(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE); + style |= (nint)WINDOW_EX_STYLE.WS_EX_LAYERED; + SetWindowLongPtrW(hwnd, WINDOW_LONG_PTR_INDEX.GWL_EXSTYLE, style); + SetLayeredWindowAttributes(hwnd, RGB(0, 0, 0), 0, LAYERED_WINDOW_ATTRIBUTES_FLAGS.LWA_COLORKEY | LAYERED_WINDOW_ATTRIBUTES_FLAGS.LWA_ALPHA); + } + + public static unsafe void BringToForeground(this Window window) + { + HWND fgHwnd = GetForegroundWindow(); + HWND hwnd = window.GetWindowHandle(); + + uint threadIdHwnd = GetWindowThreadProcessId(hwnd, default); + uint threadIdFgHwnd = GetWindowThreadProcessId(fgHwnd, default); + + if (threadIdHwnd != threadIdFgHwnd) + { + AttachThreadInput(threadIdHwnd, threadIdFgHwnd, true); + SetForegroundWindow(hwnd); + AttachThreadInput(threadIdHwnd, threadIdFgHwnd, false); + } + else + { + SetForegroundWindow(hwnd); + } + } + + public static double GetRasterizationScale(this Window window) + { + // TODO: test this + if (window.Content is not null) + { + return window.Content.RasterizationScale; + } + + uint dpi = GetDpiForWindow(window.GetWindowHandle()); + return Math.Round(dpi / 96D, 2, MidpointRounding.AwayFromZero); } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/XamlWindowController.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/XamlWindowController.cs index e046ee0f..1c41628d 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/XamlWindowController.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/XamlWindowController.cs @@ -3,11 +3,13 @@ using Microsoft.UI; using Microsoft.UI.Composition.SystemBackdrops; +using Microsoft.UI.Content; using Microsoft.UI.Windowing; using Microsoft.UI.Xaml; using Microsoft.UI.Xaml.Media; using Snap.Hutao.Core.LifeCycle; using Snap.Hutao.Core.Setting; +using Snap.Hutao.Core.Windowing.Abstraction; using Snap.Hutao.Service; using Snap.Hutao.Win32; using Snap.Hutao.Win32.Foundation; @@ -22,38 +24,27 @@ using static Snap.Hutao.Win32.User32; namespace Snap.Hutao.Core.Windowing; [SuppressMessage("", "CA1001")] +[SuppressMessage("", "SA1124")] +[SuppressMessage("", "SA1204")] internal sealed class XamlWindowController { private readonly Window window; - private readonly XamlWindowOptions options; private readonly IServiceProvider serviceProvider; private readonly XamlWindowSubclass subclass; private readonly XamlWindowNonRudeHWND windowNonRudeHWND; - public XamlWindowController(Window window, in XamlWindowOptions options, IServiceProvider serviceProvider) + public XamlWindowController(Window window, IServiceProvider serviceProvider) { this.window = window; - this.options = options; this.serviceProvider = serviceProvider; - subclass = new(window, options); - windowNonRudeHWND = new(options.Hwnd); + // Subclassing and NonRudeHWND are standard infrastructure. + subclass = new(window); + windowNonRudeHWND = new(window.GetWindowHandle()); InitializeCore(); } - private static void TransformToCenterScreen(ref RectInt32 rect) - { - DisplayArea displayArea = DisplayArea.GetFromRect(rect, DisplayAreaFallback.Nearest); - RectInt32 workAreaRect = displayArea.WorkArea; - - rect.Width = Math.Min(workAreaRect.Width, rect.Width); - rect.Height = Math.Min(workAreaRect.Height, rect.Height); - - rect.X = workAreaRect.X + ((workAreaRect.Width - rect.Width) / 2); - rect.Y = workAreaRect.Y + ((workAreaRect.Height - rect.Height) / 2); - } - private void InitializeCore() { RuntimeOptions runtimeOptions = serviceProvider.GetRequiredService(); @@ -61,76 +52,46 @@ internal sealed class XamlWindowController window.AppWindow.Title = SH.FormatAppNameAndVersion(runtimeOptions.Version); window.AppWindow.SetIcon(Path.Combine(runtimeOptions.InstalledLocation, "Assets/Logo.ico")); - ExtendsContentIntoTitleBar(); - RecoverOrInitWindowSize(); - UpdateElementTheme(appOptions.ElementTheme); - UpdateImmersiveDarkMode(options.TitleBar, default!); + // ExtendContentIntoTitleBar + if (window is IXamlWindowExtendContentIntoTitleBar xamlWindow) + { + ExtendsContentIntoTitleBar(window, xamlWindow); + } + + // Size stuff + if (window is IXamlWindowHasInitSize xamlWindow2) + { + RecoverOrInitWindowSize(xamlWindow2); + } + + // Element Theme & Immersive Dark + UpdateElementTheme(window, appOptions.ElementTheme); + + if (window is IXamlWindowContentAsFrameworkElement xamlWindow3) + { + UpdateImmersiveDarkMode(xamlWindow3.ContentAccess, default!); + xamlWindow3.ContentAccess.ActualThemeChanged += UpdateImmersiveDarkMode; + } // appWindow.Show(true); // appWindow.Show can't bring window to top. window.Activate(); - options.BringToForeground(); + window.BringToForeground(); + + // SystemBackdrop UpdateSystemBackdrop(appOptions.BackdropType); + + if (window.GetDesktopWindowXamlSource() is { } desktopWindowXamlSource) + { + DesktopChildSiteBridge desktopChildSiteBridge = desktopWindowXamlSource.SiteBridge; + desktopChildSiteBridge.ResizePolicy = ContentSizePolicy.ResizeContentToParentWindow; + } + appOptions.PropertyChanged += OnOptionsPropertyChanged; subclass.Initialize(); - window.Closed += OnWindowClosed; - options.TitleBar.ActualThemeChanged += UpdateImmersiveDarkMode; - } - - private void RecoverOrInitWindowSize() - { - // Set first launch size - double scale = options.GetRasterizationScale(); - SizeInt32 scaledSize = options.InitSize.Scale(scale); - RectInt32 rect = StructMarshal.RectInt32(scaledSize); - - if (!string.IsNullOrEmpty(options.PersistRectKey)) - { - RectInt32 persistedRect = (CompactRect)LocalSetting.Get(options.PersistRectKey, (CompactRect)rect); - if (persistedRect.Size() >= options.InitSize.Size()) - { - rect = persistedRect.Scale(scale); - } - } - - TransformToCenterScreen(ref rect); - window.AppWindow.MoveAndResize(rect); - } - - private void SaveOrSkipWindowSize() - { - if (string.IsNullOrEmpty(options.PersistRectKey)) - { - return; - } - - WINDOWPLACEMENT windowPlacement = WINDOWPLACEMENT.Create(); - GetWindowPlacement(options.Hwnd, ref windowPlacement); - - // prevent save value when we are maximized. - if (!windowPlacement.ShowCmd.HasFlag(SHOW_WINDOW_CMD.SW_SHOWMAXIMIZED)) - { - double scale = 1.0 / options.GetRasterizationScale(); - LocalSetting.Set(options.PersistRectKey, (CompactRect)window.AppWindow.GetRect().Scale(scale)); - } - } - - private void OnOptionsPropertyChanged(object? sender, PropertyChangedEventArgs e) - { - if (sender is not AppOptions options) - { - return; - } - - _ = e.PropertyName switch - { - nameof(AppOptions.BackdropType) => UpdateSystemBackdrop(options.BackdropType), - nameof(AppOptions.ElementTheme) => UpdateElementTheme(options.ElementTheme), - _ => false, - }; } private void OnWindowClosed(object sender, WindowEventArgs args) @@ -150,22 +111,31 @@ internal sealed class XamlWindowController } else { - SaveOrSkipWindowSize(); + if (window is IXamlWindowRectPersisted rectPersisted) + { + SaveOrSkipWindowSize(rectPersisted); + } + subclass?.Dispose(); windowNonRudeHWND?.Dispose(); } } - private void ExtendsContentIntoTitleBar() - { - AppWindowTitleBar appTitleBar = window.AppWindow.TitleBar; - appTitleBar.IconShowOptions = IconShowOptions.HideIconAndSystemMenu; - appTitleBar.ExtendsContentIntoTitleBar = true; + #region SystemBackdrop & ElementTheme - UpdateTitleButtonColor(); - UpdateDragRectangles(); - options.TitleBar.ActualThemeChanged += (_, _) => UpdateTitleButtonColor(); - options.TitleBar.SizeChanged += (_, _) => UpdateDragRectangles(); + private void OnOptionsPropertyChanged(object? sender, PropertyChangedEventArgs e) + { + if (sender is not AppOptions options) + { + return; + } + + _ = e.PropertyName switch + { + nameof(AppOptions.BackdropType) => UpdateSystemBackdrop(options.BackdropType), + nameof(AppOptions.ElementTheme) => UpdateElementTheme(window, options.ElementTheme), + _ => false, + }; } private bool UpdateSystemBackdrop(BackdropType backdropType) @@ -184,21 +154,108 @@ internal sealed class XamlWindowController return true; } - private bool UpdateElementTheme(ElementTheme theme) + private static bool UpdateElementTheme(Window window, ElementTheme theme) { - ((FrameworkElement)window.Content).RequestedTheme = theme; + if (window is IXamlWindowContentAsFrameworkElement xamlWindow) + { + xamlWindow.ContentAccess.RequestedTheme = theme; + return true; + } - return true; + if (window.Content is FrameworkElement frameworkElement) + { + frameworkElement.RequestedTheme = theme; + return true; + } + + return false; + } + #endregion + + #region IXamlWindowContentAsFrameworkElement + + private unsafe void UpdateImmersiveDarkMode(FrameworkElement titleBar, object discard) + { + BOOL isDarkMode = Control.Theme.ThemeHelper.IsDarkMode(titleBar.ActualTheme); + DwmSetWindowAttribute(window.GetWindowHandle(), DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE, ref isDarkMode); + } + #endregion + + #region IXamlWindowHasInitSize & IXamlWindowRectPersisted + + private static void TransformToCenterScreen(ref RectInt32 rect) + { + DisplayArea displayArea = DisplayArea.GetFromRect(rect, DisplayAreaFallback.Nearest); + RectInt32 workAreaRect = displayArea.WorkArea; + + rect.Width = Math.Min(workAreaRect.Width, rect.Width); + rect.Height = Math.Min(workAreaRect.Height, rect.Height); + + rect.X = workAreaRect.X + ((workAreaRect.Width - rect.Width) / 2); + rect.Y = workAreaRect.Y + ((workAreaRect.Height - rect.Height) / 2); + } + + private void RecoverOrInitWindowSize(IXamlWindowHasInitSize xamlWindow) + { + double scale = window.GetRasterizationScale(); + SizeInt32 scaledSize = xamlWindow.InitSize.Scale(scale); + RectInt32 rect = StructMarshal.RectInt32(scaledSize); + + if (window is IXamlWindowRectPersisted rectPersisted) + { + RectInt32 persistedRect = (CompactRect)LocalSetting.Get(rectPersisted.PersistRectKey, (CompactRect)rect); + if (persistedRect.Size() >= xamlWindow.InitSize.Size()) + { + rect = persistedRect.Scale(scale); + } + } + + TransformToCenterScreen(ref rect); + window.AppWindow.MoveAndResize(rect); + } + + private void SaveOrSkipWindowSize(IXamlWindowRectPersisted rectPersisted) + { + WINDOWPLACEMENT windowPlacement = WINDOWPLACEMENT.Create(); + GetWindowPlacement(window.GetWindowHandle(), ref windowPlacement); + + // prevent save value when we are maximized. + if (!windowPlacement.ShowCmd.HasFlag(SHOW_WINDOW_CMD.SW_SHOWMAXIMIZED)) + { + double scale = 1.0 / window.GetRasterizationScale(); + LocalSetting.Set(rectPersisted.PersistRectKey, (CompactRect)window.AppWindow.GetRect().Scale(scale)); + } + } + #endregion + + #region IXamlWindowExtendContentIntoTitleBar + + private void ExtendsContentIntoTitleBar(Window window, IXamlWindowExtendContentIntoTitleBar xamlWindow) + { + AppWindowTitleBar appTitleBar = window.AppWindow.TitleBar; + appTitleBar.IconShowOptions = IconShowOptions.HideIconAndSystemMenu; + appTitleBar.ExtendsContentIntoTitleBar = true; + + UpdateTitleButtonColor(); + xamlWindow.TitleBarAccess.ActualThemeChanged += (_, _) => UpdateTitleButtonColor(); + + UpdateDragRectangles(); + xamlWindow.TitleBarAccess.SizeChanged += (_, _) => UpdateDragRectangles(); } private void UpdateTitleButtonColor() { + if (window is not IXamlWindowExtendContentIntoTitleBar xamlWindow) + { + return; + } + AppWindowTitleBar appTitleBar = window.AppWindow.TitleBar; appTitleBar.ButtonBackgroundColor = Colors.Transparent; appTitleBar.ButtonInactiveBackgroundColor = Colors.Transparent; - bool isDarkMode = Control.Theme.ThemeHelper.IsDarkMode(options.TitleBar.ActualTheme); + bool isDarkMode = Control.Theme.ThemeHelper.IsDarkMode(xamlWindow.TitleBarAccess.ActualTheme); Color systemBaseLowColor = Control.Theme.SystemColors.BaseLowColor(isDarkMode); appTitleBar.ButtonHoverBackgroundColor = systemBaseLowColor; @@ -217,20 +274,20 @@ internal sealed class XamlWindowController appTitleBar.ButtonPressedForegroundColor = systemBaseHighColor; } - private unsafe void UpdateImmersiveDarkMode(FrameworkElement titleBar, object discard) - { - BOOL isDarkMode = Control.Theme.ThemeHelper.IsDarkMode(titleBar.ActualTheme); - DwmSetWindowAttribute(options.Hwnd, DWMWINDOWATTRIBUTE.DWMWA_USE_IMMERSIVE_DARK_MODE, ref isDarkMode); - } - private void UpdateDragRectangles() { + if (window is not IXamlWindowExtendContentIntoTitleBar xamlWindow) + { + return; + } + AppWindowTitleBar appTitleBar = window.AppWindow.TitleBar; - double scale = options.GetRasterizationScale(); + double scale = window.GetRasterizationScale(); // 48 is the navigation button leftInset - RectInt32 dragRect = StructMarshal.RectInt32(48, 0, options.TitleBar.ActualSize).Scale(scale); + RectInt32 dragRect = StructMarshal.RectInt32(48, 0, xamlWindow.TitleBarAccess.ActualSize).Scale(scale); appTitleBar.SetDragRectangles([dragRect]); } + #endregion } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/XamlWindowOptions.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/XamlWindowOptions.cs index acd2e78b..03dab84e 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/XamlWindowOptions.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/XamlWindowOptions.cs @@ -1,87 +1,22 @@ // Copyright (c) DGP Studio. All rights reserved. // Licensed under the MIT license. -using Microsoft.UI.Input; using Microsoft.UI.Xaml; -using Snap.Hutao.Win32.Foundation; using Windows.Graphics; -using WinRT.Interop; -using static Snap.Hutao.Win32.User32; namespace Snap.Hutao.Core.Windowing; /// /// Window 选项 /// -internal readonly struct XamlWindowOptions +internal sealed class XamlWindowOptions { - /// - /// 窗体句柄 - /// - public readonly HWND Hwnd; - - /// - /// 非客户端区域指针源 - /// - public readonly InputNonClientPointerSource InputNonClientPointerSource; - - /// - /// 标题栏元素 - /// - public readonly FrameworkElement TitleBar; - - /// - /// 初始大小 - /// - public readonly SizeInt32 InitSize; - - /// - /// 是否持久化尺寸 - /// - [Obsolete] - public readonly bool PersistSize; - - public readonly string? PersistRectKey; - public XamlWindowOptions(Window window, FrameworkElement titleBar, SizeInt32 initSize, string? persistSize = default) { - Hwnd = WindowNative.GetWindowHandle(window); - InputNonClientPointerSource = InputNonClientPointerSource.GetForWindowId(window.AppWindow.Id); - TitleBar = titleBar; - InitSize = initSize; PersistRectKey = persistSize; } - /// - /// 获取窗体当前的DPI缩放比 - /// - /// 缩放比 - public double GetRasterizationScale() - { - uint dpi = GetDpiForWindow(Hwnd); - return Math.Round(dpi / 96D, 2, MidpointRounding.AwayFromZero); - } + public SizeInt32 InitSize { get; } - /// - /// 将窗口设为前台窗口 - /// - /// 窗口句柄 - public unsafe void BringToForeground() - { - HWND fgHwnd = GetForegroundWindow(); - - uint threadIdHwnd = GetWindowThreadProcessId(Hwnd, default); - uint threadIdFgHwnd = GetWindowThreadProcessId(fgHwnd, default); - - if (threadIdHwnd != threadIdFgHwnd) - { - AttachThreadInput(threadIdHwnd, threadIdFgHwnd, true); - SetForegroundWindow(Hwnd); - AttachThreadInput(threadIdHwnd, threadIdFgHwnd, false); - } - else - { - SetForegroundWindow(Hwnd); - } - } + public string? PersistRectKey { get; } } \ No newline at end of file diff --git a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/XamlWindowSubclass.cs b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/XamlWindowSubclass.cs index 9278ce29..c492a035 100644 --- a/src/Snap.Hutao/Snap.Hutao/Core/Windowing/XamlWindowSubclass.cs +++ b/src/Snap.Hutao/Snap.Hutao/Core/Windowing/XamlWindowSubclass.cs @@ -2,6 +2,7 @@ // Licensed under the MIT license. using Microsoft.UI.Xaml; +using Snap.Hutao.Core.Windowing.Abstraction; using Snap.Hutao.Core.Windowing.Backdrop; using Snap.Hutao.Core.Windowing.NotifyIcon; using Snap.Hutao.Win32; @@ -22,28 +23,28 @@ internal sealed class XamlWindowSubclass : IDisposable private const int WindowSubclassId = 101; private readonly Window window; - private readonly XamlWindowOptions options; + private readonly HWND hwnd; // We have to explicitly hold a reference to SUBCLASSPROC private SUBCLASSPROC windowProc = default!; private UnmanagedAccess unmanagedAccess = default!; - public XamlWindowSubclass(Window window, in XamlWindowOptions options) + public XamlWindowSubclass(Window window) { this.window = window; - this.options = options; + hwnd = window.GetWindowHandle(); } public unsafe bool Initialize() { windowProc = SUBCLASSPROC.Create(&OnSubclassProcedure); unmanagedAccess = UnmanagedAccess.Create(this); - return SetWindowSubclass(options.Hwnd, windowProc, WindowSubclassId, unmanagedAccess); + return SetWindowSubclass(hwnd, windowProc, WindowSubclassId, unmanagedAccess); } public void Dispose() { - RemoveWindowSubclass(options.Hwnd, windowProc, WindowSubclassId); + RemoveWindowSubclass(hwnd, windowProc, WindowSubclassId); windowProc = default!; unmanagedAccess.Dispose(); } @@ -59,9 +60,9 @@ internal sealed class XamlWindowSubclass : IDisposable { case WM_GETMINMAXINFO: { - if (state.window is IMinMaxInfoHandler handler) + if (state.window is IXamlWindowSubclassMinMaxInfoHandler handler) { - handler.HandleMinMaxInfo(ref *(MINMAXINFO*)lParam, state.options.GetRasterizationScale()); + handler.HandleMinMaxInfo(ref *(MINMAXINFO*)lParam, state.window.GetRasterizationScale()); } break; diff --git a/src/Snap.Hutao/Snap.Hutao/GuideWindow.xaml.cs b/src/Snap.Hutao/Snap.Hutao/GuideWindow.xaml.cs index 0fa446c5..59f8e60d 100644 --- a/src/Snap.Hutao/Snap.Hutao/GuideWindow.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/GuideWindow.xaml.cs @@ -4,7 +4,9 @@ using Microsoft.UI.Xaml; using Snap.Hutao.Core.Setting; using Snap.Hutao.Core.Windowing; +using Snap.Hutao.Core.Windowing.Abstraction; using Snap.Hutao.Win32.UI.WindowsAndMessaging; +using Windows.Graphics; namespace Snap.Hutao; @@ -12,7 +14,10 @@ namespace Snap.Hutao; /// 指引窗口 /// [Injection(InjectAs.Singleton)] -internal sealed partial class GuideWindow : Window, IXamlWindowOptionsSource, IMinMaxInfoHandler +internal sealed partial class GuideWindow : Window, + IXamlWindowExtendContentIntoTitleBar, + IXamlWindowRectPersisted, + IXamlWindowSubclassMinMaxInfoHandler { private const int MinWidth = 1000; private const int MinHeight = 650; @@ -20,16 +25,17 @@ internal sealed partial class GuideWindow : Window, IXamlWindowOptionsSource, IM private const int MaxWidth = 1200; private const int MaxHeight = 800; - private readonly XamlWindowOptions windowOptions; - public GuideWindow(IServiceProvider serviceProvider) { InitializeComponent(); - windowOptions = new(this, DragableGrid, new(MinWidth, MinHeight), SettingKeys.GuideWindowRect); this.InitializeController(serviceProvider); } - XamlWindowOptions IXamlWindowOptionsSource.WindowOptions { get => windowOptions; } + public FrameworkElement TitleBarAccess { get => DragableGrid; } + + public string PersistRectKey { get => SettingKeys.GuideWindowRect; } + + public SizeInt32 InitSize { get; } = new(MinWidth, MinHeight); public unsafe void HandleMinMaxInfo(ref MINMAXINFO info, double scalingFactor) { diff --git a/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml.cs b/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml.cs index b1df5857..d489962d 100644 --- a/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/LaunchGameWindow.xaml.cs @@ -3,10 +3,11 @@ using Microsoft.UI.Xaml; using Snap.Hutao.Control.Extension; -using Snap.Hutao.Core.Setting; using Snap.Hutao.Core.Windowing; +using Snap.Hutao.Core.Windowing.Abstraction; using Snap.Hutao.ViewModel.Game; using Snap.Hutao.Win32.UI.WindowsAndMessaging; +using Windows.Graphics; namespace Snap.Hutao; @@ -15,7 +16,11 @@ namespace Snap.Hutao; /// [HighQuality] [Injection(InjectAs.Singleton)] -internal sealed partial class LaunchGameWindow : Window, IDisposable, IXamlWindowOptionsSource, IMinMaxInfoHandler +internal sealed partial class LaunchGameWindow : Window, + IDisposable, + IXamlWindowExtendContentIntoTitleBar, + IXamlWindowHasInitSize, + IXamlWindowSubclassMinMaxInfoHandler { private const int MinWidth = 240; private const int MinHeight = 240; @@ -23,7 +28,6 @@ internal sealed partial class LaunchGameWindow : Window, IDisposable, IXamlWindo private const int MaxWidth = 320; private const int MaxHeight = 320; - private readonly XamlWindowOptions windowOptions; private readonly IServiceScope scope; /// @@ -35,13 +39,14 @@ internal sealed partial class LaunchGameWindow : Window, IDisposable, IXamlWindo InitializeComponent(); scope = serviceProvider.CreateScope(); - windowOptions = new(this, DragableGrid, new(MaxWidth, MaxHeight)); + this.InitializeController(serviceProvider); RootGrid.InitializeDataContext(scope.ServiceProvider); } - /// - public XamlWindowOptions WindowOptions { get => windowOptions; } + public FrameworkElement TitleBarAccess { get => DragableGrid; } + + public SizeInt32 InitSize { get; } = new(MaxWidth, MaxHeight); /// public void Dispose() diff --git a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs index 1ec58d21..5aa6f39f 100644 --- a/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs +++ b/src/Snap.Hutao/Snap.Hutao/MainWindow.xaml.cs @@ -5,7 +5,9 @@ using Microsoft.UI.Content; using Microsoft.UI.Xaml; using Snap.Hutao.Core.Setting; using Snap.Hutao.Core.Windowing; +using Snap.Hutao.Core.Windowing.Abstraction; using Snap.Hutao.Win32.UI.WindowsAndMessaging; +using Windows.Graphics; namespace Snap.Hutao; @@ -14,13 +16,14 @@ namespace Snap.Hutao; /// [HighQuality] [Injection(InjectAs.Singleton)] -internal sealed partial class MainWindow : Window, IXamlWindowOptionsSource, IMinMaxInfoHandler +internal sealed partial class MainWindow : Window, + IXamlWindowExtendContentIntoTitleBar, + IXamlWindowRectPersisted, + IXamlWindowSubclassMinMaxInfoHandler { private const int MinWidth = 1000; private const int MinHeight = 600; - private readonly XamlWindowOptions windowOptions; - /// /// 构造一个新的主窗体 /// @@ -28,18 +31,14 @@ internal sealed partial class MainWindow : Window, IXamlWindowOptionsSource, IMi public MainWindow(IServiceProvider serviceProvider) { InitializeComponent(); - windowOptions = new(this, TitleBarView.DragArea, new(1200, 741), SettingKeys.WindowRect); this.InitializeController(serviceProvider); - - if (this.GetDesktopWindowXamlSource() is { } desktopWindowXamlSource) - { - DesktopChildSiteBridge desktopChildSiteBridge = desktopWindowXamlSource.SiteBridge; - desktopChildSiteBridge.ResizePolicy = ContentSizePolicy.ResizeContentToParentWindow; - } } - /// - public XamlWindowOptions WindowOptions { get => windowOptions; } + public FrameworkElement TitleBarAccess { get => TitleBarView.DragArea; } + + public string PersistRectKey { get => SettingKeys.WindowRect; } + + public SizeInt32 InitSize { get; } = new(1200, 741); /// public unsafe void HandleMinMaxInfo(ref MINMAXINFO pInfo, double scalingFactor) diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/NotifyIconViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/NotifyIconViewModel.cs index daefd7b9..49003411 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/NotifyIconViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/NotifyIconViewModel.cs @@ -49,7 +49,7 @@ internal sealed partial class NotifyIconViewModel : ObservableObject { // MainWindow is activated, bring to foreground mainWindow.Show(); - mainWindow.WindowOptions.BringToForeground(); + mainWindow.BringToForeground(); return; } @@ -61,14 +61,14 @@ internal sealed partial class NotifyIconViewModel : ObservableObject // TODO: Can actually be no any window is initialized mainWindow.Show(); - mainWindow.WindowOptions.BringToForeground(); + mainWindow.BringToForeground(); break; } case Window otherWindow: { otherWindow.Show(); - (otherWindow as IXamlWindowOptionsSource)?.WindowOptions.BringToForeground(); + otherWindow.BringToForeground(); return; } } diff --git a/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs b/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs index 18fd0e1a..d1aeabe9 100644 --- a/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs +++ b/src/Snap.Hutao/Snap.Hutao/ViewModel/TestViewModel.cs @@ -7,6 +7,7 @@ using Snap.Hutao.Core.Caching; using Snap.Hutao.Core.ExceptionService; using Snap.Hutao.Core.LifeCycle; using Snap.Hutao.Core.Setting; +using Snap.Hutao.Core.Windowing; using Snap.Hutao.Service.Notification; using Snap.Hutao.ViewModel.Guide; using Snap.Hutao.Web.Hutao.HutaoAsAService; @@ -125,7 +126,7 @@ internal sealed partial class TestViewModel : Abstraction.ViewModel { if (serviceProvider.GetRequiredService().Window is MainWindow mainWindow) { - double scale = mainWindow.WindowOptions.GetRasterizationScale(); + double scale = mainWindow.GetRasterizationScale(); mainWindow.AppWindow.Resize(new Windows.Graphics.SizeInt32(1372, 772).Scale(scale)); } }