mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
refactor xaml window controller
This commit is contained in:
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// 为扩展窗体提供必要的选项
|
||||
@@ -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; }
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
{
|
||||
/// <summary>
|
||||
/// 处理最大最小信息
|
||||
/// </summary>
|
||||
/// <param name="info">信息</param>
|
||||
/// <param name="scalingFactor">缩放比</param>
|
||||
unsafe void HandleMinMaxInfo(ref MINMAXINFO info, double scalingFactor);
|
||||
}
|
||||
@@ -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();
|
||||
|
||||
@@ -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<Window, XamlWindowController> WindowControllers = [];
|
||||
|
||||
public static void InitializeController<TWindow>(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);
|
||||
}
|
||||
}
|
||||
@@ -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<RuntimeOptions>();
|
||||
@@ -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
|
||||
}
|
||||
@@ -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;
|
||||
|
||||
/// <summary>
|
||||
/// Window 选项
|
||||
/// </summary>
|
||||
internal readonly struct XamlWindowOptions
|
||||
internal sealed class XamlWindowOptions
|
||||
{
|
||||
/// <summary>
|
||||
/// 窗体句柄
|
||||
/// </summary>
|
||||
public readonly HWND Hwnd;
|
||||
|
||||
/// <summary>
|
||||
/// 非客户端区域指针源
|
||||
/// </summary>
|
||||
public readonly InputNonClientPointerSource InputNonClientPointerSource;
|
||||
|
||||
/// <summary>
|
||||
/// 标题栏元素
|
||||
/// </summary>
|
||||
public readonly FrameworkElement TitleBar;
|
||||
|
||||
/// <summary>
|
||||
/// 初始大小
|
||||
/// </summary>
|
||||
public readonly SizeInt32 InitSize;
|
||||
|
||||
/// <summary>
|
||||
/// 是否持久化尺寸
|
||||
/// </summary>
|
||||
[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;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// 获取窗体当前的DPI缩放比
|
||||
/// </summary>
|
||||
/// <returns>缩放比</returns>
|
||||
public double GetRasterizationScale()
|
||||
{
|
||||
uint dpi = GetDpiForWindow(Hwnd);
|
||||
return Math.Round(dpi / 96D, 2, MidpointRounding.AwayFromZero);
|
||||
}
|
||||
public SizeInt32 InitSize { get; }
|
||||
|
||||
/// <summary>
|
||||
/// 将窗口设为前台窗口
|
||||
/// </summary>
|
||||
/// <param name="hwnd">窗口句柄</param>
|
||||
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; }
|
||||
}
|
||||
@@ -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<XamlWindowSubclass> 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;
|
||||
|
||||
@@ -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;
|
||||
/// 指引窗口
|
||||
/// </summary>
|
||||
[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)
|
||||
{
|
||||
|
||||
@@ -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;
|
||||
/// </summary>
|
||||
[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;
|
||||
|
||||
/// <summary>
|
||||
@@ -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<LaunchGameViewModel>(scope.ServiceProvider);
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public XamlWindowOptions WindowOptions { get => windowOptions; }
|
||||
public FrameworkElement TitleBarAccess { get => DragableGrid; }
|
||||
|
||||
public SizeInt32 InitSize { get; } = new(MaxWidth, MaxHeight);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public void Dispose()
|
||||
|
||||
@@ -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;
|
||||
/// </summary>
|
||||
[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;
|
||||
|
||||
/// <summary>
|
||||
/// 构造一个新的主窗体
|
||||
/// </summary>
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
/// <inheritdoc/>
|
||||
public XamlWindowOptions WindowOptions { get => windowOptions; }
|
||||
public FrameworkElement TitleBarAccess { get => TitleBarView.DragArea; }
|
||||
|
||||
public string PersistRectKey { get => SettingKeys.WindowRect; }
|
||||
|
||||
public SizeInt32 InitSize { get; } = new(1200, 741);
|
||||
|
||||
/// <inheritdoc/>
|
||||
public unsafe void HandleMinMaxInfo(ref MINMAXINFO pInfo, double scalingFactor)
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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<ICurrentXamlWindowReference>().Window is MainWindow mainWindow)
|
||||
{
|
||||
double scale = mainWindow.WindowOptions.GetRasterizationScale();
|
||||
double scale = mainWindow.GetRasterizationScale();
|
||||
mainWindow.AppWindow.Resize(new Windows.Graphics.SizeInt32(1372, 772).Scale(scale));
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user