Files
DX12/Engine/Platform/PlatformWin32.cpp
2026-03-19 18:27:49 +08:00

373 lines
9.0 KiB
C++
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
/**
* @file PlatformWin32.cpp
* @brief Win32 平台窗口系统实现。
* @details
* 该文件实现平台窗口后端的完整行为,包括:
* - Win32 窗口类注册与窗口创建;
* - 窗口 ID 与 HWND 的双向关联管理;
* - 大小变化、销毁、全屏切换等消息处理;
* - 对 Platform.h / Window.h 暴露接口的底层支撑。
*/
#ifdef _WIN64
#include "Platform.h"
#include "PlatformTypes.h"
namespace XEngine::platform {
namespace {
/**
* @brief Win32 窗口实例运行时状态。
*/
struct window_info
{
HWND hwnd{ nullptr };
RECT client_area{ 0,0,1920,1080 };
RECT fullscreen_area{};
POINT top_left{ 0,0 };
DWORD style{ WS_VISIBLE };
bool is_fullscreen{ false };
bool is_closed{ false };
};
/**
* @brief 窗口对象存储池。
*/
utl::free_list<window_info> windows;
/**
* @brief 根据窗口 ID 获取窗口状态对象。
* @param id 窗口 ID。
* @return 对应窗口状态引用。
*/
window_info&
get_from_id(window_id id)
{
assert(windows[id].hwnd);
return windows[id];
}
/**
* @brief 根据 HWND 获取窗口状态对象。
* @param handle 原生窗口句柄。
* @return 对应窗口状态引用。
*/
window_info&
get_from_handle(window_handle handle)
{
const window_id id{ (id::id_type)GetWindowLongPtr(handle, GWLP_USERDATA) };
return get_from_id(id);
}
/**
* @brief 标记窗口是否发生了可见尺寸变更。
*/
bool resized{ false };
/**
* @brief 按目标客户区更新窗口尺寸与位置。
* @param info 窗口状态。
* @param area 目标客户区矩形。
*/
void
resize_window(const window_info& info, const RECT& area)
{
RECT window_rect{ area };
AdjustWindowRect(&window_rect, info.style, FALSE);
const s32 width{ window_rect.right - window_rect.left };
const s32 height{ window_rect.bottom - window_rect.top };
MoveWindow(info.hwnd, info.top_left.x, info.top_left.y, width, height, true);
}
/// <Window parameter implementation>
/**
* @brief 查询窗口是否处于全屏状态。
* @param id 窗口 ID。
* @return 全屏返回 true。
*/
bool
is_window_fullscreen(window_id id)
{
assert(id != u32_invalid_id);
return get_from_id(id).is_fullscreen;
}
/**
* @brief 查询窗口原生句柄。
* @param id 窗口 ID。
* @return HWND 句柄。
*/
window_handle
get_window_handle(window_id id)
{
return get_from_id(id).hwnd;
}
/**
* @brief 设置窗口标题文本。
* @param id 窗口 ID。
* @param caption 标题文本。
*/
void
set_window_caption(window_id id, const wchar_t* caption)
{
window_info& info{ get_from_id(id) };
SetWindowText(info.hwnd, caption);
}
/**
* @brief 查询窗口矩形(客户区或全屏区)。
* @param id 窗口 ID。
* @return 左上右下坐标向量。
*/
math::u32v4
get_window_size(window_id id)
{
window_info& info{ get_from_id(id) };
RECT& rect{ info.is_fullscreen ? info.fullscreen_area : info.client_area };
return { (u32)rect.left,(u32)rect.top,(u32)rect.right,(u32)rect.bottom };
}
/**
* @brief 调整窗口客户区尺寸。
* @param id 窗口 ID。
* @param width 新宽度。
* @param height 新高度。
*/
void
resize_window(window_id id, u32 width, u32 height)
{
window_info& info{ get_from_id(id) };
// when we host the window in the level editor we just update
// the internal data.
if (info.style & WS_CHILD)
{
GetClientRect(info.hwnd, &info.client_area);
}
else
{
RECT& area{ info.is_fullscreen ? info.fullscreen_area : info.client_area };
area.bottom = area.top + height;
area.right = area.left + width;
resize_window(info, area);
}
}
/**
* @brief 查询窗口是否已被关闭。
* @param id 窗口 ID。
* @return 已关闭返回 true。
*/
bool
is_window_close(window_id id)
{
return get_from_id(id).is_closed;
}
/// <Window parameter implementation>
/**
* @brief 平台内部默认窗口过程。
* @param hwnd 窗口句柄。
* @param msg 消息类型。
* @param wparam 消息参数。
* @param lparam 消息参数。
* @return 消息处理结果。
*/
LRESULT CALLBACK internal_window_proc(HWND hwnd, UINT msg, WPARAM wparam, LPARAM lparam)
{
switch (msg)
{
case WM_NCCREATE:
{
DEBUG_OP(SetLastError(0));
const window_id id{ windows.add() };
windows[id].hwnd = hwnd;
SetWindowLongPtr(hwnd, GWLP_USERDATA, (LONG_PTR)id);
assert(GetLastError() == 0);
}
break;
case WM_DESTROY:
get_from_handle(hwnd).is_closed = true;
break;
case WM_SIZE:
resized = (wparam != SIZE_MINIMIZED);
break;
default:
break;
}
if (resized && GetAsyncKeyState(VK_LBUTTON) >= 0)
{
window_info& info{ get_from_handle(hwnd) };
assert(info.hwnd);
GetClientRect(info.hwnd, info.is_fullscreen ? &info.fullscreen_area : &info.client_area);
resized = false;
}
LONG_PTR long_ptr{ GetWindowLongPtr(hwnd, 0) };
return long_ptr
? ((window_proc)long_ptr)(hwnd, msg, wparam, lparam)
: DefWindowProc(hwnd, msg, wparam, lparam);
assert(GetLastError() == 0);
}
/**
* @brief 切换窗口全屏状态。
* @param id 窗口 ID。
* @param is_fullscreen 目标全屏状态。
*/
void
set_window_fullscreen(window_id id, bool is_fullscreen)
{
window_info& info{ windows[id] };
if (info.is_fullscreen != is_fullscreen)
{
info.is_fullscreen = is_fullscreen;
if (is_fullscreen)
{
GetClientRect(info.hwnd, &info.client_area);
RECT rect;
GetWindowRect(info.hwnd, &rect);
info.top_left.x = rect.left;
info.top_left.y = rect.top;
SetWindowLongPtr(info.hwnd, GWL_STYLE, info.style);
ShowWindow(info.hwnd, SW_MAXIMIZE);
}
else
{
SetWindowLongPtr(info.hwnd, GWL_STYLE, info.style);
resize_window(info, info.client_area);
ShowWindow(info.hwnd, SW_SHOWNORMAL);
}
}
}
} // namespace
/**
* @brief 创建 Win32 窗口并注册到窗口池。
* @param init_info 可选窗口初始化参数。
* @return 创建成功的窗口句柄对象。
*/
window
create_window(const window_init_info* init_info /* = nullptr */)
{
window_proc callback{ init_info ? init_info->callback : nullptr }; // 回调窗口过程:由初始化参数提供;未提供时为 nullptr。
window_handle parent{ init_info ? init_info->parent : nullptr }; // 父窗口句柄:用于子窗口挂载;未提供时创建顶层窗口。
WNDCLASSEX wc;
ZeroMemory(&wc, sizeof(WNDCLASSEX));
// 初始化 WNDCLASSEX 字段。
wc.cbSize = sizeof(WNDCLASSEX); // 结构体大小。
wc.style = CS_HREDRAW | CS_VREDRAW; // 水平/垂直尺寸变化时重绘。
wc.lpfnWndProc = internal_window_proc; // 默认窗口过程入口。
wc.cbClsExtra = 0; // 类额外字节数。
wc.cbWndExtra = callback ? sizeof(callback) : 0; // 实例额外字节数:有回调时用于存储回调指针。
wc.hInstance = 0; // 当前模块实例句柄(使用默认)。
wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); // 大图标。
wc.hCursor = LoadCursor(NULL, IDC_ARROW); // 光标。
wc.hbrBackground = CreateSolidBrush(RGB(26, 48, 76)); // 背景画刷。
wc.lpszMenuName = NULL; // 菜单名(无)。
wc.lpszClassName = L"XWindow"; // 窗口类名。
wc.hIconSm = LoadIcon(NULL, IDI_APPLICATION); // 小图标。
// 注册窗口类。
if (!RegisterClassEx(&wc))
{
// 若类已注册或注册失败,后续创建窗口时由系统返回结果。
}
window_info info{};
info.client_area.right = (init_info && init_info->width) ? info.client_area.left + init_info->width : info.client_area.right;
info.client_area.bottom = (init_info && init_info->height) ? info.client_area.top + init_info->height : info.client_area.bottom;
info.style |= parent ? WS_CHILD : WS_OVERLAPPEDWINDOW;
RECT rect{ info.client_area };
AdjustWindowRect(&rect, info.style, FALSE);
const wchar_t* caption{ (init_info && init_info->caption) ? init_info->caption : L"XGame" };
const s32 left{ (init_info) ? init_info->left : info.top_left.x };
const s32 top{ (init_info) ? init_info->top : info.top_left.y };
const s32 width{ rect.right - rect.left };
const s32 height{ rect.bottom - rect.top };
// 创建窗口实例。
info.hwnd = CreateWindowEx(
0, // 扩展样式。
wc.lpszClassName, // 窗口类名。
caption, // 窗口标题。
info.style, // 窗口样式。
// 尺寸与位置。
left, top, width, height,
parent, // 父窗口句柄nullptr 表示顶层窗口)。
nullptr, // 菜单句柄(未使用)。
nullptr, // 实例句柄(未显式传入)。
nullptr // 创建参数(未使用)。
);
if (info.hwnd)
{
DEBUG_OP(SetLastError(0));
if (callback) SetWindowLongPtr(info.hwnd, 0, (LONG_PTR)callback);
assert(GetLastError() == 0);
ShowWindow(info.hwnd, SW_SHOWNORMAL);
UpdateWindow(info.hwnd);
window_id id{ (id::id_type)GetWindowLongPtr(info.hwnd, GWLP_USERDATA) };
windows[id] = info;
return window{ id };
}
return {}; // 创建失败时返回无效窗口句柄。
}
/**
* @brief 销毁窗口并从窗口池移除。
* @param id 窗口 ID。
*/
void
remove_window(window_id id)
{
window_info& info{ get_from_id(id) };
DestroyWindow(info.hwnd);
windows.remove(id);
}
}
#include "IncludeWindowCpp.h"
#endif // _WIN64