|
| 1 | +#include "pch.h" |
| 2 | +#include "CursorWindow.h" |
| 3 | + |
| 4 | +static HCURSOR CreateCursorFromBitmaps(HBITMAP hColorBitmap, HBITMAP hMaskBitmap) noexcept { |
| 5 | + ICONINFO iconInfo = { |
| 6 | + .fIcon = FALSE, |
| 7 | + .xHotspot = 0, |
| 8 | + .yHotspot = 0, |
| 9 | + .hbmMask = hMaskBitmap, |
| 10 | + .hbmColor = hColorBitmap |
| 11 | + }; |
| 12 | + return CreateIconIndirect(&iconInfo); |
| 13 | +} |
| 14 | + |
| 15 | +static HCURSOR CreateColorCursor() noexcept { |
| 16 | + const uint32_t width = (uint32_t)GetSystemMetrics(SM_CXCURSOR); |
| 17 | + const uint32_t height = (uint32_t)GetSystemMetrics(SM_CYCURSOR); |
| 18 | + |
| 19 | + // 颜色位图初始化为半透明红色 |
| 20 | + std::vector<uint32_t> colorBits(width * height, 0x80FF0000); |
| 21 | + |
| 22 | + wil::unique_hbitmap hColorBitmap(CreateBitmap(width, height, 1, 32, colorBits.data())); |
| 23 | + // 仍需要遮罩位图,不过既然颜色位图有透明通道,遮罩位图不会被使用,因此无需初始化 |
| 24 | + wil::unique_hbitmap hMaskBitmap(CreateBitmap(width, height, 1, 1, nullptr)); |
| 25 | + return CreateCursorFromBitmaps(hColorBitmap.get(), hMaskBitmap.get()); |
| 26 | +} |
| 27 | + |
| 28 | +static HCURSOR CreateMonochromeCursor() noexcept { |
| 29 | + const uint32_t width = (uint32_t)GetSystemMetrics(SM_CXCURSOR); |
| 30 | + const uint32_t height = (uint32_t)GetSystemMetrics(SM_CYCURSOR); |
| 31 | + |
| 32 | + // width 必定是 16 的倍数,因此遮罩位图每行都已按 WORD 对齐 |
| 33 | + assert(width % 16 == 0); |
| 34 | + const uint32_t widthByteCount = width / 8; |
| 35 | + // 单色光标无颜色位图,遮罩位图高度是光标高度的两倍 |
| 36 | + const uint32_t bitmapHeight = height * 2; |
| 37 | + |
| 38 | + // 分为四个部分: |
| 39 | + // 左上:白色 (AND=0, XOR=1) |
| 40 | + // 右上:黑色 (AND=0, XOR=0) |
| 41 | + // 左下:反色 (AND=1, XOR=1) |
| 42 | + // 右下:透明 (AND=1, XOR=0) |
| 43 | + assert(width % 16 == 0); |
| 44 | + std::vector<uint8_t> maskBits(widthByteCount * bitmapHeight); |
| 45 | + |
| 46 | + // AND 遮罩下半置 1 |
| 47 | + for (uint32_t y = height / 2; y < height; ++y) { |
| 48 | + for (uint32_t x = 0; x < width; ++x) { |
| 49 | + uint32_t byteIdx = y * widthByteCount + x / 8; |
| 50 | + uint32_t bitIdx = 7 - (x % 8); |
| 51 | + maskBits[byteIdx] |= 1 << bitIdx; |
| 52 | + } |
| 53 | + } |
| 54 | + |
| 55 | + // XOR 遮罩左半置 1 |
| 56 | + for (uint32_t y = height; y < bitmapHeight; ++y) { |
| 57 | + for (uint32_t x = 0; x < width / 2; ++x) { |
| 58 | + uint32_t byteIdx = y * widthByteCount + x / 8; |
| 59 | + uint32_t bitIdx = 7 - (x % 8); |
| 60 | + maskBits[byteIdx] |= 1 << bitIdx; |
| 61 | + } |
| 62 | + } |
| 63 | + |
| 64 | + wil::unique_hbitmap hMaskBitmap(CreateBitmap(width, bitmapHeight, 1, 1, maskBits.data())); |
| 65 | + return CreateCursorFromBitmaps(NULL, hMaskBitmap.get()); |
| 66 | +} |
| 67 | + |
| 68 | +static HCURSOR CreateMaskedColorCursor() noexcept { |
| 69 | + const uint32_t width = (uint32_t)GetSystemMetrics(SM_CXCURSOR); |
| 70 | + const uint32_t height = (uint32_t)GetSystemMetrics(SM_CYCURSOR); |
| 71 | + |
| 72 | + // width 必定是 16 的倍数,因此遮罩位图每行都已按 WORD 对齐 |
| 73 | + assert(width % 16 == 0); |
| 74 | + const uint32_t widthByteCount = width / 8; |
| 75 | + |
| 76 | + // 分为两个部分: |
| 77 | + // 左半:红色 (COLOR=0x00FF0000, MASK=0) |
| 78 | + // 右半:和红色 XOR (COLOR=0x00FF0000, MASK=1) |
| 79 | + |
| 80 | + // 颜色位图初始化为红色。注意透明通道为 0,否则会被识别为彩色光标,遮罩位图被忽略 |
| 81 | + std::vector<uint32_t> colorBits(width * height, 0x00FF0000); |
| 82 | + std::vector<uint8_t> maskBits(widthByteCount * height, 0); |
| 83 | + |
| 84 | + // XOR 遮罩右半置 1 |
| 85 | + for (uint32_t y = 0; y < height; ++y) { |
| 86 | + for (uint32_t x = width / 2; x < width; ++x) { |
| 87 | + uint32_t byteIdx = y * widthByteCount + x / 8; |
| 88 | + uint32_t bitIdx = 7 - (x % 8); |
| 89 | + maskBits[byteIdx] |= 1 << bitIdx; |
| 90 | + } |
| 91 | + } |
| 92 | + |
| 93 | + wil::unique_hbitmap hColorBitmap(CreateBitmap(width, height, 1, 32, colorBits.data())); |
| 94 | + wil::unique_hbitmap hMaskBitmap(CreateBitmap(width, height, 1, 1, maskBits.data())); |
| 95 | + return CreateCursorFromBitmaps(hColorBitmap.get(), hMaskBitmap.get()); |
| 96 | +} |
| 97 | + |
| 98 | +bool CursorWindow::Create() noexcept { |
| 99 | + static const wchar_t* WINDOW_NAME = L"CursorWindow"; |
| 100 | + |
| 101 | + _hCursor = CreateColorCursor(); |
| 102 | + |
| 103 | + WNDCLASSEXW wcex = { |
| 104 | + .cbSize = sizeof(WNDCLASSEX), |
| 105 | + .style = CS_HREDRAW | CS_VREDRAW, |
| 106 | + .lpfnWndProc = _WndProc, |
| 107 | + .hInstance = wil::GetModuleInstanceHandle(), |
| 108 | + .lpszClassName = WINDOW_NAME |
| 109 | + }; |
| 110 | + if (!RegisterClassEx(&wcex)) { |
| 111 | + return false; |
| 112 | + } |
| 113 | + |
| 114 | + CreateWindow( |
| 115 | + WINDOW_NAME, |
| 116 | + WINDOW_NAME, |
| 117 | + WS_OVERLAPPEDWINDOW, |
| 118 | + CW_USEDEFAULT, |
| 119 | + CW_USEDEFAULT, |
| 120 | + CW_USEDEFAULT, |
| 121 | + CW_USEDEFAULT, |
| 122 | + NULL, |
| 123 | + NULL, |
| 124 | + wil::GetModuleInstanceHandle(), |
| 125 | + this |
| 126 | + ); |
| 127 | + if (!Handle()) { |
| 128 | + return false; |
| 129 | + } |
| 130 | + |
| 131 | + const double dpiScale = _DpiScale(); |
| 132 | + SetWindowPos(Handle(), NULL, 0, 0, |
| 133 | + std::lround(500 * dpiScale), std::lround(400 * dpiScale), |
| 134 | + SWP_NOMOVE | SWP_SHOWWINDOW); |
| 135 | + return true; |
| 136 | +} |
| 137 | + |
| 138 | +LRESULT CursorWindow::_MessageHandler(UINT msg, WPARAM wParam, LPARAM lParam) noexcept { |
| 139 | + switch (msg) { |
| 140 | + case WM_CREATE: |
| 141 | + { |
| 142 | + const LRESULT ret = base_type::_MessageHandler(msg, wParam, lParam); |
| 143 | + |
| 144 | + const HMODULE hInst = wil::GetModuleInstanceHandle(); |
| 145 | + _hwndBtn1 = CreateWindow(L"BUTTON", L"彩色光标", |
| 146 | + WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, Handle(), (HMENU)1, hInst, 0); |
| 147 | + _hwndBtn2 = CreateWindow(L"BUTTON", L"单色光标", |
| 148 | + WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, Handle(), (HMENU)2, hInst, 0); |
| 149 | + _hwndBtn3 = CreateWindow(L"BUTTON", L"彩色掩码光标", |
| 150 | + WS_CHILD | WS_VISIBLE, 0, 0, 0, 0, Handle(), (HMENU)3, hInst, 0); |
| 151 | + _UpdateButtonPos(); |
| 152 | + |
| 153 | + SendMessage(_hwndBtn1, WM_SETFONT, (WPARAM)_UIFont(), TRUE); |
| 154 | + SendMessage(_hwndBtn2, WM_SETFONT, (WPARAM)_UIFont(), TRUE); |
| 155 | + SendMessage(_hwndBtn3, WM_SETFONT, (WPARAM)_UIFont(), TRUE); |
| 156 | + |
| 157 | + return ret; |
| 158 | + } |
| 159 | + case WM_COMMAND: |
| 160 | + { |
| 161 | + if (HIWORD(wParam) == BN_CLICKED) { |
| 162 | + const WORD btnId = LOWORD(wParam); |
| 163 | + if (btnId == 1) { |
| 164 | + _hCursor = CreateColorCursor(); |
| 165 | + } else if (btnId == 2) { |
| 166 | + _hCursor = CreateMonochromeCursor(); |
| 167 | + } else { |
| 168 | + _hCursor = CreateMaskedColorCursor(); |
| 169 | + } |
| 170 | + |
| 171 | + return 0; |
| 172 | + } |
| 173 | + break; |
| 174 | + } |
| 175 | + case WM_SIZE: |
| 176 | + { |
| 177 | + _UpdateButtonPos(); |
| 178 | + break; |
| 179 | + } |
| 180 | + case WM_SETCURSOR: |
| 181 | + { |
| 182 | + if (LOWORD(lParam) == HTCLIENT) { |
| 183 | + SetCursor(_hCursor); |
| 184 | + return TRUE; |
| 185 | + } |
| 186 | + break; |
| 187 | + } |
| 188 | + case WM_ERASEBKGND: |
| 189 | + { |
| 190 | + // 绘制渐变背景 |
| 191 | + PAINTSTRUCT ps; |
| 192 | + HDC hdc = BeginPaint(Handle(), &ps); |
| 193 | + |
| 194 | + RECT rc; |
| 195 | + GetClientRect(Handle(), &rc); |
| 196 | + |
| 197 | + TRIVERTEX vertices[] = { |
| 198 | + { |
| 199 | + .x = rc.left, |
| 200 | + .y = rc.top, |
| 201 | + .Red = 0xE000, |
| 202 | + .Green = 0x6000, |
| 203 | + .Blue = 0xE000 |
| 204 | + }, |
| 205 | + { |
| 206 | + .x = rc.right, |
| 207 | + .y = rc.bottom, |
| 208 | + .Red = 0x6000, |
| 209 | + .Green = 0xE000, |
| 210 | + .Blue = 0x6000 |
| 211 | + } |
| 212 | + }; |
| 213 | + GRADIENT_RECT rect = { 0, 1 }; |
| 214 | + GradientFill(hdc, vertices, 2, &rect, 1, GRADIENT_FILL_RECT_V); |
| 215 | + |
| 216 | + EndPaint(Handle(), &ps); |
| 217 | + return TRUE; |
| 218 | + } |
| 219 | + case WM_DESTROY: |
| 220 | + PostQuitMessage(0); |
| 221 | + break; |
| 222 | + } |
| 223 | + |
| 224 | + return base_type::_MessageHandler(msg, wParam, lParam); |
| 225 | +} |
| 226 | + |
| 227 | +void CursorWindow::_UpdateButtonPos() noexcept { |
| 228 | + RECT clientRect; |
| 229 | + GetClientRect(Handle(), &clientRect); |
| 230 | + |
| 231 | + const double dpiScale = _DpiScale(); |
| 232 | + const SIZE btnSize = { std::lround(160 * dpiScale),std::lround(40 * dpiScale) }; |
| 233 | + const LONG spacing = std::lround(8 * dpiScale); |
| 234 | + |
| 235 | + const LONG btnLeft = ((clientRect.right - clientRect.left) - btnSize.cx) / 2; |
| 236 | + const LONG windowCenterY = (clientRect.bottom - clientRect.top) / 2; |
| 237 | + |
| 238 | + SetWindowPos(_hwndBtn1, NULL, btnLeft, windowCenterY - 3 * btnSize.cy / 2 - spacing, |
| 239 | + btnSize.cx, btnSize.cy, SWP_NOACTIVATE); |
| 240 | + SetWindowPos(_hwndBtn2, NULL, btnLeft, windowCenterY - btnSize.cy / 2, |
| 241 | + btnSize.cx, btnSize.cy, SWP_NOACTIVATE); |
| 242 | + SetWindowPos(_hwndBtn3, NULL, btnLeft, windowCenterY + btnSize.cy / 2 + spacing, |
| 243 | + btnSize.cx, btnSize.cy, SWP_NOACTIVATE); |
| 244 | +} |
0 commit comments