843 lines
28 KiB
C++
843 lines
28 KiB
C++
#include "win32_window.h"
|
||
|
||
#include <dwmapi.h>
|
||
#include <flutter_windows.h>
|
||
#include <shellscalingapi.h>
|
||
#include <windowsx.h>
|
||
|
||
#include <chrono>
|
||
#include <fstream>
|
||
#include <iomanip>
|
||
#include <mutex>
|
||
#include <sstream>
|
||
#include <string>
|
||
|
||
#include "resource.h"
|
||
|
||
// ============================================================
|
||
// WindowCompositionAttribute 相关定义(与 flutter_acrylic 一致)
|
||
// 用于调用 user32.dll 的未公开 API SetWindowCompositionAttribute
|
||
// 这是 flutter_acrylic 禁用 Mica/Acrylic 的关键调用
|
||
// ============================================================
|
||
typedef enum _WCA_WINDOWCOMPOSITIONATTRIB {
|
||
WCA_ATTRIB_ACCENT_POLICY = 19,
|
||
} WCA_WINDOWCOMPOSITIONATTRIB;
|
||
|
||
typedef enum _WCA_ACCENT_STATE {
|
||
WCA_ACCENT_DISABLED = 0,
|
||
WCA_ACCENT_ENABLE_ACRYLICBLURBEHIND = 4,
|
||
WCA_ACCENT_ENABLE_HOSTBACKDROP = 5,
|
||
} WCA_ACCENT_STATE;
|
||
|
||
typedef struct _WCA_ACCENT_POLICY {
|
||
WCA_ACCENT_STATE AccentState;
|
||
DWORD AccentFlags;
|
||
DWORD GradientColor;
|
||
DWORD AnimationId;
|
||
} WCA_ACCENT_POLICY;
|
||
|
||
typedef struct _WCA_WINDOWCOMPOSITIONATTRIBDATA {
|
||
WCA_WINDOWCOMPOSITIONATTRIB Attrib;
|
||
PVOID pvData;
|
||
SIZE_T cbData;
|
||
} WCA_WINDOWCOMPOSITIONATTRIBDATA;
|
||
|
||
typedef BOOL(WINAPI* WCA_SetWindowCompositionAttribute)(
|
||
HWND, WCA_WINDOWCOMPOSITIONATTRIBDATA*);
|
||
|
||
// ============================================================
|
||
// debug instrumentation for window-drag-lag
|
||
// ============================================================
|
||
namespace {
|
||
std::mutex g_debug_log_mutex;
|
||
std::wstring g_debug_log_path;
|
||
|
||
void DebugLog(const std::string& tag, const std::string& detail) {
|
||
// 已禁用文件日志以排除 I/O 对消息循环的干扰。
|
||
(void)tag;
|
||
(void)detail;
|
||
#if 0
|
||
auto now = std::chrono::system_clock::now();
|
||
auto us = std::chrono::duration_cast<std::chrono::microseconds>(
|
||
now.time_since_epoch())
|
||
.count();
|
||
std::lock_guard<std::mutex> lock(g_debug_log_mutex);
|
||
if (g_debug_log_path.empty()) {
|
||
g_debug_log_path = L"E:\\project\\flutter\\f\\xianyan\\xianyan_drag_debug.log";
|
||
}
|
||
std::wofstream ofs(g_debug_log_path, std::ios::app);
|
||
if (!ofs.is_open()) return;
|
||
ofs << us << L" [" << std::wstring(tag.begin(), tag.end()) << L"] "
|
||
<< std::wstring(detail.begin(), detail.end()) << L"\n";
|
||
#endif
|
||
}
|
||
|
||
// 仅记录拖拽状态关键事件,验证 EnterSizeMove/ExitSizeMove 是否被调用。
|
||
void DragDebugLog(const std::string& line) {
|
||
static std::mutex m;
|
||
static std::wstring path =
|
||
L"E:\\project\\flutter\\f\\xianyan\\xianyan_drag_state.log";
|
||
std::lock_guard<std::mutex> lock(m);
|
||
std::ofstream ofs(path, std::ios::app);
|
||
if (!ofs.is_open()) return;
|
||
auto now = std::chrono::system_clock::now();
|
||
auto ms = std::chrono::duration_cast<std::chrono::milliseconds>(
|
||
now.time_since_epoch())
|
||
.count();
|
||
ofs << ms << " " << line << "\n";
|
||
}
|
||
|
||
const char* HitTestName(LRESULT ht) {
|
||
switch (ht) {
|
||
case HTNOWHERE: return "HTNOWHERE";
|
||
case HTCLIENT: return "HTCLIENT";
|
||
case HTCAPTION: return "HTCAPTION";
|
||
case HTLEFT: return "HTLEFT";
|
||
case HTRIGHT: return "HTRIGHT";
|
||
case HTTOP: return "HTTOP";
|
||
case HTBOTTOM: return "HTBOTTOM";
|
||
case HTTOPLEFT: return "HTTOPLEFT";
|
||
case HTTOPRIGHT: return "HTTOPRIGHT";
|
||
case HTBOTTOMLEFT: return "HTBOTTOMLEFT";
|
||
case HTBOTTOMRIGHT: return "HTBOTTOMRIGHT";
|
||
case HTTRANSPARENT: return "HTTRANSPARENT";
|
||
default: return "OTHER";
|
||
}
|
||
}
|
||
|
||
std::string MsgName(UINT msg) {
|
||
switch (msg) {
|
||
case WM_NCHITTEST: return "WM_NCHITTEST";
|
||
case WM_NCLBUTTONDOWN: return "WM_NCLBUTTONDOWN";
|
||
case WM_NCLBUTTONUP: return "WM_NCLBUTTONUP";
|
||
case WM_NCMOUSEMOVE: return "WM_NCMOUSEMOVE";
|
||
case WM_LBUTTONDOWN: return "WM_LBUTTONDOWN";
|
||
case WM_LBUTTONUP: return "WM_LBUTTONUP";
|
||
case WM_MOUSEMOVE: return "WM_MOUSEMOVE";
|
||
case WM_MOVE: return "WM_MOVE";
|
||
case WM_MOVING: return "WM_MOVING";
|
||
case WM_SIZE: return "WM_SIZE";
|
||
case WM_WINDOWPOSCHANGING: return "WM_WINDOWPOSCHANGING";
|
||
case WM_WINDOWPOSCHANGED: return "WM_WINDOWPOSCHANGED";
|
||
case WM_SYSCOMMAND: return "WM_SYSCOMMAND";
|
||
case WM_ENTERSIZEMOVE: return "WM_ENTERSIZEMOVE";
|
||
case WM_EXITSIZEMOVE: return "WM_EXITSIZEMOVE";
|
||
case WM_PAINT: return "WM_PAINT";
|
||
case WM_ERASEBKGND: return "WM_ERASEBKGND";
|
||
default: return "MSG_" + std::to_string(msg);
|
||
}
|
||
}
|
||
} // namespace
|
||
|
||
namespace {
|
||
|
||
/// Window attribute that enables dark mode window decorations.
|
||
///
|
||
/// Redefined in case the developer's machine has a Windows SDK older than
|
||
/// version 10.0.22000.0.
|
||
/// See: https://docs.microsoft.com/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute
|
||
#ifndef DWMWA_USE_IMMERSIVE_DARK_MODE
|
||
#define DWMWA_USE_IMMERSIVE_DARK_MODE 20
|
||
#endif
|
||
|
||
// DWM 过渡动画与背景材质类型(兼容旧版 SDK)
|
||
#ifndef DWMWA_TRANSITIONS_FORCEDISABLED
|
||
#define DWMWA_TRANSITIONS_FORCEDISABLED 3
|
||
#endif
|
||
#ifndef DWMWA_SYSTEMBACKDROP_TYPE
|
||
#define DWMWA_SYSTEMBACKDROP_TYPE 38
|
||
#endif
|
||
#ifndef DWMWA_MICA_EFFECT
|
||
#define DWMWA_MICA_EFFECT 1029
|
||
#endif
|
||
|
||
// RtlGetVersion 用于获取真实系统版本(GetVersionExW 在 Win8.1+ 会返回兼容性版本)
|
||
typedef LONG NTSTATUS, *PNTSTATUS;
|
||
#define STATUS_SUCCESS (0x00000000)
|
||
typedef NTSTATUS(WINAPI* RtlGetVersionPtr)(PRTL_OSVERSIONINFOW);
|
||
#ifndef DWMSBT_AUTO
|
||
#define DWMSBT_AUTO 0
|
||
#endif
|
||
#ifndef DWMSBT_NONE
|
||
#define DWMSBT_NONE 1
|
||
#endif
|
||
#ifndef DWMSBT_MAINWINDOW
|
||
#define DWMSBT_MAINWINDOW 3
|
||
#endif
|
||
#ifndef DWMSBT_TRANSIENTWINDOW
|
||
#define DWMSBT_TRANSIENTWINDOW 4
|
||
#endif
|
||
#ifndef DWMSBT_TABBEDWINDOW
|
||
#define DWMSBT_TABBEDWINDOW 2
|
||
#endif
|
||
|
||
constexpr const wchar_t kWindowClassName[] = L"FLUTTER_RUNNER_WIN32_WINDOW";
|
||
|
||
/// Registry key for app theme preference.
|
||
///
|
||
/// A value of 0 indicates apps should use dark mode. A non-zero or missing
|
||
/// value indicates apps should use light mode.
|
||
constexpr const wchar_t kGetPreferredBrightnessRegKey[] =
|
||
L"Software\\Microsoft\\Windows\\CurrentVersion\\Themes\\Personalize";
|
||
constexpr const wchar_t kGetPreferredBrightnessRegValue[] = L"AppsUseLightTheme";
|
||
|
||
// The number of Win32Window objects that currently exist.
|
||
static int g_active_window_count = 0;
|
||
|
||
using EnableNonClientDpiScaling = BOOL __stdcall(HWND hwnd);
|
||
|
||
// Scale helper to convert logical scaler values to physical using passed in
|
||
// scale factor
|
||
int Scale(int source, double scale_factor) {
|
||
return static_cast<int>(source * scale_factor);
|
||
}
|
||
|
||
// Dynamically loads the |EnableNonClientDpiScaling| from the User32 module.
|
||
// This API is only needed for PerMonitor V1 awareness mode.
|
||
void EnableFullDpiSupportIfAvailable(HWND hwnd) {
|
||
HMODULE user32_module = LoadLibraryA("User32.dll");
|
||
if (!user32_module) {
|
||
return;
|
||
}
|
||
auto enable_non_client_dpi_scaling =
|
||
reinterpret_cast<EnableNonClientDpiScaling*>(
|
||
GetProcAddress(user32_module, "EnableNonClientDpiScaling"));
|
||
if (enable_non_client_dpi_scaling != nullptr) {
|
||
enable_non_client_dpi_scaling(hwnd);
|
||
}
|
||
FreeLibrary(user32_module);
|
||
}
|
||
|
||
} // namespace
|
||
|
||
// Manages the Win32Window's window class registration.
|
||
class WindowClassRegistrar {
|
||
public:
|
||
~WindowClassRegistrar() = default;
|
||
|
||
// Returns the singleton registrar instance.
|
||
static WindowClassRegistrar* GetInstance() {
|
||
if (!instance_) {
|
||
instance_ = new WindowClassRegistrar();
|
||
}
|
||
return instance_;
|
||
}
|
||
|
||
// Returns the name of the window class, registering the class if it hasn't
|
||
// previously been registered.
|
||
const wchar_t* GetWindowClass();
|
||
|
||
// Unregisters the window class. Should only be called if there are no
|
||
// instances of the window.
|
||
void UnregisterWindowClass();
|
||
|
||
private:
|
||
WindowClassRegistrar() = default;
|
||
|
||
static WindowClassRegistrar* instance_;
|
||
|
||
bool class_registered_ = false;
|
||
};
|
||
|
||
WindowClassRegistrar* WindowClassRegistrar::instance_ = nullptr;
|
||
|
||
const wchar_t* WindowClassRegistrar::GetWindowClass() {
|
||
if (!class_registered_) {
|
||
WNDCLASS window_class{};
|
||
window_class.hCursor = LoadCursor(nullptr, IDC_ARROW);
|
||
window_class.lpszClassName = kWindowClassName;
|
||
window_class.style = CS_DBLCLKS;
|
||
window_class.cbClsExtra = 0;
|
||
window_class.cbWndExtra = 0;
|
||
window_class.hInstance = GetModuleHandle(nullptr);
|
||
window_class.hIcon =
|
||
LoadIcon(window_class.hInstance, MAKEINTRESOURCE(IDI_APP_ICON));
|
||
window_class.hbrBackground = 0;
|
||
window_class.lpszMenuName = nullptr;
|
||
window_class.lpfnWndProc = Win32Window::WndProc;
|
||
RegisterClass(&window_class);
|
||
class_registered_ = true;
|
||
}
|
||
return kWindowClassName;
|
||
}
|
||
|
||
void WindowClassRegistrar::UnregisterWindowClass() {
|
||
UnregisterClass(kWindowClassName, nullptr);
|
||
class_registered_ = false;
|
||
}
|
||
|
||
Win32Window::Win32Window() {
|
||
++g_active_window_count;
|
||
}
|
||
|
||
Win32Window::~Win32Window() {
|
||
--g_active_window_count;
|
||
Destroy();
|
||
}
|
||
|
||
bool Win32Window::Create(const std::wstring& title,
|
||
const Point& origin,
|
||
const Size& size) {
|
||
Destroy();
|
||
|
||
const wchar_t* window_class =
|
||
WindowClassRegistrar::GetInstance()->GetWindowClass();
|
||
|
||
const POINT target_point = {static_cast<LONG>(origin.x),
|
||
static_cast<LONG>(origin.y)};
|
||
HMONITOR monitor = MonitorFromPoint(target_point, MONITOR_DEFAULTTONEAREST);
|
||
UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
|
||
double scale_factor = dpi / 96.0;
|
||
|
||
HWND window = CreateWindow(
|
||
window_class, title.c_str(),
|
||
WS_OVERLAPPEDWINDOW | WS_CLIPCHILDREN,
|
||
Scale(origin.x, scale_factor), Scale(origin.y, scale_factor),
|
||
Scale(size.width, scale_factor), Scale(size.height, scale_factor),
|
||
nullptr, nullptr, GetModuleHandle(nullptr), this);
|
||
|
||
if (!window) {
|
||
return false;
|
||
}
|
||
|
||
DetectBackdropCapabilities(&system_backdrop_supported_,
|
||
&mica_effect_supported_);
|
||
UpdateTheme(window);
|
||
|
||
return OnCreate();
|
||
}
|
||
|
||
bool Win32Window::Show() {
|
||
return ShowWindow(window_handle_, SW_SHOWNORMAL);
|
||
}
|
||
|
||
// static
|
||
LRESULT CALLBACK Win32Window::WndProc(HWND const window,
|
||
UINT const message,
|
||
WPARAM const wparam,
|
||
LPARAM const lparam) noexcept {
|
||
if (message == WM_NCCREATE) {
|
||
auto window_struct = reinterpret_cast<CREATESTRUCT*>(lparam);
|
||
SetWindowLongPtr(window, GWLP_USERDATA,
|
||
reinterpret_cast<LONG_PTR>(window_struct->lpCreateParams));
|
||
|
||
auto that = static_cast<Win32Window*>(window_struct->lpCreateParams);
|
||
EnableFullDpiSupportIfAvailable(window);
|
||
that->window_handle_ = window;
|
||
} else if (Win32Window* that = GetThisFromHandle(window)) {
|
||
return that->MessageHandler(window, message, wparam, lparam);
|
||
}
|
||
|
||
return DefWindowProc(window, message, wparam, lparam);
|
||
}
|
||
|
||
LRESULT
|
||
Win32Window::MessageHandler(HWND hwnd,
|
||
UINT const message,
|
||
WPARAM const wparam,
|
||
LPARAM const lparam) noexcept {
|
||
// region debug-point parent-msg
|
||
if (message == WM_NCHITTEST || message == WM_NCLBUTTONDOWN ||
|
||
message == WM_NCLBUTTONUP || message == WM_NCMOUSEMOVE ||
|
||
message == WM_LBUTTONDOWN || message == WM_LBUTTONUP ||
|
||
message == WM_MOUSEMOVE || message == WM_MOVE || message == WM_MOVING ||
|
||
message == WM_SIZE || message == WM_WINDOWPOSCHANGING ||
|
||
message == WM_WINDOWPOSCHANGED || message == WM_SYSCOMMAND ||
|
||
message == WM_ENTERSIZEMOVE || message == WM_EXITSIZEMOVE ||
|
||
message == WM_ERASEBKGND) {
|
||
std::ostringstream oss;
|
||
oss << "hwnd=" << hwnd << " msg=" << MsgName(message)
|
||
<< " wp=" << wparam << " lp=" << lparam;
|
||
DebugLog("parent", oss.str());
|
||
}
|
||
// endregion debug-point parent-msg
|
||
|
||
switch (message) {
|
||
case WM_DESTROY:
|
||
window_handle_ = nullptr;
|
||
Destroy();
|
||
if (quit_on_close_) {
|
||
PostQuitMessage(0);
|
||
}
|
||
return 0;
|
||
|
||
case WM_DPICHANGED: {
|
||
auto newRectSize = reinterpret_cast<RECT*>(lparam);
|
||
LONG newWidth = newRectSize->right - newRectSize->left;
|
||
LONG newHeight = newRectSize->bottom - newRectSize->top;
|
||
|
||
SetWindowPos(hwnd, nullptr, newRectSize->left, newRectSize->top, newWidth,
|
||
newHeight, SWP_NOZORDER | SWP_NOACTIVATE);
|
||
|
||
return 0;
|
||
}
|
||
case WM_SIZE: {
|
||
RECT rect = GetClientArea();
|
||
if (child_content_ != nullptr) {
|
||
// DwmExtendFrameIntoClientArea(margins={-1}) 会改变窗口帧布局,
|
||
// 但 GetClientRect 始终返回正确的客户区尺寸(左上角为 0,0),
|
||
// 所以 MoveWindow 从 (0,0) 开始填充整个客户区是正确的。
|
||
// 关键:确保 bRepaint=TRUE 以立即重绘。
|
||
MoveWindow(child_content_, 0, 0, rect.right, rect.bottom, TRUE);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
case WM_ACTIVATE:
|
||
if (child_content_ != nullptr) {
|
||
SetFocus(child_content_);
|
||
}
|
||
return 0;
|
||
|
||
case WM_DWMCOLORIZATIONCOLORCHANGED:
|
||
UpdateTheme(hwnd);
|
||
return 0;
|
||
|
||
case WM_ERASEBKGND: {
|
||
UINT64 enter = std::chrono::duration_cast<std::chrono::microseconds>(
|
||
std::chrono::system_clock::now().time_since_epoch())
|
||
.count();
|
||
LRESULT r = DefWindowProc(hwnd, message, wparam, lparam);
|
||
UINT64 now = std::chrono::duration_cast<std::chrono::microseconds>(
|
||
std::chrono::system_clock::now().time_since_epoch())
|
||
.count();
|
||
std::ostringstream oss;
|
||
oss << "WM_ERASEBKGND duration_us=" << (now - enter);
|
||
DebugLog("parent", oss.str());
|
||
return r;
|
||
}
|
||
|
||
case WM_ENTERSIZEMOVE: {
|
||
EnterSizeMove(hwnd);
|
||
break;
|
||
}
|
||
|
||
case WM_EXITSIZEMOVE: {
|
||
ExitSizeMove(hwnd);
|
||
break;
|
||
}
|
||
|
||
case WM_GETMINMAXINFO: {
|
||
// 处理窗口最小尺寸限制
|
||
MINMAXINFO* mmi = reinterpret_cast<MINMAXINFO*>(lparam);
|
||
if (min_width_ > 0 && min_height_ > 0) {
|
||
// 获取当前 DPI 缩放因子
|
||
HMONITOR monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
|
||
UINT dpi = FlutterDesktopGetDpiForMonitor(monitor);
|
||
double scale_factor = dpi / 96.0;
|
||
|
||
// 设置最小跟踪尺寸(物理像素)
|
||
mmi->ptMinTrackSize.x = static_cast<LONG>(min_width_ * scale_factor);
|
||
mmi->ptMinTrackSize.y = static_cast<LONG>(min_height_ * scale_factor);
|
||
}
|
||
return 0;
|
||
}
|
||
|
||
case WM_NCHITTEST: {
|
||
// 标题栏可拖拽区域返回 HTCAPTION,让 Windows 进入原生模态拖拽循环。
|
||
// 原生拖拽由 DWM 直接处理,不会经过 Flutter 插件链,避免 MethodChannel
|
||
// 和 Dart setState 导致的延迟。
|
||
POINT pt = {GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam)};
|
||
ScreenToClient(hwnd, &pt);
|
||
|
||
UINT dpi = GetDpiForWindow(hwnd);
|
||
double scale = static_cast<double>(dpi) / 96.0;
|
||
int title_h = static_cast<int>(title_bar_height_ * scale);
|
||
int btn_w = static_cast<int>(title_bar_button_width_ * scale);
|
||
RECT cr;
|
||
GetClientRect(hwnd, &cr);
|
||
|
||
{
|
||
std::ostringstream oss;
|
||
oss << "WM_NCHITTEST raw_screen=(" << GET_X_LPARAM(lparam) << ","
|
||
<< GET_Y_LPARAM(lparam) << ") client=(" << pt.x << "," << pt.y
|
||
<< ") dpi=" << dpi << " scale=" << scale
|
||
<< " title_h=" << title_h << " btn_w=" << btn_w
|
||
<< " cr=(" << cr.right << "," << cr.bottom << ")";
|
||
DebugLog("parent", oss.str());
|
||
}
|
||
|
||
if (cr.right <= 0 || cr.bottom <= 0) {
|
||
DebugLog("parent", "WM_NCHITTEST fallback: empty client rect");
|
||
break;
|
||
}
|
||
|
||
if (pt.y >= 0 && pt.y < title_h &&
|
||
pt.x >= 0 && pt.x < cr.right - btn_w) {
|
||
std::ostringstream oss;
|
||
oss << "WM_NCHITTEST -> HTCAPTION pt=(" << pt.x << "," << pt.y
|
||
<< ") title_h=" << title_h << " btn_w=" << btn_w
|
||
<< " cr=(" << cr.right << "," << cr.bottom << ")";
|
||
DebugLog("parent", oss.str());
|
||
return HTCAPTION;
|
||
}
|
||
std::ostringstream oss;
|
||
oss << "WM_NCHITTEST -> Def pt=(" << pt.x << "," << pt.y
|
||
<< ") title_h=" << title_h << " btn_w=" << btn_w;
|
||
DebugLog("parent", oss.str());
|
||
break;
|
||
}
|
||
|
||
case WM_NCLBUTTONDOWN: {
|
||
// 标题栏 HTCAPTION 的拖拽已在 FlutterWindow::MessageHandler 中
|
||
// 前置处理(先禁用 DWM 效果再进入原生拖拽循环)。
|
||
// 此处仅处理非标题栏区域点击,由 DefWindowProc 处理窗口边框调整大小等。
|
||
std::ostringstream oss;
|
||
oss << "WM_NCLBUTTONDOWN ht=" << HitTestName(static_cast<LRESULT>(wparam));
|
||
DebugLog("parent", oss.str());
|
||
break;
|
||
}
|
||
}
|
||
|
||
return DefWindowProc(window_handle_, message, wparam, lparam);
|
||
}
|
||
|
||
void Win32Window::Destroy() {
|
||
OnDestroy();
|
||
|
||
if (window_handle_) {
|
||
DestroyWindow(window_handle_);
|
||
window_handle_ = nullptr;
|
||
}
|
||
if (g_active_window_count == 0) {
|
||
WindowClassRegistrar::GetInstance()->UnregisterWindowClass();
|
||
}
|
||
}
|
||
|
||
Win32Window* Win32Window::GetThisFromHandle(HWND const window) noexcept {
|
||
return reinterpret_cast<Win32Window*>(
|
||
GetWindowLongPtr(window, GWLP_USERDATA));
|
||
}
|
||
|
||
void Win32Window::SetChildContent(HWND content) {
|
||
child_content_ = content;
|
||
SetParent(content, window_handle_);
|
||
RECT frame = GetClientArea();
|
||
|
||
MoveWindow(content, 0, 0, frame.right, frame.bottom, true);
|
||
|
||
SetFocus(child_content_);
|
||
}
|
||
|
||
RECT Win32Window::GetClientArea() {
|
||
RECT frame;
|
||
GetClientRect(window_handle_, &frame);
|
||
return frame;
|
||
}
|
||
|
||
HWND Win32Window::GetHandle() {
|
||
return window_handle_;
|
||
}
|
||
|
||
void Win32Window::SetQuitOnClose(bool quit_on_close) {
|
||
quit_on_close_ = quit_on_close;
|
||
}
|
||
|
||
bool Win32Window::OnCreate() {
|
||
// No-op; provided for subclasses.
|
||
return true;
|
||
}
|
||
|
||
void Win32Window::OnDestroy() {
|
||
// No-op; provided for subclasses.
|
||
}
|
||
|
||
void Win32Window::UpdateTheme(HWND const window) {
|
||
DWORD light_mode;
|
||
DWORD light_mode_size = sizeof(light_mode);
|
||
LSTATUS result = RegGetValue(HKEY_CURRENT_USER, kGetPreferredBrightnessRegKey,
|
||
kGetPreferredBrightnessRegValue,
|
||
RRF_RT_REG_DWORD, nullptr, &light_mode,
|
||
&light_mode_size);
|
||
|
||
if (result == ERROR_SUCCESS) {
|
||
BOOL enable_dark_mode = light_mode == 0;
|
||
DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE,
|
||
&enable_dark_mode, sizeof(enable_dark_mode));
|
||
}
|
||
}
|
||
|
||
void Win32Window::SetDarkMode(HWND const window, bool dark_mode) {
|
||
BOOL enable_dark_mode = dark_mode ? TRUE : FALSE;
|
||
DwmSetWindowAttribute(window, DWMWA_USE_IMMERSIVE_DARK_MODE,
|
||
&enable_dark_mode, sizeof(enable_dark_mode));
|
||
}
|
||
|
||
// ============================================================
|
||
// 静态成员变量初始化
|
||
// ============================================================
|
||
|
||
bool Win32Window::fullscreen_ = false;
|
||
RECT Win32Window::saved_window_rect_ = {0};
|
||
LONG Win32Window::saved_window_style_ = 0;
|
||
LONG Win32Window::saved_window_ex_style_ = 0;
|
||
WINDOWPLACEMENT Win32Window::saved_placement_ = {sizeof(WINDOWPLACEMENT)};
|
||
unsigned int Win32Window::min_width_ = 400;
|
||
unsigned int Win32Window::min_height_ = 600;
|
||
double Win32Window::title_bar_height_ = 36.0;
|
||
double Win32Window::title_bar_button_width_ = 138.0;
|
||
|
||
// ============================================================
|
||
// 窗口管理扩展方法实现
|
||
// ============================================================
|
||
|
||
void Win32Window::SetWindowTitle(HWND const window,
|
||
const std::wstring& title) {
|
||
SetWindowTextW(window, title.c_str());
|
||
}
|
||
|
||
void Win32Window::SetFullscreen(HWND const window, bool fullscreen) {
|
||
if (fullscreen == fullscreen_) {
|
||
return; // 状态未变化
|
||
}
|
||
|
||
if (fullscreen) {
|
||
// 进入全屏:保存当前窗口状态
|
||
saved_window_style_ = GetWindowLong(window, GWL_STYLE);
|
||
saved_window_ex_style_ = GetWindowLong(window, GWL_EXSTYLE);
|
||
GetWindowPlacement(window, &saved_placement_);
|
||
GetWindowRect(window, &saved_window_rect_);
|
||
|
||
// 移除标题栏和边框,设置为全屏样式
|
||
LONG new_style = saved_window_style_ & ~WS_OVERLAPPEDWINDOW;
|
||
SetWindowLong(window, GWL_STYLE, new_style);
|
||
|
||
// 获取屏幕尺寸并最大化
|
||
HMONITOR monitor = MonitorFromWindow(window, MONITOR_DEFAULTTONEAREST);
|
||
MONITORINFO monitor_info;
|
||
monitor_info.cbSize = sizeof(monitor_info);
|
||
GetMonitorInfo(monitor, &monitor_info);
|
||
|
||
SetWindowPos(window, HWND_TOP, monitor_info.rcMonitor.left,
|
||
monitor_info.rcMonitor.top,
|
||
monitor_info.rcMonitor.right - monitor_info.rcMonitor.left,
|
||
monitor_info.rcMonitor.bottom - monitor_info.rcMonitor.top,
|
||
SWP_NOZORDER | SWP_FRAMECHANGED | SWP_SHOWWINDOW);
|
||
|
||
fullscreen_ = true;
|
||
} else {
|
||
// 退出全屏:恢复保存的窗口状态
|
||
SetWindowLong(window, GWL_STYLE, saved_window_style_);
|
||
SetWindowLong(window, GWL_EXSTYLE, saved_window_ex_style_);
|
||
SetWindowPlacement(window, &saved_placement_);
|
||
SetWindowPos(window, nullptr, saved_window_rect_.left,
|
||
saved_window_rect_.top,
|
||
saved_window_rect_.right - saved_window_rect_.left,
|
||
saved_window_rect_.bottom - saved_window_rect_.top,
|
||
SWP_NOZORDER | SWP_FRAMECHANGED | SWP_SHOWWINDOW);
|
||
|
||
fullscreen_ = false;
|
||
}
|
||
}
|
||
|
||
bool Win32Window::IsFullscreen(HWND const window) {
|
||
return fullscreen_;
|
||
}
|
||
|
||
void Win32Window::SetMinSize(HWND const window, unsigned int width,
|
||
unsigned int height) {
|
||
min_width_ = width;
|
||
min_height_ = height;
|
||
}
|
||
|
||
void Win32Window::PerformHapticFeedback(HWND const window, int feedback_type) {
|
||
// Windows 无原生触觉反馈,使用 MessageBeep 模拟
|
||
// feedback_type: 0=light, 1=medium, 2=heavy, 3=selection
|
||
UINT beep_type = MB_OK; // 默认
|
||
switch (feedback_type) {
|
||
case 0: // light
|
||
beep_type = 0xFFFFFFFF; // 简单蜂鸣
|
||
break;
|
||
case 1: // medium
|
||
beep_type = MB_ICONINFORMATION;
|
||
break;
|
||
case 2: // heavy
|
||
beep_type = MB_ICONWARNING;
|
||
break;
|
||
case 3: // selection
|
||
beep_type = 0xFFFFFFFF;
|
||
break;
|
||
default:
|
||
break;
|
||
}
|
||
MessageBeep(beep_type);
|
||
}
|
||
|
||
std::string Win32Window::GetSystemAppearance() {
|
||
DWORD light_mode;
|
||
DWORD light_mode_size = sizeof(light_mode);
|
||
LSTATUS result = RegGetValue(HKEY_CURRENT_USER,
|
||
kGetPreferredBrightnessRegKey,
|
||
kGetPreferredBrightnessRegValue,
|
||
RRF_RT_REG_DWORD, nullptr, &light_mode,
|
||
&light_mode_size);
|
||
|
||
if (result == ERROR_SUCCESS && light_mode == 0) {
|
||
return "dark";
|
||
}
|
||
return "light";
|
||
}
|
||
|
||
// ============================================================
|
||
// 拖拽时临时禁用 DWM Mica/Acrylic 合成,避免拖拽卡顿
|
||
// ============================================================
|
||
|
||
void Win32Window::DetectBackdropCapabilities(bool* system_backdrop,
|
||
bool* mica_effect) {
|
||
*system_backdrop = false;
|
||
*mica_effect = false;
|
||
|
||
// 使用 RtlGetVersion 获取真实系统版本,避免 GetVersionExW 的兼容性谎言。
|
||
RTL_OSVERSIONINFOW osvi = {0};
|
||
osvi.dwOSVersionInfoSize = sizeof(osvi);
|
||
HMODULE ntdll = GetModuleHandleW(L"ntdll.dll");
|
||
if (ntdll) {
|
||
auto rtl_get_version = reinterpret_cast<RtlGetVersionPtr>(
|
||
GetProcAddress(ntdll, "RtlGetVersion"));
|
||
if (rtl_get_version && STATUS_SUCCESS != rtl_get_version(&osvi)) {
|
||
return;
|
||
}
|
||
}
|
||
if (osvi.dwMajorVersion < 10) {
|
||
return;
|
||
}
|
||
|
||
HWND hwnd = GetDesktopWindow();
|
||
if (osvi.dwBuildNumber >= 22523) {
|
||
INT probe = DWMSBT_AUTO;
|
||
*system_backdrop = SUCCEEDED(DwmSetWindowAttribute(
|
||
hwnd, DWMWA_SYSTEMBACKDROP_TYPE, &probe, sizeof(probe)));
|
||
}
|
||
if (osvi.dwBuildNumber >= 22000) {
|
||
BOOL probe = FALSE;
|
||
*mica_effect = SUCCEEDED(DwmSetWindowAttribute(
|
||
hwnd, DWMWA_MICA_EFFECT, &probe, sizeof(probe)));
|
||
}
|
||
}
|
||
|
||
int Win32Window::GetCurrentBackdropType(HWND hwnd) {
|
||
INT value = -1;
|
||
HRESULT hr = DwmGetWindowAttribute(hwnd, DWMWA_SYSTEMBACKDROP_TYPE,
|
||
&value, sizeof(value));
|
||
if (SUCCEEDED(hr)) {
|
||
return static_cast<int>(value);
|
||
}
|
||
return -1;
|
||
}
|
||
|
||
void Win32Window::SetBackdropType(HWND hwnd, int type) {
|
||
INT value = static_cast<INT>(type);
|
||
DwmSetWindowAttribute(hwnd, DWMWA_SYSTEMBACKDROP_TYPE, &value, sizeof(value));
|
||
}
|
||
|
||
void Win32Window::SetTransitionsEnabled(HWND hwnd, BOOL enabled) {
|
||
DwmSetWindowAttribute(hwnd, DWMWA_TRANSITIONS_FORCEDISABLED,
|
||
&enabled, sizeof(enabled));
|
||
}
|
||
|
||
void Win32Window::EnterSizeMove(HWND hwnd) {
|
||
if (is_in_size_move_loop_) return;
|
||
is_in_size_move_loop_ = true;
|
||
|
||
DragDebugLog("EnterSizeMove begin");
|
||
|
||
// ============================================================
|
||
// 关键:调用 SetWindowCompositionAttribute(ACCENT_DISABLED)
|
||
//
|
||
// 这是 flutter_acrylic 的 Window.setEffect(disabled) 能立即禁用 Mica
|
||
// 的关键调用,之前的 v4~v7 修复都遗漏了这一步。
|
||
//
|
||
// SetWindowCompositionAttribute 是 user32.dll 的未公开 API,
|
||
// 通过它设置窗口的 ACCENT_POLICY 为 ACCENT_DISABLED,可以立即禁用
|
||
// 所有窗口合成效果(包括 Mica/Acrylic)。
|
||
//
|
||
// 仅靠 DwmSetWindowAttribute 是不够的——它是异步的,DWM 不会立即应用
|
||
// 变更。SetWindowCompositionAttribute 能立即生效。
|
||
// ============================================================
|
||
{
|
||
HMODULE user32 = GetModuleHandleW(L"user32.dll");
|
||
if (user32) {
|
||
auto set_window_composition_attribute =
|
||
reinterpret_cast<WCA_SetWindowCompositionAttribute>(
|
||
GetProcAddress(user32, "SetWindowCompositionAttribute"));
|
||
if (set_window_composition_attribute) {
|
||
WCA_ACCENT_POLICY accent = {WCA_ACCENT_DISABLED, 2, 0, 0};
|
||
WCA_WINDOWCOMPOSITIONATTRIBDATA data;
|
||
data.Attrib = WCA_ATTRIB_ACCENT_POLICY;
|
||
data.pvData = &accent;
|
||
data.cbData = sizeof(accent);
|
||
set_window_composition_attribute(hwnd, &data);
|
||
DragDebugLog("SetWindowCompositionAttribute(ACCENT_DISABLED) called");
|
||
}
|
||
}
|
||
}
|
||
|
||
// 使用 {0, 0, 1, 0} 而非 {0, 0, 0, 0}:
|
||
// 与 flutter_acrylic 的 setEffect(disabled) 一致。
|
||
// 注释:At least one margin should be non-negative in order to show
|
||
// the DWM window shadow created by handling WM_NCCALCSIZE.
|
||
saved_margins_ = {-1, -1, -1, -1};
|
||
has_saved_margins_ = true;
|
||
MARGINS drag_margins = {0, 0, 1, 0};
|
||
DwmExtendFrameIntoClientArea(hwnd, &drag_margins);
|
||
|
||
// 禁用 System Backdrop(Win11 22523+)
|
||
saved_backdrop_type_ = GetCurrentBackdropType(hwnd);
|
||
DragDebugLog(std::string("saved_backdrop_type=") +
|
||
std::to_string(saved_backdrop_type_));
|
||
if (saved_backdrop_type_ > DWMSBT_NONE || saved_backdrop_type_ == -1) {
|
||
SetBackdropType(hwnd, DWMSBT_NONE);
|
||
}
|
||
|
||
// 禁用 Mica Effect(Win11 22000+)
|
||
if (mica_effect_supported_) {
|
||
DWORD value = FALSE;
|
||
DwmGetWindowAttribute(hwnd, DWMWA_MICA_EFFECT, &saved_mica_enabled_,
|
||
sizeof(saved_mica_enabled_));
|
||
DragDebugLog(std::string("saved_mica_enabled=") +
|
||
std::to_string(saved_mica_enabled_));
|
||
if (saved_mica_enabled_) {
|
||
DwmSetWindowAttribute(hwnd, DWMWA_MICA_EFFECT, &value, sizeof(value));
|
||
}
|
||
}
|
||
|
||
// 禁用 DWM 过渡动画,进一步降低合成延迟
|
||
DwmGetWindowAttribute(hwnd, DWMWA_TRANSITIONS_FORCEDISABLED,
|
||
&saved_transitions_enabled_,
|
||
sizeof(saved_transitions_enabled_));
|
||
SetTransitionsEnabled(hwnd, TRUE);
|
||
|
||
DragDebugLog("EnterSizeMove done");
|
||
}
|
||
|
||
void Win32Window::ExitSizeMove(HWND hwnd) {
|
||
if (!is_in_size_move_loop_) return;
|
||
is_in_size_move_loop_ = false;
|
||
|
||
DragDebugLog("ExitSizeMove begin");
|
||
|
||
// 恢复 backdrop
|
||
if (system_backdrop_supported_ && saved_backdrop_type_ > DWMSBT_NONE) {
|
||
SetBackdropType(hwnd, saved_backdrop_type_);
|
||
saved_backdrop_type_ = -1;
|
||
}
|
||
|
||
// 恢复 Mica effect
|
||
if (mica_effect_supported_ && saved_mica_enabled_) {
|
||
DwmSetWindowAttribute(hwnd, DWMWA_MICA_EFFECT, &saved_mica_enabled_,
|
||
sizeof(saved_mica_enabled_));
|
||
saved_mica_enabled_ = FALSE;
|
||
}
|
||
|
||
// 恢复 DWM 扩展帧边距
|
||
if (has_saved_margins_) {
|
||
DwmExtendFrameIntoClientArea(hwnd, &saved_margins_);
|
||
has_saved_margins_ = false;
|
||
}
|
||
|
||
// 恢复 DWM 过渡动画
|
||
SetTransitionsEnabled(hwnd, saved_transitions_enabled_);
|
||
|
||
DragDebugLog("ExitSizeMove done");
|
||
}
|