Magpie/tools/WindowCase/CursorWindow.cpp
2026-01-08 16:57:14 +08:00

242 lines
7 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.

#include "pch.h"
#include "CursorWindow.h"
static wil::unique_hcursor CreateCursorFromBitmaps(HBITMAP hColorBitmap, HBITMAP hMaskBitmap) noexcept {
ICONINFO iconInfo = {
.fIcon = FALSE,
.xHotspot = 0,
.yHotspot = 0,
.hbmMask = hMaskBitmap,
.hbmColor = hColorBitmap
};
return wil::unique_hcursor(CreateIconIndirect(&iconInfo));
}
static wil::unique_hcursor CreateColorCursor() noexcept {
const uint32_t width = (uint32_t)GetSystemMetrics(SM_CXCURSOR);
const uint32_t height = (uint32_t)GetSystemMetrics(SM_CYCURSOR);
// 颜色位图初始化为半透明红色
std::vector<uint32_t> colorBits(width * height, 0x80FF0000);
wil::unique_hbitmap hColorBitmap(CreateBitmap(width, height, 1, 32, colorBits.data()));
// 仍需要遮罩位图,不过既然颜色位图有透明通道,遮罩位图不会被使用,因此无需初始化
wil::unique_hbitmap hMaskBitmap(CreateBitmap(width, height, 1, 1, nullptr));
return CreateCursorFromBitmaps(hColorBitmap.get(), hMaskBitmap.get());
}
static wil::unique_hcursor CreateMonochromeCursor() noexcept {
const uint32_t width = (uint32_t)GetSystemMetrics(SM_CXCURSOR);
const uint32_t height = (uint32_t)GetSystemMetrics(SM_CYCURSOR);
// width 必定是 16 的倍数,因此遮罩位图每行都已按 WORD 对齐
assert(width % 16 == 0);
const uint32_t widthByteCount = width / 8;
// 单色光标无颜色位图,遮罩位图高度是光标高度的两倍
const uint32_t bitmapHeight = height * 2;
// 分为四个部分:
// 左上:白色 (AND=0, XOR=1)
// 右上:黑色 (AND=0, XOR=0)
// 左下:反色 (AND=1, XOR=1)
// 右下:透明 (AND=1, XOR=0)
assert(width % 16 == 0);
std::vector<uint8_t> maskBits(widthByteCount * bitmapHeight);
// AND 遮罩下半置 1
for (uint32_t y = height / 2; y < height; ++y) {
for (uint32_t x = 0; x < width; ++x) {
uint32_t byteIdx = y * widthByteCount + x / 8;
uint32_t bitIdx = 7 - (x % 8);
maskBits[byteIdx] |= 1 << bitIdx;
}
}
// XOR 遮罩左半置 1
for (uint32_t y = height; y < bitmapHeight; ++y) {
for (uint32_t x = 0; x < width / 2; ++x) {
uint32_t byteIdx = y * widthByteCount + x / 8;
uint32_t bitIdx = 7 - (x % 8);
maskBits[byteIdx] |= 1 << bitIdx;
}
}
wil::unique_hbitmap hMaskBitmap(CreateBitmap(width, bitmapHeight, 1, 1, maskBits.data()));
return CreateCursorFromBitmaps(NULL, hMaskBitmap.get());
}
static wil::unique_hcursor CreateMaskedColorCursor() noexcept {
const uint32_t width = (uint32_t)GetSystemMetrics(SM_CXCURSOR);
const uint32_t height = (uint32_t)GetSystemMetrics(SM_CYCURSOR);
// width 必定是 16 的倍数,因此遮罩位图每行都已按 WORD 对齐
assert(width % 16 == 0);
const uint32_t widthByteCount = width / 8;
// 分为两个部分:
// 左半:红色 (COLOR=0x00FF0000, MASK=0)
// 右半:和红色 XOR (COLOR=0x00FF0000, MASK=1)
// 颜色位图初始化为红色。注意透明通道为 0否则会被识别为彩色光标
std::vector<uint32_t> colorBits(width * height, 0x00FF0000);
std::vector<uint8_t> maskBits(widthByteCount * height, 0);
// XOR 遮罩右半置 1
for (uint32_t y = 0; y < height; ++y) {
for (uint32_t x = width / 2; x < width; ++x) {
uint32_t byteIdx = y * widthByteCount + x / 8;
uint32_t bitIdx = 7 - (x % 8);
maskBits[byteIdx] |= 1 << bitIdx;
}
}
wil::unique_hbitmap hColorBitmap(CreateBitmap(width, height, 1, 32, colorBits.data()));
wil::unique_hbitmap hMaskBitmap(CreateBitmap(width, height, 1, 1, maskBits.data()));
return CreateCursorFromBitmaps(hColorBitmap.get(), hMaskBitmap.get());
}
bool CursorWindow::Create() noexcept {
static const wchar_t* WINDOW_NAME = L"CursorWindow";
_hCursor = CreateColorCursor();
WNDCLASSEXW wcex = {
.cbSize = sizeof(WNDCLASSEX),
.style = CS_HREDRAW | CS_VREDRAW,
.lpfnWndProc = _WndProc,
.hInstance = wil::GetModuleInstanceHandle(),
.lpszClassName = WINDOW_NAME
};
if (!RegisterClassEx(&wcex)) {
return false;
}
CreateWindow(
WINDOW_NAME,
WINDOW_NAME,
WS_OVERLAPPEDWINDOW,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
CW_USEDEFAULT,
NULL,
NULL,
wil::GetModuleInstanceHandle(),
this
);
if (!Handle()) {
return false;
}
const double dpiScale = _DpiScale();
SetWindowPos(Handle(), NULL, 0, 0,
std::lround(500 * dpiScale), std::lround(400 * dpiScale),
SWP_NOMOVE | SWP_SHOWWINDOW);
return true;
}
LRESULT CursorWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) noexcept {
switch (msg) {
case WM_CREATE:
{
const LRESULT ret = base_type::_MessageHandler(msg, wParam, lParam);
const HMODULE hInst = wil::GetModuleInstanceHandle();
_hwndBtn1 = CreateWindow(L"BUTTON", L"彩色光标",
WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, Handle(), (HMENU)1, hInst, 0);
_hwndBtn2 = CreateWindow(L"BUTTON", L"单色光标",
WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, Handle(), (HMENU)2, hInst, 0);
_hwndBtn3 = CreateWindow(L"BUTTON", L"彩色掩码光标",
WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, Handle(), (HMENU)3, hInst, 0);
_UpdateButtonPos();
SendMessage(_hwndBtn1, WM_SETFONT, (WPARAM)_UIFont(), TRUE);
SendMessage(_hwndBtn2, WM_SETFONT, (WPARAM)_UIFont(), TRUE);
SendMessage(_hwndBtn3, WM_SETFONT, (WPARAM)_UIFont(), TRUE);
return ret;
}
case WM_COMMAND:
{
if (HIWORD(wParam) == BN_CLICKED) {
const WORD btnId = LOWORD(wParam);
if (btnId == 1) {
_hCursor = CreateColorCursor();
} else if (btnId == 2) {
_hCursor = CreateMonochromeCursor();
} else {
_hCursor = CreateMaskedColorCursor();
}
return 0;
}
break;
}
case WM_SIZE:
{
_UpdateButtonPos();
break;
}
case WM_SETCURSOR:
{
if (LOWORD(lParam) == HTCLIENT) {
SetCursor(_hCursor.get());
return TRUE;
}
break;
}
case WM_ERASEBKGND:
{
// 绘制渐变背景
wil::unique_hdc_paint hdc = wil::BeginPaint(Handle());
RECT rc;
GetClientRect(Handle(), &rc);
TRIVERTEX vertices[] = {
{
.x = rc.left,
.y = rc.top,
.Red = 0xE000,
.Green = 0x6000,
.Blue = 0xE000
},
{
.x = rc.right,
.y = rc.bottom,
.Red = 0x6000,
.Green = 0xE000,
.Blue = 0x6000
}
};
GRADIENT_RECT rect = { 0, 1 };
GradientFill(hdc.get(), vertices, 2, &rect, 1, GRADIENT_FILL_RECT_V);
return TRUE;
}
case WM_DESTROY:
PostQuitMessage(0);
break;
}
return base_type::_MessageHandler(msg, wParam, lParam);
}
void CursorWindow::_UpdateButtonPos() noexcept {
RECT clientRect;
GetClientRect(Handle(), &clientRect);
const double dpiScale = _DpiScale();
const SIZE btnSize = { std::lround(160 * dpiScale),std::lround(40 * dpiScale) };
const LONG spacing = std::lround(8 * dpiScale);
const LONG btnLeft = ((clientRect.right - clientRect.left) - btnSize.cx) / 2;
const LONG windowCenterY = (clientRect.bottom - clientRect.top) / 2;
SetWindowPos(_hwndBtn1, NULL, btnLeft, windowCenterY - 3 * btnSize.cy / 2 - spacing,
btnSize.cx, btnSize.cy, SWP_NOACTIVATE);
SetWindowPos(_hwndBtn2, NULL, btnLeft, windowCenterY - btnSize.cy / 2,
btnSize.cx, btnSize.cy, SWP_NOACTIVATE);
SetWindowPos(_hwndBtn3, NULL, btnLeft, windowCenterY + btnSize.cy / 2 + spacing,
btnSize.cx, btnSize.cy, SWP_NOACTIVATE);
}