mirror of
https://jihulab.com/DGP-Studio/Snap.Hutao.git
synced 2025-11-19 21:02:53 +08:00
use AppWindow API if we can
This commit is contained in:
11
src/Snap.Hutao/Snap.Hutao.Win32/Graphics/Gdi/MONITORINFO.cs
Normal file
11
src/Snap.Hutao/Snap.Hutao.Win32/Graphics/Gdi/MONITORINFO.cs
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
using System.Runtime.InteropServices;
|
||||||
|
|
||||||
|
namespace Windows.Win32.Graphics.Gdi;
|
||||||
|
|
||||||
|
public partial struct MONITORINFO
|
||||||
|
{
|
||||||
|
public static MONITORINFO Default
|
||||||
|
{
|
||||||
|
get => new() { cbSize = (uint)Marshal.SizeOf<MONITORINFO>() };
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,16 +1,30 @@
|
|||||||
// ComCtl32
|
// Type definition
|
||||||
SetWindowSubclass
|
MINMAXINFO
|
||||||
RemoveWindowSubclass
|
|
||||||
|
|
||||||
|
//
|
||||||
DefSubclassProc
|
DefSubclassProc
|
||||||
|
|
||||||
|
SetWindowSubclass
|
||||||
|
RemoveWindowSubclass
|
||||||
|
|
||||||
|
|
||||||
// User32
|
// User32
|
||||||
SetWindowText
|
|
||||||
GetDpiForWindow
|
GetDpiForWindow
|
||||||
|
|
||||||
|
GetMonitorInfo
|
||||||
|
|
||||||
GetWindowPlacement
|
GetWindowPlacement
|
||||||
SetWindowPlacement
|
SetWindowPlacement
|
||||||
|
|
||||||
// Type definition
|
GetWindowRect
|
||||||
MINMAXINFO
|
|
||||||
|
MonitorFromRect
|
||||||
|
|
||||||
|
SetWindowPos
|
||||||
|
|
||||||
|
SetWindowText
|
||||||
|
|
||||||
|
|
||||||
// Const value
|
// Const value
|
||||||
WM_GETMINMAXINFO
|
WM_GETMINMAXINFO
|
||||||
@@ -11,6 +11,7 @@
|
|||||||
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.10-beta">
|
<PackageReference Include="Microsoft.Windows.CsWin32" Version="0.2.10-beta">
|
||||||
<PrivateAssets>all</PrivateAssets>
|
<PrivateAssets>all</PrivateAssets>
|
||||||
</PackageReference>
|
</PackageReference>
|
||||||
|
<PackageReference Include="Microsoft.Windows.SDK.Win32Metadata" Version="24.0.1-preview" />
|
||||||
</ItemGroup>
|
</ItemGroup>
|
||||||
|
|
||||||
</Project>
|
</Project>
|
||||||
|
|||||||
@@ -11,10 +11,7 @@ public partial struct WINDOWPLACEMENT
|
|||||||
{
|
{
|
||||||
get
|
get
|
||||||
{
|
{
|
||||||
return new WINDOWPLACEMENT()
|
return new() { length = (uint)Marshal.SizeOf<WINDOWPLACEMENT>() };
|
||||||
{
|
|
||||||
length = (uint)Marshal.SizeOf<WINDOWPLACEMENT>(),
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -67,11 +67,7 @@ public partial class App : Application
|
|||||||
get => ApplicationData.Current.LocalSettings;
|
get => ApplicationData.Current.LocalSettings;
|
||||||
}
|
}
|
||||||
|
|
||||||
/// <summary>
|
/// <inheritdoc/>
|
||||||
/// Invoked when the application is launched.
|
|
||||||
/// Any async operation in this method should be wrapped with try catch
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="args">Details about the launch request and process.</param>
|
|
||||||
[SuppressMessage("", "VSTHRD100")]
|
[SuppressMessage("", "VSTHRD100")]
|
||||||
protected override async void OnLaunched(LaunchActivatedEventArgs args)
|
protected override async void OnLaunched(LaunchActivatedEventArgs args)
|
||||||
{
|
{
|
||||||
@@ -83,7 +79,6 @@ public partial class App : Application
|
|||||||
firstInstance.Activated += OnActivated;
|
firstInstance.Activated += OnActivated;
|
||||||
|
|
||||||
Window = Ioc.Default.GetRequiredService<MainWindow>();
|
Window = Ioc.Default.GetRequiredService<MainWindow>();
|
||||||
Window.Activate();
|
|
||||||
|
|
||||||
logger.LogInformation(EventIds.CommonLog, "Cache folder : {folder}", CacheFolder.Path);
|
logger.LogInformation(EventIds.CommonLog, "Cache folder : {folder}", CacheFolder.Path);
|
||||||
|
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
|||||||
private readonly IImageCache imageCache;
|
private readonly IImageCache imageCache;
|
||||||
|
|
||||||
private SpriteVisual? spriteVisual;
|
private SpriteVisual? spriteVisual;
|
||||||
|
private bool isShow = true;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构造一个新的单色图像
|
/// 构造一个新的单色图像
|
||||||
@@ -88,7 +89,7 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
|||||||
|
|
||||||
if (exception is HttpRequestException httpRequestException)
|
if (exception is HttpRequestException httpRequestException)
|
||||||
{
|
{
|
||||||
infoBarService.Warning($"GET {uri}\n{httpRequestException}");
|
infoBarService.Error(httpRequestException, $"GET {uri}");
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
@@ -113,7 +114,6 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
|||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
// should hide
|
|
||||||
image.HideAsync(token).SafeForget(logger);
|
image.HideAsync(token).SafeForget(logger);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -149,14 +149,22 @@ public abstract class CompositionImage : Microsoft.UI.Xaml.Controls.Control
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task ShowAsync(CancellationToken token)
|
private async Task ShowAsync(CancellationToken token)
|
||||||
{
|
{
|
||||||
return AnimationBuilder.Create().Opacity(1d).StartAsync(this, token);
|
if (!isShow)
|
||||||
|
{
|
||||||
|
await AnimationBuilder.Create().Opacity(1d).StartAsync(this, token);
|
||||||
|
isShow = true;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private Task HideAsync(CancellationToken token)
|
private async Task HideAsync(CancellationToken token)
|
||||||
{
|
{
|
||||||
return AnimationBuilder.Create().Opacity(0d).StartAsync(this, token);
|
if (isShow)
|
||||||
|
{
|
||||||
|
await AnimationBuilder.Create().Opacity(0d).StartAsync(this, token);
|
||||||
|
isShow = false;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
|
private void OnSizeChanged(object sender, SizeChangedEventArgs e)
|
||||||
|
|||||||
@@ -1,146 +0,0 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
|
||||||
// Licensed under the MIT license.
|
|
||||||
|
|
||||||
using Microsoft.UI;
|
|
||||||
using Microsoft.UI.Windowing;
|
|
||||||
using Microsoft.UI.Xaml;
|
|
||||||
using Snap.Hutao.Control.HostBackdrop;
|
|
||||||
using Snap.Hutao.Core.Logging;
|
|
||||||
using Snap.Hutao.Core.Setting;
|
|
||||||
using Windows.Win32.Foundation;
|
|
||||||
using Windows.Win32.UI.Shell;
|
|
||||||
using Windows.Win32.UI.WindowsAndMessaging;
|
|
||||||
using WinRT.Interop;
|
|
||||||
|
|
||||||
using static Windows.Win32.PInvoke;
|
|
||||||
|
|
||||||
namespace Snap.Hutao.Core;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 窗口管理器
|
|
||||||
/// 主要包含了针对窗体的 P/Inoke 逻辑
|
|
||||||
/// </summary>
|
|
||||||
internal class WindowManager
|
|
||||||
{
|
|
||||||
private const int MinWidth = 848;
|
|
||||||
private const int MinHeight = 524;
|
|
||||||
private const int SubclassId = 101;
|
|
||||||
|
|
||||||
private static readonly Windows.UI.Color SystemBaseLowColor = Windows.UI.Color.FromArgb(0x33, 0xFF, 0xFF, 0xFF);
|
|
||||||
private static readonly Windows.UI.Color SystemBaseMediumLowColor = Windows.UI.Color.FromArgb(0x66, 0xFF, 0xFF, 0xFF);
|
|
||||||
private readonly HWND handle;
|
|
||||||
private readonly Window window;
|
|
||||||
private readonly UIElement titleBar;
|
|
||||||
private readonly ILogger<WindowManager> logger;
|
|
||||||
|
|
||||||
// We have to explictly hold a reference to the SUBCLASSPROC,
|
|
||||||
// otherwise will casuse System.ExecutionEngineException
|
|
||||||
private SUBCLASSPROC? subClassProc;
|
|
||||||
|
|
||||||
/// <summary>
|
|
||||||
/// 构造一个新的窗口状态管理器
|
|
||||||
/// </summary>
|
|
||||||
/// <param name="window">窗口</param>
|
|
||||||
/// <param name="titleBar">充当标题栏的元素</param>
|
|
||||||
public WindowManager(Window window, UIElement titleBar)
|
|
||||||
{
|
|
||||||
this.window = window;
|
|
||||||
this.titleBar = titleBar;
|
|
||||||
logger = Ioc.Default.GetRequiredService<ILogger<WindowManager>>();
|
|
||||||
|
|
||||||
handle = (HWND)WindowNative.GetWindowHandle(window);
|
|
||||||
|
|
||||||
InitializeWindow();
|
|
||||||
}
|
|
||||||
|
|
||||||
private static RECT RetriveWindowRect()
|
|
||||||
{
|
|
||||||
int left = LocalSetting.GetValueType<int>(SettingKeys.WindowLeft);
|
|
||||||
int top = LocalSetting.GetValueType<int>(SettingKeys.WindowTop);
|
|
||||||
int right = LocalSetting.GetValueType<int>(SettingKeys.WindowRight);
|
|
||||||
int bottom = LocalSetting.GetValueType<int>(SettingKeys.WindowBottom);
|
|
||||||
|
|
||||||
return new(left, top, right, bottom);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void SaveWindowRect(HWND handle)
|
|
||||||
{
|
|
||||||
WINDOWPLACEMENT windowPlacement = WINDOWPLACEMENT.Default;
|
|
||||||
|
|
||||||
GetWindowPlacement(handle, ref windowPlacement);
|
|
||||||
|
|
||||||
LocalSetting.Set(SettingKeys.WindowLeft, windowPlacement.rcNormalPosition.left);
|
|
||||||
LocalSetting.Set(SettingKeys.WindowTop, windowPlacement.rcNormalPosition.top);
|
|
||||||
LocalSetting.Set(SettingKeys.WindowRight, windowPlacement.rcNormalPosition.right);
|
|
||||||
LocalSetting.Set(SettingKeys.WindowBottom, windowPlacement.rcNormalPosition.bottom);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void InitializeWindow()
|
|
||||||
{
|
|
||||||
if (false && AppWindowTitleBar.IsCustomizationSupported())
|
|
||||||
{
|
|
||||||
AppWindow appWindow = GetAppWindow();
|
|
||||||
AppWindowTitleBar titleBar = appWindow.TitleBar;
|
|
||||||
titleBar.ExtendsContentIntoTitleBar = true;
|
|
||||||
|
|
||||||
titleBar.ButtonBackgroundColor = Colors.Transparent;
|
|
||||||
titleBar.ButtonHoverBackgroundColor = SystemBaseLowColor;
|
|
||||||
titleBar.ButtonPressedBackgroundColor = SystemBaseMediumLowColor;
|
|
||||||
titleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
|
|
||||||
|
|
||||||
// appWindow.TitleBar.SetDragRectangles();
|
|
||||||
appWindow.Title = "胡桃";
|
|
||||||
}
|
|
||||||
else
|
|
||||||
{
|
|
||||||
window.ExtendsContentIntoTitleBar = true;
|
|
||||||
window.SetTitleBar(titleBar);
|
|
||||||
|
|
||||||
SetWindowText(handle, "胡桃");
|
|
||||||
}
|
|
||||||
|
|
||||||
window.Closed += OnWindowClosed;
|
|
||||||
RECT rect = RetriveWindowRect();
|
|
||||||
if (rect.Size > 0)
|
|
||||||
{
|
|
||||||
WINDOWPLACEMENT windowPlacement = WINDOWPLACEMENT.Create(new(-1, -1), rect, SHOW_WINDOW_CMD.SW_SHOWNORMAL);
|
|
||||||
SetWindowPlacement(handle, in windowPlacement);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool micaApplied = new SystemBackdrop(window).TrySetBackdrop();
|
|
||||||
logger.LogInformation(EventIds.BackdropState, "Apply {name} : {result}", nameof(SystemBackdrop), micaApplied ? "succeed" : "failed");
|
|
||||||
|
|
||||||
subClassProc = new(OnSubclassProcedure);
|
|
||||||
bool subClassApplied = SetWindowSubclass(handle, subClassProc, SubclassId, 0);
|
|
||||||
logger.LogInformation(EventIds.SubClassing, "Apply {name} : {result}", nameof(SUBCLASSPROC), subClassApplied ? "succeed" : "failed");
|
|
||||||
}
|
|
||||||
|
|
||||||
private void OnWindowClosed(object sender, WindowEventArgs args)
|
|
||||||
{
|
|
||||||
RemoveWindowSubclass(handle, subClassProc, SubclassId);
|
|
||||||
subClassProc = null;
|
|
||||||
SaveWindowRect(handle);
|
|
||||||
}
|
|
||||||
|
|
||||||
private LRESULT OnSubclassProcedure(HWND hwnd, uint uMsg, WPARAM wParam, LPARAM lParam, nuint uIdSubclass, nuint dwRefData)
|
|
||||||
{
|
|
||||||
switch (uMsg)
|
|
||||||
{
|
|
||||||
case WM_GETMINMAXINFO:
|
|
||||||
{
|
|
||||||
uint dpi = GetDpiForWindow(handle);
|
|
||||||
float scalingFactor = dpi / 96f;
|
|
||||||
Win32.Unsafe.SetMinTrackSize(lParam, MinWidth * scalingFactor, MinHeight * scalingFactor);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
|
|
||||||
}
|
|
||||||
|
|
||||||
private AppWindow GetAppWindow()
|
|
||||||
{
|
|
||||||
WindowId windowId = Win32Interop.GetWindowIdFromWindow(handle);
|
|
||||||
return AppWindow.GetFromWindowId(windowId);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
91
src/Snap.Hutao/Snap.Hutao/Core/Windowing/Interop.cs
Normal file
91
src/Snap.Hutao/Snap.Hutao/Core/Windowing/Interop.cs
Normal file
@@ -0,0 +1,91 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Microsoft.UI;
|
||||||
|
using Microsoft.UI.Windowing;
|
||||||
|
using System.Numerics;
|
||||||
|
using Windows.Win32.Foundation;
|
||||||
|
using Windows.Win32.Graphics.Gdi;
|
||||||
|
using Windows.Win32.UI.WindowsAndMessaging;
|
||||||
|
using static Windows.Win32.PInvoke;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core.Windowing;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// Win32/COM 窗体互操作
|
||||||
|
/// </summary>
|
||||||
|
internal static class Interop
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取 <see cref="AppWindow"/>
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hwnd">窗体句柄</param>
|
||||||
|
/// <returns>AppWindow</returns>
|
||||||
|
public static AppWindow GetAppWindow(HWND hwnd)
|
||||||
|
{
|
||||||
|
WindowId windowId = Win32Interop.GetWindowIdFromWindow(hwnd);
|
||||||
|
return AppWindow.GetFromWindowId(windowId);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 设置窗体位置
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hwnd">窗体句柄</param>
|
||||||
|
public static void SetWindowPosition(HWND hwnd)
|
||||||
|
{
|
||||||
|
if (WindowRect.RetriveWindowRect() is RECT { Size: > 0 } retrived)
|
||||||
|
{
|
||||||
|
WINDOWPLACEMENT windowPlacement = WINDOWPLACEMENT.Create(new(-1, -1), retrived, SHOW_WINDOW_CMD.SW_SHOWNORMAL);
|
||||||
|
SetWindowPlacement(hwnd, in windowPlacement);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Set first launch size.
|
||||||
|
Vector2 size = Interop.TransformSizeForWindow(new(1200, 741), hwnd);
|
||||||
|
SET_WINDOW_POS_FLAGS first = SET_WINDOW_POS_FLAGS.SWP_NOMOVE | SET_WINDOW_POS_FLAGS.SWP_NOZORDER;
|
||||||
|
SetWindowPos(hwnd, default, 0, 0, (int)size.X, (int)size.Y, first);
|
||||||
|
|
||||||
|
// Make it centralized
|
||||||
|
GetWindowRect(hwnd, out RECT rect);
|
||||||
|
Interop.TransformToCenterScreen(ref rect);
|
||||||
|
|
||||||
|
SET_WINDOW_POS_FLAGS center = SET_WINDOW_POS_FLAGS.SWP_NOSIZE | SET_WINDOW_POS_FLAGS.SWP_NOZORDER | SET_WINDOW_POS_FLAGS.SWP_NOACTIVATE;
|
||||||
|
SetWindowPos(hwnd, default, rect.left, rect.top, 0, 0, center);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 转换到当前窗体的DPI的尺寸
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="size">尺寸</param>
|
||||||
|
/// <param name="hwnd">窗体句柄</param>
|
||||||
|
/// <returns>对应当前窗体DPI的尺寸</returns>
|
||||||
|
public static Vector2 TransformSizeForWindow(Vector2 size, HWND hwnd)
|
||||||
|
{
|
||||||
|
uint dpi = GetDpiForWindow(hwnd);
|
||||||
|
float scale = (float)dpi / 96;
|
||||||
|
return new(size.X * scale, size.Y * scale);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 将矩形转换到屏幕中央
|
||||||
|
/// 当宽高超过时,会裁剪
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="rect">矩形</param>
|
||||||
|
public static void TransformToCenterScreen(ref RECT rect)
|
||||||
|
{
|
||||||
|
HMONITOR hMonitor = MonitorFromRect(rect, MONITOR_FROM_FLAGS.MONITOR_DEFAULTTONEAREST);
|
||||||
|
MONITORINFO mi = MONITORINFO.Default;
|
||||||
|
GetMonitorInfo(hMonitor, ref mi);
|
||||||
|
|
||||||
|
RECT workAreaRect = mi.rcWork;
|
||||||
|
|
||||||
|
int width = rect.right - rect.left;
|
||||||
|
int height = rect.bottom - rect.top;
|
||||||
|
|
||||||
|
rect.left = workAreaRect.left + ((workAreaRect.right - workAreaRect.left - width) / 2);
|
||||||
|
rect.top = workAreaRect.top + ((workAreaRect.bottom - workAreaRect.top - height) / 2);
|
||||||
|
rect.right = rect.left + width;
|
||||||
|
rect.bottom = rect.top + height;
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,7 +8,7 @@ using System.Runtime.InteropServices;
|
|||||||
using Windows.System;
|
using Windows.System;
|
||||||
using WinRT;
|
using WinRT;
|
||||||
|
|
||||||
namespace Snap.Hutao.Control.HostBackdrop;
|
namespace Snap.Hutao.Core.Windowing;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 系统背景帮助类
|
/// 系统背景帮助类
|
||||||
133
src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowManager.cs
Normal file
133
src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowManager.cs
Normal file
@@ -0,0 +1,133 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Microsoft.UI;
|
||||||
|
using Microsoft.UI.Windowing;
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
|
using Snap.Hutao.Core.Logging;
|
||||||
|
using System.Collections.Generic;
|
||||||
|
using Windows.Graphics;
|
||||||
|
using Windows.UI;
|
||||||
|
using Windows.Win32.Foundation;
|
||||||
|
using Windows.Win32.UI.Shell;
|
||||||
|
using WinRT.Interop;
|
||||||
|
using static Windows.Win32.PInvoke;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core.Windowing;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 窗口管理器
|
||||||
|
/// 主要包含了针对窗体的 P/Inoke 逻辑
|
||||||
|
/// </summary>
|
||||||
|
internal sealed class WindowManager : IDisposable
|
||||||
|
{
|
||||||
|
private const int MinWidth = 848;
|
||||||
|
private const int MinHeight = 524;
|
||||||
|
private const int SubclassId = 101;
|
||||||
|
|
||||||
|
private readonly HWND handle;
|
||||||
|
private readonly Window window;
|
||||||
|
private readonly FrameworkElement titleBar;
|
||||||
|
private readonly ILogger<WindowManager> logger;
|
||||||
|
private readonly WindowSubclassManager subclassManager;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个新的窗口状态管理器
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="window">窗口</param>
|
||||||
|
/// <param name="titleBar">充当标题栏的元素</param>
|
||||||
|
public WindowManager(Window window, FrameworkElement titleBar)
|
||||||
|
{
|
||||||
|
this.window = window;
|
||||||
|
this.titleBar = titleBar;
|
||||||
|
logger = Ioc.Default.GetRequiredService<ILogger<WindowManager>>();
|
||||||
|
|
||||||
|
handle = (HWND)WindowNative.GetWindowHandle(window);
|
||||||
|
subclassManager = new(handle);
|
||||||
|
|
||||||
|
InitializeWindow();
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
WindowRect.SaveWindowRect(handle);
|
||||||
|
subclassManager.Dispose();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void InitializeWindow()
|
||||||
|
{
|
||||||
|
if (AppWindowTitleBar.IsCustomizationSupported())
|
||||||
|
{
|
||||||
|
AppWindow appWindow = Interop.GetAppWindow(handle);
|
||||||
|
appWindow.Title = "胡桃";
|
||||||
|
|
||||||
|
AppWindowTitleBar appTitleBar = appWindow.TitleBar;
|
||||||
|
appTitleBar.ExtendsContentIntoTitleBar = true;
|
||||||
|
|
||||||
|
UpdateTitleButtonColor(appTitleBar);
|
||||||
|
UpdateDragRectangles(appTitleBar);
|
||||||
|
titleBar.SizeChanged += (s, e) => UpdateDragRectangles(appTitleBar);
|
||||||
|
titleBar.ActualThemeChanged += (s, e) => UpdateTitleButtonColor(appTitleBar);
|
||||||
|
|
||||||
|
appWindow.Show(true);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
SetWindowText(handle, "胡桃");
|
||||||
|
window.ExtendsContentIntoTitleBar = true;
|
||||||
|
window.SetTitleBar(titleBar);
|
||||||
|
|
||||||
|
window.Activate();
|
||||||
|
}
|
||||||
|
|
||||||
|
Interop.SetWindowPosition(handle);
|
||||||
|
|
||||||
|
bool micaApplied = new SystemBackdrop(window).TrySetBackdrop();
|
||||||
|
logger.LogInformation(EventIds.BackdropState, "Apply {name} : {result}", nameof(SystemBackdrop), micaApplied ? "succeed" : "failed");
|
||||||
|
|
||||||
|
bool subClassApplied = subclassManager.TrySetWindowSubclass();
|
||||||
|
logger.LogInformation(EventIds.SubClassing, "Apply {name} : {result}", nameof(SUBCLASSPROC), subClassApplied ? "succeed" : "failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateDragRectangles(AppWindowTitleBar appTitleBar)
|
||||||
|
{
|
||||||
|
uint dpi = GetDpiForWindow(handle);
|
||||||
|
|
||||||
|
// double scaleAdjustment = (uint)((((long)dpi * 100) + (96 >> 1)) / 96) / 100.0;
|
||||||
|
double scale = Math.Round(dpi / 96d, 2, MidpointRounding.AwayFromZero);
|
||||||
|
|
||||||
|
List<RectInt32> dragRectsList = new();
|
||||||
|
|
||||||
|
// 48 is the navigation button leftInset
|
||||||
|
RectInt32 dragRect = new((int)(48 * scale), 0, (int)(titleBar.ActualWidth * scale), (int)(titleBar.ActualHeight * scale));
|
||||||
|
dragRectsList.Add(dragRect);
|
||||||
|
|
||||||
|
RectInt32[] dragRects = dragRectsList.ToArray();
|
||||||
|
|
||||||
|
appTitleBar.SetDragRectangles(dragRects);
|
||||||
|
}
|
||||||
|
|
||||||
|
[SuppressMessage("", "CA1822")]
|
||||||
|
private void UpdateTitleButtonColor(AppWindowTitleBar appTitleBar)
|
||||||
|
{
|
||||||
|
appTitleBar.ButtonBackgroundColor = Colors.Transparent;
|
||||||
|
appTitleBar.ButtonInactiveBackgroundColor = Colors.Transparent;
|
||||||
|
|
||||||
|
Color systemBaseLowColor = (Color)App.Current.Resources["SystemBaseLowColor"];
|
||||||
|
appTitleBar.ButtonHoverBackgroundColor = systemBaseLowColor;
|
||||||
|
|
||||||
|
Color systemBaseMediumLowColor = (Color)App.Current.Resources["SystemBaseMediumLowColor"];
|
||||||
|
appTitleBar.ButtonPressedBackgroundColor = systemBaseMediumLowColor;
|
||||||
|
|
||||||
|
// The Foreground doesn't accept Alpha channel. So we translate it to gray.
|
||||||
|
byte light = (byte)((systemBaseMediumLowColor.R + systemBaseMediumLowColor.G + systemBaseMediumLowColor.B) / 3);
|
||||||
|
byte result = (byte)((systemBaseMediumLowColor.A / 255.0) * light);
|
||||||
|
appTitleBar.ButtonInactiveForegroundColor = Color.FromArgb(0XFF, result, result, result);
|
||||||
|
|
||||||
|
Color systemBaseHighColor = (Color)App.Current.Resources["SystemBaseHighColor"];
|
||||||
|
appTitleBar.ButtonForegroundColor = systemBaseHighColor;
|
||||||
|
appTitleBar.ButtonHoverForegroundColor = systemBaseHighColor;
|
||||||
|
appTitleBar.ButtonPressedForegroundColor = systemBaseHighColor;
|
||||||
|
}
|
||||||
|
}
|
||||||
45
src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowRect.cs
Normal file
45
src/Snap.Hutao/Snap.Hutao/Core/Windowing/WindowRect.cs
Normal file
@@ -0,0 +1,45 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Snap.Hutao.Core.Setting;
|
||||||
|
using Windows.Win32.Foundation;
|
||||||
|
using Windows.Win32.UI.WindowsAndMessaging;
|
||||||
|
using static Windows.Win32.PInvoke;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core.Windowing;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 窗体矩形
|
||||||
|
/// </summary>
|
||||||
|
internal static class WindowRect
|
||||||
|
{
|
||||||
|
/// <summary>
|
||||||
|
/// 获取窗体矩形
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>矩形</returns>
|
||||||
|
public static RECT RetriveWindowRect()
|
||||||
|
{
|
||||||
|
int left = LocalSetting.GetValueType<int>(SettingKeys.WindowLeft);
|
||||||
|
int top = LocalSetting.GetValueType<int>(SettingKeys.WindowTop);
|
||||||
|
int right = LocalSetting.GetValueType<int>(SettingKeys.WindowRight);
|
||||||
|
int bottom = LocalSetting.GetValueType<int>(SettingKeys.WindowBottom);
|
||||||
|
|
||||||
|
return new(left, top, right, bottom);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 保存窗体矩形
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="handle">窗体句柄</param>
|
||||||
|
public static void SaveWindowRect(HWND handle)
|
||||||
|
{
|
||||||
|
WINDOWPLACEMENT windowPlacement = WINDOWPLACEMENT.Default;
|
||||||
|
|
||||||
|
GetWindowPlacement(handle, ref windowPlacement);
|
||||||
|
|
||||||
|
LocalSetting.Set(SettingKeys.WindowLeft, windowPlacement.rcNormalPosition.left);
|
||||||
|
LocalSetting.Set(SettingKeys.WindowTop, windowPlacement.rcNormalPosition.top);
|
||||||
|
LocalSetting.Set(SettingKeys.WindowRight, windowPlacement.rcNormalPosition.right);
|
||||||
|
LocalSetting.Set(SettingKeys.WindowBottom, windowPlacement.rcNormalPosition.bottom);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,68 @@
|
|||||||
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Windows.Win32.Foundation;
|
||||||
|
using Windows.Win32.UI.Shell;
|
||||||
|
|
||||||
|
using static Windows.Win32.PInvoke;
|
||||||
|
|
||||||
|
namespace Snap.Hutao.Core.Windowing;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 窗体子类管理器
|
||||||
|
/// </summary>
|
||||||
|
internal class WindowSubclassManager : IDisposable
|
||||||
|
{
|
||||||
|
private const int SubclassId = 101;
|
||||||
|
|
||||||
|
private const int MinWidth = 848;
|
||||||
|
private const int MinHeight = 524;
|
||||||
|
|
||||||
|
private readonly HWND hwnd;
|
||||||
|
|
||||||
|
// We have to explictly hold a reference to the SUBCLASSPROC,
|
||||||
|
// otherwise will casuse System.ExecutionEngineException
|
||||||
|
private SUBCLASSPROC? subClassProc;
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 构造一个新的窗体子类管理器
|
||||||
|
/// </summary>
|
||||||
|
/// <param name="hwnd">窗体句柄</param>
|
||||||
|
public WindowSubclassManager(HWND hwnd)
|
||||||
|
{
|
||||||
|
this.hwnd = hwnd;
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <summary>
|
||||||
|
/// 尝试设置窗体子类
|
||||||
|
/// </summary>
|
||||||
|
/// <returns>是否设置成功</returns>
|
||||||
|
public bool TrySetWindowSubclass()
|
||||||
|
{
|
||||||
|
subClassProc = new(OnSubclassProcedure);
|
||||||
|
return SetWindowSubclass(hwnd, subClassProc, SubclassId, 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
/// <inheritdoc/>
|
||||||
|
public void Dispose()
|
||||||
|
{
|
||||||
|
RemoveWindowSubclass(hwnd, subClassProc, SubclassId);
|
||||||
|
subClassProc = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
private LRESULT OnSubclassProcedure(HWND hwnd, uint uMsg, WPARAM wParam, LPARAM lParam, nuint uIdSubclass, nuint dwRefData)
|
||||||
|
{
|
||||||
|
switch (uMsg)
|
||||||
|
{
|
||||||
|
case WM_GETMINMAXINFO:
|
||||||
|
{
|
||||||
|
uint dpi = GetDpiForWindow(hwnd);
|
||||||
|
float scalingFactor = dpi / 96f;
|
||||||
|
Win32.Unsafe.SetMinTrackSize(lParam, MinWidth * scalingFactor, MinHeight * scalingFactor);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return DefSubclassProc(hwnd, uMsg, wParam, lParam);
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -3,7 +3,7 @@
|
|||||||
|
|
||||||
using Microsoft.UI.Xaml;
|
using Microsoft.UI.Xaml;
|
||||||
using Snap.Hutao.Context.Database;
|
using Snap.Hutao.Context.Database;
|
||||||
using Snap.Hutao.Core;
|
using Snap.Hutao.Core.Windowing;
|
||||||
|
|
||||||
namespace Snap.Hutao;
|
namespace Snap.Hutao;
|
||||||
|
|
||||||
@@ -14,6 +14,7 @@ namespace Snap.Hutao;
|
|||||||
public sealed partial class MainWindow : Window
|
public sealed partial class MainWindow : Window
|
||||||
{
|
{
|
||||||
private readonly AppDbContext appDbContext;
|
private readonly AppDbContext appDbContext;
|
||||||
|
private readonly WindowManager windowManager;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构造一个新的主窗体
|
/// 构造一个新的主窗体
|
||||||
@@ -24,11 +25,13 @@ public sealed partial class MainWindow : Window
|
|||||||
{
|
{
|
||||||
this.appDbContext = appDbContext;
|
this.appDbContext = appDbContext;
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
_ = new WindowManager(this, TitleBarView.DragableArea);
|
windowManager = new WindowManager(this, (FrameworkElement)TitleBarView.DragableArea);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void MainWindowClosed(object sender, WindowEventArgs args)
|
private void MainWindowClosed(object sender, WindowEventArgs args)
|
||||||
{
|
{
|
||||||
|
windowManager.Dispose();
|
||||||
|
|
||||||
// save userdata datebase
|
// save userdata datebase
|
||||||
int changes = appDbContext.SaveChanges();
|
int changes = appDbContext.SaveChanges();
|
||||||
Verify.Operation(changes == 0, "存在未经处理的数据库记录更改");
|
Verify.Operation(changes == 0, "存在未经处理的数据库记录更改");
|
||||||
|
|||||||
@@ -1,10 +1,13 @@
|
|||||||
// Copyright (c) DGP Studio. All rights reserved.
|
// Copyright (c) DGP Studio. All rights reserved.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
|
|
||||||
|
using Microsoft.UI.Xaml;
|
||||||
using Microsoft.UI.Xaml.Controls;
|
using Microsoft.UI.Xaml.Controls;
|
||||||
|
using Snap.Hutao.Core.Logging;
|
||||||
using Snap.Hutao.Service.Abstraction;
|
using Snap.Hutao.Service.Abstraction;
|
||||||
using Snap.Hutao.Service.Navigation;
|
using Snap.Hutao.Service.Navigation;
|
||||||
using Snap.Hutao.View.Page;
|
using Snap.Hutao.View.Page;
|
||||||
|
using Windows.UI.ViewManagement;
|
||||||
|
|
||||||
namespace Snap.Hutao.View;
|
namespace Snap.Hutao.View;
|
||||||
|
|
||||||
@@ -15,6 +18,7 @@ public sealed partial class MainView : UserControl
|
|||||||
{
|
{
|
||||||
private readonly INavigationService navigationService;
|
private readonly INavigationService navigationService;
|
||||||
private readonly IInfoBarService infoBarService;
|
private readonly IInfoBarService infoBarService;
|
||||||
|
private readonly UISettings uISettings;
|
||||||
|
|
||||||
/// <summary>
|
/// <summary>
|
||||||
/// 构造一个新的主视图
|
/// 构造一个新的主视图
|
||||||
@@ -23,6 +27,9 @@ public sealed partial class MainView : UserControl
|
|||||||
{
|
{
|
||||||
InitializeComponent();
|
InitializeComponent();
|
||||||
|
|
||||||
|
uISettings = new();
|
||||||
|
uISettings.ColorValuesChanged += OnUISettingsColorValuesChanged;
|
||||||
|
|
||||||
infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
|
infoBarService = Ioc.Default.GetRequiredService<IInfoBarService>();
|
||||||
infoBarService.Initialize(InfoBarStack);
|
infoBarService.Initialize(InfoBarStack);
|
||||||
|
|
||||||
@@ -31,4 +38,28 @@ public sealed partial class MainView : UserControl
|
|||||||
|
|
||||||
navigationService.Navigate<AnnouncementPage>(INavigationAwaiter.Default, true);
|
navigationService.Navigate<AnnouncementPage>(INavigationAwaiter.Default, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void OnUISettingsColorValuesChanged(UISettings sender, object args)
|
||||||
|
{
|
||||||
|
Program.UIDispatcherQueue.TryEnqueue(UpdateTheme);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void UpdateTheme()
|
||||||
|
{
|
||||||
|
if (RequestedTheme.ToString() == App.Current.RequestedTheme.ToString())
|
||||||
|
{
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ILogger<MainView> logger = Ioc.Default.GetRequiredService<ILogger<MainView>>();
|
||||||
|
logger.LogInformation(EventIds.CommonLog, "Element Theme [{element}] App Theme [{app}]", RequestedTheme, App.Current.RequestedTheme);
|
||||||
|
|
||||||
|
// Update controls' theme which presents in the PopupRoot
|
||||||
|
RequestedTheme = App.Current.RequestedTheme switch
|
||||||
|
{
|
||||||
|
ApplicationTheme.Light => ElementTheme.Light,
|
||||||
|
ApplicationTheme.Dark => ElementTheme.Dark,
|
||||||
|
_ => throw Must.NeverHappen(),
|
||||||
|
};
|
||||||
|
}
|
||||||
}
|
}
|
||||||
@@ -94,8 +94,10 @@ openInWebview: function(url){ location.href = url }}";
|
|||||||
{
|
{
|
||||||
await WebView.EnsureCoreWebView2Async();
|
await WebView.EnsureCoreWebView2Async();
|
||||||
|
|
||||||
await WebView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(MihoyoSDKDefinition);
|
WebView.CoreWebView2.Settings.AreDefaultContextMenusEnabled = false;
|
||||||
WebView.CoreWebView2.WebMessageReceived += OnWebMessageReceived;
|
WebView.CoreWebView2.WebMessageReceived += OnWebMessageReceived;
|
||||||
|
|
||||||
|
await WebView.CoreWebView2.AddScriptToExecuteOnDocumentCreatedAsync(MihoyoSDKDefinition);
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -61,6 +61,7 @@
|
|||||||
TargetType="FlyoutPresenter"
|
TargetType="FlyoutPresenter"
|
||||||
BasedOn="{StaticResource DefaultFlyoutPresenterStyle}">
|
BasedOn="{StaticResource DefaultFlyoutPresenterStyle}">
|
||||||
<Setter Property="Padding" Value="0,2,0,2"/>
|
<Setter Property="Padding" Value="0,2,0,2"/>
|
||||||
|
<Setter Property="Background" Value="{ThemeResource FlyoutPresenterBackground}" />
|
||||||
</Style>
|
</Style>
|
||||||
</Flyout.FlyoutPresenterStyle>
|
</Flyout.FlyoutPresenterStyle>
|
||||||
<StackPanel>
|
<StackPanel>
|
||||||
|
|||||||
Reference in New Issue
Block a user