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));
}
}