/ src / modules / MeasureTool / MeasureToolCore / BoundsToolOverlayUI.cpp
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  }