BoundsToolOverlayUI.cpp
1 #include "pch.h" 2 #include "BoundsToolOverlayUI.h" 3 #include "CoordinateSystemConversion.h" 4 #include "Clipboard.h" 5 #include "constants.h" 6 7 #include <common/utils/window.h> 8 #include <vector> 9 10 namespace 11 { 12 Measurement GetMeasurement(const CursorDrag& currentBounds, POINT cursorPos, float px2mmRatio) 13 { 14 D2D1_RECT_F rect; 15 std::tie(rect.left, rect.right) = 16 std::minmax(static_cast<float>(cursorPos.x), currentBounds.startPos.x); 17 std::tie(rect.top, rect.bottom) = 18 std::minmax(static_cast<float>(cursorPos.y), currentBounds.startPos.y); 19 20 return Measurement(rect, px2mmRatio); 21 } 22 23 void CopyToClipboard(HWND window, const BoundsToolState& toolState, POINT cursorPos) 24 { 25 std::vector<Measurement> allMeasurements; 26 for (const auto& [handle, perScreen] : toolState.perScreen) 27 { 28 allMeasurements.append_range(perScreen.measurements); 29 30 if (handle == window && perScreen.currentBounds) 31 { 32 auto px2mmRatio = toolState.commonState->GetPhysicalPx2MmRatio(window); 33 allMeasurements.push_back(GetMeasurement(*perScreen.currentBounds, cursorPos, px2mmRatio)); 34 } 35 } 36 37 SetClipboardToMeasurements(allMeasurements, true, true, toolState.commonState->units); 38 } 39 40 void ToggleCursor(const bool show) 41 { 42 if (show) 43 { 44 for (; ShowCursor(show) < 0;) 45 ; 46 } 47 else 48 { 49 for (; ShowCursor(show) >= 0;) 50 ; 51 } 52 } 53 54 void HandleCursorMove(HWND window, BoundsToolState* toolState, const POINT cursorPos, const DWORD touchID = 0) 55 { 56 if (!toolState->perScreen[window].currentBounds || (toolState->perScreen[window].currentBounds->touchID != touchID)) 57 return; 58 59 toolState->perScreen[window].currentBounds->currentPos = 60 D2D_POINT_2F{ .x = static_cast<float>(cursorPos.x), .y = static_cast<float>(cursorPos.y) }; 61 } 62 63 void HandleCursorDown(HWND window, BoundsToolState* toolState, const POINT cursorPos, const DWORD touchID = 0) 64 { 65 ToggleCursor(false); 66 67 RECT windowRect; 68 if (GetWindowRect(window, &windowRect)) 69 ClipCursor(&windowRect); 70 71 const D2D_POINT_2F newBoundsStart = { .x = static_cast<float>(cursorPos.x), .y = static_cast<float>(cursorPos.y) }; 72 toolState->perScreen[window].currentBounds = CursorDrag{ 73 .startPos = newBoundsStart, 74 .currentPos = newBoundsStart, 75 .touchID = touchID 76 }; 77 } 78 79 void HandleCursorUp(HWND window, BoundsToolState* toolState, const POINT cursorPos) 80 { 81 ToggleCursor(true); 82 ClipCursor(nullptr); 83 CopyToClipboard(window, *toolState, cursorPos); 84 85 auto& perScreen = toolState->perScreen[window]; 86 87 if (const bool shiftPress = GetKeyState(VK_SHIFT) & 0x80000; shiftPress && perScreen.currentBounds) 88 { 89 auto px2mmRatio = toolState->commonState->GetPhysicalPx2MmRatio(window); 90 perScreen.measurements.push_back(GetMeasurement(*perScreen.currentBounds, cursorPos, px2mmRatio)); 91 } 92 93 perScreen.currentBounds = std::nullopt; 94 } 95 } 96 97 LRESULT CALLBACK BoundsToolWndProc(HWND window, UINT message, WPARAM wparam, LPARAM lparam) noexcept 98 { 99 switch (message) 100 { 101 case WM_CREATE: 102 { 103 auto toolState = GetWindowCreateParam<BoundsToolState*>(lparam); 104 StoreWindowParam(window, toolState); 105 break; 106 } 107 case WM_ERASEBKGND: 108 return 1; 109 case WM_KEYUP: 110 if (wparam == VK_ESCAPE) 111 { 112 if (const auto* toolState = GetWindowParam<BoundsToolState*>(window)) 113 { 114 CopyToClipboard(window, *toolState, convert::FromSystemToWindow(window, toolState->commonState->cursorPosSystemSpace)); 115 } 116 117 PostMessageW(window, WM_CLOSE, {}, {}); 118 } 119 break; 120 case WM_LBUTTONDOWN: 121 { 122 const bool touchEvent = (GetMessageExtraInfo() & consts::MOUSEEVENTF_FROMTOUCH) == consts::MOUSEEVENTF_FROMTOUCH; 123 if (touchEvent) 124 break; 125 126 auto toolState = GetWindowParam<BoundsToolState*>(window); 127 if (!toolState) 128 break; 129 130 HandleCursorDown(window, 131 toolState, 132 convert::FromSystemToWindow(window, toolState->commonState->cursorPosSystemSpace)); 133 break; 134 } 135 case WM_CURSOR_LEFT_MONITOR: 136 { 137 ToggleCursor(true); 138 139 ClipCursor(nullptr); 140 auto toolState = GetWindowParam<BoundsToolState*>(window); 141 if (!toolState) 142 break; 143 toolState->perScreen[window].currentBounds = std::nullopt; 144 break; 145 } 146 case WM_TOUCH: 147 { 148 auto toolState = GetWindowParam<BoundsToolState*>(window); 149 if (!toolState) 150 break; 151 std::array<TOUCHINPUT, 8> inputs; 152 const size_t nInputs = std::min(static_cast<size_t>(LOWORD(wparam)), inputs.size()); 153 const auto inputHandle = std::bit_cast<HTOUCHINPUT>(lparam); 154 GetTouchInputInfo(inputHandle, static_cast<UINT>(nInputs), inputs.data(), sizeof(TOUCHINPUT)); 155 156 for (UINT i = 0; i < nInputs; ++i) 157 { 158 const auto& input = inputs[i]; 159 160 if (const bool down = (input.dwFlags & TOUCHEVENTF_DOWN) && (input.dwFlags & TOUCHEVENTF_PRIMARY); down) 161 { 162 HandleCursorDown( 163 window, 164 toolState, 165 POINT{ TOUCH_COORD_TO_PIXEL(input.x), TOUCH_COORD_TO_PIXEL(input.y) }, 166 input.dwID); 167 continue; 168 } 169 170 if (const bool up = input.dwFlags & TOUCHEVENTF_UP; up) 171 { 172 HandleCursorUp( 173 window, 174 toolState, 175 POINT{ TOUCH_COORD_TO_PIXEL(input.x), TOUCH_COORD_TO_PIXEL(input.y) }); 176 continue; 177 } 178 179 if (const bool move = input.dwFlags & TOUCHEVENTF_MOVE; move) 180 { 181 HandleCursorMove(window, 182 toolState, 183 POINT{ TOUCH_COORD_TO_PIXEL(input.x), TOUCH_COORD_TO_PIXEL(input.y) }, 184 input.dwID); 185 continue; 186 } 187 } 188 189 CloseTouchInputHandle(inputHandle); 190 break; 191 } 192 193 case WM_MOUSEMOVE: 194 { 195 const bool touchEvent = (GetMessageExtraInfo() & consts::MOUSEEVENTF_FROMTOUCH) == consts::MOUSEEVENTF_FROMTOUCH; 196 if (touchEvent) 197 break; 198 199 auto toolState = GetWindowParam<BoundsToolState*>(window); 200 if (!toolState) 201 break; 202 203 HandleCursorMove(window, 204 toolState, 205 convert::FromSystemToWindow(window, toolState->commonState->cursorPosSystemSpace)); 206 break; 207 } 208 209 case WM_LBUTTONUP: 210 { 211 const bool touchEvent = (GetMessageExtraInfo() & consts::MOUSEEVENTF_FROMTOUCH) == consts::MOUSEEVENTF_FROMTOUCH; 212 if (touchEvent) 213 break; 214 215 auto toolState = GetWindowParam<BoundsToolState*>(window); 216 if (!toolState) 217 break; 218 219 HandleCursorUp(window, 220 toolState, 221 convert::FromSystemToWindow(window, toolState->commonState->cursorPosSystemSpace)); 222 break; 223 } 224 case WM_RBUTTONUP: 225 { 226 const bool touchEvent = (GetMessageExtraInfo() & consts::MOUSEEVENTF_FROMTOUCH) == consts::MOUSEEVENTF_FROMTOUCH; 227 if (touchEvent) 228 break; 229 230 ToggleCursor(true); 231 232 auto* toolState = GetWindowParam<BoundsToolState*>(window); 233 if (!toolState) 234 break; 235 236 auto& perScreen = toolState->perScreen[window]; 237 238 if (perScreen.currentBounds) 239 { 240 perScreen.currentBounds = std::nullopt; 241 } 242 else 243 { 244 if (perScreen.measurements.empty()) 245 { 246 PostMessageW(window, WM_CLOSE, {}, {}); 247 } 248 else 249 { 250 perScreen.measurements.clear(); 251 } 252 } 253 break; 254 } 255 } 256 257 return DefWindowProcW(window, message, wparam, lparam); 258 } 259 260 namespace 261 { 262 void DrawMeasurement(const Measurement& measurement, 263 const CommonState& commonState, 264 HWND window, 265 const D2DState& d2dState, 266 std::optional<D2D_POINT_2F> textBoxCenter) 267 { 268 const bool screenQuadrantAware = textBoxCenter.has_value(); 269 d2dState.ToggleAliasedLinesMode(true); 270 d2dState.dxgiWindowState.rt->DrawRectangle(measurement.rect, d2dState.solidBrushes[Brush::line].get()); 271 d2dState.ToggleAliasedLinesMode(false); 272 273 OverlayBoxText text; 274 const auto [crossSymbolPos, measureStringBufLen] = 275 measurement.Print(text.buffer.data(), 276 text.buffer.size(), 277 true, 278 true, 279 commonState.units | Measurement::Unit::Pixel); // Always show pixels. 280 281 D2D_POINT_2F textBoxPos; 282 if (textBoxCenter) 283 textBoxPos = *textBoxCenter; 284 else 285 { 286 textBoxPos.x = measurement.rect.left + measurement.Width(Measurement::Unit::Pixel) / 2; 287 textBoxPos.y = measurement.rect.top + measurement.Height(Measurement::Unit::Pixel) / 2; 288 } 289 290 d2dState.DrawTextBox(text.buffer.data(), 291 measureStringBufLen, 292 crossSymbolPos, 293 textBoxPos, 294 screenQuadrantAware, 295 window); 296 } 297 } 298 299 void DrawBoundsToolTick(const CommonState& commonState, 300 const BoundsToolState& toolState, 301 const HWND window, 302 const D2DState& d2dState) 303 { 304 const auto it = toolState.perScreen.find(window); 305 if (it == end(toolState.perScreen)) 306 return; 307 308 d2dState.dxgiWindowState.rt->Clear(); 309 310 const auto& perScreen = it->second; 311 for (const auto& measure : perScreen.measurements) 312 DrawMeasurement(measure, commonState, window, d2dState, {}); 313 314 if (perScreen.currentBounds.has_value()) 315 { 316 D2D1_RECT_F rect; 317 std::tie(rect.left, rect.right) = std::minmax(perScreen.currentBounds->startPos.x, perScreen.currentBounds->currentPos.x); 318 std::tie(rect.top, rect.bottom) = std::minmax(perScreen.currentBounds->startPos.y, perScreen.currentBounds->currentPos.y); 319 auto px2mmRatio = toolState.commonState->GetPhysicalPx2MmRatio(window); 320 DrawMeasurement(Measurement{ rect, px2mmRatio }, commonState, window, d2dState, perScreen.currentBounds->currentPos); 321 } 322 }