/ tools / FancyZones_DrawLayoutTest / FancyZones_DrawLayoutTest.cpp
FancyZones_DrawLayoutTest.cpp
  1  #include "framework.h"
  2  #include "FancyZones_DrawLayoutTest.h"
  3  
  4  #include <Uxtheme.h>
  5  #include <objidl.h>
  6  #include <gdiplus.h>
  7  #include <Dwmapi.h>
  8  #include <shellscalingapi.h>
  9  
 10  #include <vector>
 11  #include <thread>
 12  #include <string>
 13  
 14  using namespace Gdiplus;
 15  
 16  #pragma comment (lib,"Gdiplus.lib")
 17  #pragma comment (lib,"UXTheme.lib")
 18  #pragma comment (lib,"Dwmapi.lib")
 19  #pragma comment (lib,"Shcore.lib")
 20  
 21  constexpr int   ZONE_COUNT           = 4;
 22  constexpr int   ANIMATION_TIME       = 200; // milliseconds
 23  constexpr int   DISPLAY_REFRESH_TIME = 10;  // milliseconds
 24  constexpr DWORD Q_KEY_CODE           = 0x51;
 25  constexpr DWORD W_KEY_CODE           = 0x57;
 26  
 27  HWND  mainWindow;
 28  HHOOK keyboardHook;
 29  bool  showZoneLayout = false;
 30  
 31  LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
 32  
 33  std::vector<RECT> zones{};
 34  std::vector<bool> highlighted{};
 35  
 36  inline const int RectWidth(const RECT& rect)
 37  {
 38      return rect.right - rect.left;
 39  }
 40  
 41  inline const int RectHeight(const RECT& rect)
 42  {
 43      return rect.bottom - rect.top;
 44  }
 45  
 46  std::vector<RECT> BuildColumnZoneLayout(int zoneCount, const RECT& workArea)
 47  {
 48      // Builds column layout with specified number of zones (columns).
 49      int zoneWidth = RectWidth(workArea) / zoneCount;
 50      int zoneHeight = RectHeight(workArea);
 51      std::vector<RECT> zones(zoneCount);
 52      for (int i = 0; i < zoneCount; ++i)
 53      {
 54          int left   = workArea.left + i * zoneWidth;
 55          int top    = workArea.top;
 56          int right  = left + zoneWidth;
 57          int bottom = top + zoneHeight;
 58  
 59          zones[i] = { left, top, right, bottom };
 60      }
 61      return zones;
 62  }
 63  
 64  int GetHighlightedZoneIdx(const std::vector<RECT>& zones, const POINT& cursorPosition)
 65  {
 66      // Determine which zone should be highlighted based on cursor position.
 67      for (size_t i = 0; i < zones.size(); ++i)
 68      {
 69          if (cursorPosition.x >= zones[i].left && cursorPosition.x < zones[i].right)
 70          {
 71              return static_cast<int>(i);
 72          }
 73      }
 74      return -1;
 75  }
 76  
 77  void ShowZonesOverlay()
 78  {
 79      // InvalidateRect will essentially send WM_PAINT to main window.
 80      UINT flags = SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE;
 81      SetWindowPos(mainWindow, nullptr, 0, 0, 0, 0, flags);
 82  
 83      std::thread{ [=]() {
 84          AnimateWindow(mainWindow, ANIMATION_TIME, AW_BLEND);
 85          InvalidateRect(mainWindow, nullptr, true);
 86      } }.detach();
 87  }
 88  
 89  void HideZonesOverlay()
 90  {
 91      highlighted = std::vector<bool>(ZONE_COUNT, false);
 92      ShowWindow(mainWindow, SW_HIDE);
 93  }
 94  
 95  void RefreshMainWindow()
 96  {
 97      while (1)
 98      {
 99          std::this_thread::sleep_for(std::chrono::milliseconds(DISPLAY_REFRESH_TIME));
100  
101          POINT cursorPosition{};
102          if (GetCursorPos(&cursorPosition))
103          {
104              if (showZoneLayout)
105              {
106                  int idx = GetHighlightedZoneIdx(zones, cursorPosition);
107                  if (idx != -1)
108                  {
109                      if (highlighted[idx]) {
110                          // Same zone is active as in previous check, skip invalidating rect.
111                      }
112                      else
113                      {
114                          highlighted = std::vector<bool>(ZONE_COUNT, false);
115                          highlighted[idx] = true;
116  
117                          ShowZonesOverlay();
118                      }
119                  }
120              }
121          }
122      }
123  }
124  
125  LRESULT CALLBACK LowLevelKeyboardProc(int nCode, WPARAM wParam, LPARAM lParam)
126  {
127      if (nCode == HC_ACTION && wParam == WM_KEYDOWN)
128      {
129          PKBDLLHOOKSTRUCT info = reinterpret_cast<KBDLLHOOKSTRUCT*>(lParam);
130          if (info->vkCode == Q_KEY_CODE)
131          {
132              PostQuitMessage(0);
133              return 1;
134          }
135          else if (info->vkCode == W_KEY_CODE)
136          {
137              // Toggle zone layout display.
138              showZoneLayout = !showZoneLayout;
139              if (showZoneLayout)
140              {
141                  ShowZonesOverlay();
142              }
143              else
144              {
145                  HideZonesOverlay();
146              }
147              return 1;
148          }
149      }
150      return CallNextHookEx(nullptr, nCode, wParam, lParam);
151  }
152  
153  void StartLowLevelKeyboardHook()
154  {
155      keyboardHook = SetWindowsHookEx(WH_KEYBOARD_LL, LowLevelKeyboardProc, GetModuleHandle(nullptr), 0);
156  }
157  
158  void StopLowLevelKeyboardHook()
159  {
160      if (keyboardHook)
161      {
162          UnhookWindowsHookEx(keyboardHook);
163          keyboardHook = nullptr;
164      }
165  }
166  
167  
168  inline void MakeWindowTransparent(HWND window)
169  {
170      int const pos = -GetSystemMetrics(SM_CXVIRTUALSCREEN) - 8;
171      if (HRGN hrgn{ CreateRectRgn(pos, 0, (pos + 1), 1) })
172      {
173          DWM_BLURBEHIND bh = { DWM_BB_ENABLE | DWM_BB_BLURREGION, TRUE, hrgn, FALSE };
174          DwmEnableBlurBehindWindow(window, &bh);
175      }
176  }
177  
178  void RegisterClass(HINSTANCE hInstance)
179  {
180    WNDCLASSEXW wcex{};
181  
182    wcex.cbSize        = sizeof(WNDCLASSEX);
183    wcex.lpfnWndProc   = WndProc;
184    wcex.hInstance     = hInstance;
185    wcex.lpszClassName = L"DrawRectangle_Test";
186    wcex.hCursor       = LoadCursor(nullptr, IDC_ARROW);
187  
188    RegisterClassExW(&wcex);
189  }
190  
191  bool InitInstance(HINSTANCE hInstance, int nCmdShow)
192  {
193    MONITORINFO mi{};
194    mi.cbSize = sizeof(mi);
195    if (!GetMonitorInfo(MonitorFromWindow(nullptr, MONITOR_DEFAULTTOPRIMARY), &mi)) {
196      return false;
197    }
198  
199    mainWindow = CreateWindowExW(WS_EX_TOOLWINDOW,
200      L"DrawRectangle_Test",
201      L"",
202      WS_POPUP,
203      mi.rcWork.left,
204      mi.rcWork.top,
205      RectWidth(mi.rcWork),
206      RectHeight(mi.rcWork),
207      nullptr,
208      nullptr,
209      hInstance,
210      nullptr);
211  
212    if (mainWindow)
213    {
214      MakeWindowTransparent(mainWindow);
215      return true;
216    }
217  
218    return false;
219  }
220  
221  int APIENTRY wWinMain(_In_ HINSTANCE hInstance,
222      _In_opt_ HINSTANCE hPrevInstance,
223      _In_ LPWSTR        lpCmdLine,
224      _In_ int             nCmdShow)
225  {
226      UNREFERENCED_PARAMETER(hPrevInstance);
227      UNREFERENCED_PARAMETER(lpCmdLine);
228  
229      GdiplusStartupInput gdiplusStartupInput;
230      ULONG_PTR                     gdiplusToken;
231      GdiplusStartup(&gdiplusToken, &gdiplusStartupInput, nullptr);
232  
233      SetProcessDpiAwareness(PROCESS_DPI_UNAWARE);
234      StartLowLevelKeyboardHook();
235  
236      RegisterClass(hInstance);
237  
238      if (!InitInstance(hInstance, nCmdShow))
239      {
240          return 0;
241      }
242  
243      RECT clientRect{};
244      GetClientRect(mainWindow, &clientRect);
245      zones = BuildColumnZoneLayout(ZONE_COUNT, clientRect);
246      highlighted = std::vector<bool>(ZONE_COUNT, false);
247  
248      // Invoke main window re-drawing from separate thread (based on changes in cursor position).
249      std::thread refreshThread = std::thread(RefreshMainWindow);
250      refreshThread.detach();
251  
252  
253      HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_FANCYZONESDRAWLAYOUTTEST));
254      MSG msg{};
255      while (GetMessage(&msg, nullptr, 0, 0))
256      {
257          if (!TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
258          {
259              TranslateMessage(&msg);
260              DispatchMessage(&msg);
261          }
262      }
263  
264      StopLowLevelKeyboardHook();
265      GdiplusShutdown(gdiplusToken);
266  
267      return (int)msg.wParam;
268  }
269  
270  struct ColorSetting
271  {
272      BYTE fillAlpha{};
273      COLORREF fill{};
274      BYTE borderAlpha{};
275      COLORREF border{};
276      int thickness{};
277  };
278  
279  inline void InitRGB(_Out_ RGBQUAD* quad, BYTE alpha, COLORREF color)
280  {
281      ZeroMemory(quad, sizeof(*quad));
282      quad->rgbReserved = alpha;
283      quad->rgbRed      = GetRValue(color) * alpha / 255;
284      quad->rgbGreen    = GetGValue(color) * alpha / 255;
285      quad->rgbBlue     = GetBValue(color) * alpha / 255;
286  }
287  
288  inline void FillRectARGB(HDC hdc, const RECT& prcFill, BYTE alpha, COLORREF color, bool blendAlpha)
289  {
290      BITMAPINFO bi;
291      ZeroMemory(&bi, sizeof(bi));
292      bi.bmiHeader.biSize        = sizeof(BITMAPINFOHEADER);
293      bi.bmiHeader.biWidth       = 1;
294      bi.bmiHeader.biHeight      = 1;
295      bi.bmiHeader.biPlanes      = 1;
296      bi.bmiHeader.biBitCount    = 32;
297      bi.bmiHeader.biCompression = BI_RGB;
298  
299      RECT fillRect;
300      CopyRect(&fillRect, &prcFill);
301  
302      RGBQUAD bitmapBits;
303      InitRGB(&bitmapBits, alpha, color);
304      StretchDIBits(
305          hdc,
306          fillRect.left,
307          fillRect.top,
308          fillRect.right - fillRect.left,
309          fillRect.bottom - fillRect.top,
310          0,
311          0,
312          1,
313          1,
314          &bitmapBits,
315          &bi,
316          DIB_RGB_COLORS,
317          SRCCOPY);
318  }
319  
320  void DrawBackdrop(HDC& hdc, const RECT& clientRect)
321  {
322      FillRectARGB(hdc, clientRect, 0, RGB(0, 0, 0), false);
323  }
324  
325  void DrawIndex(HDC hdc, const RECT& rect, size_t index)
326  {
327      Gdiplus::Graphics g(hdc);
328  
329      Gdiplus::FontFamily fontFamily(L"Segoe ui");
330      Gdiplus::Font font(&fontFamily, 80, Gdiplus::FontStyleRegular, Gdiplus::UnitPixel);
331      Gdiplus::SolidBrush solidBrush(Gdiplus::Color(255, 0, 0, 0));
332  
333      std::wstring text = std::to_wstring(index);
334  
335      g.SetTextRenderingHint(Gdiplus::TextRenderingHintAntiAlias);
336      Gdiplus::StringFormat stringFormat = new Gdiplus::StringFormat();
337      stringFormat.SetAlignment(Gdiplus::StringAlignmentCenter);
338      stringFormat.SetLineAlignment(Gdiplus::StringAlignmentCenter);
339  
340      Gdiplus::RectF gdiRect(
341          static_cast<Gdiplus::REAL>(rect.left),
342          static_cast<Gdiplus::REAL>(rect.top),
343          static_cast<Gdiplus::REAL>(RectWidth(rect)),
344          static_cast<Gdiplus::REAL>(RectHeight(rect)));
345  
346      g.DrawString(text.c_str(), -1, &font, gdiRect, &stringFormat, &solidBrush);
347  }
348  
349  void DrawZone(HDC hdc, const ColorSetting& colorSetting, const RECT& rect, size_t index)
350  {
351      Gdiplus::Graphics g(hdc);
352      Gdiplus::Color fillColor(colorSetting.fillAlpha, GetRValue(colorSetting.fill), GetGValue(colorSetting.fill), GetBValue(colorSetting.fill));
353      Gdiplus::Color borderColor(colorSetting.borderAlpha, GetRValue(colorSetting.border), GetGValue(colorSetting.border), GetBValue(colorSetting.border));
354  
355      Gdiplus::Rect rectangle(rect.left, rect.top, RectWidth(rect), RectHeight(rect));
356  
357      Gdiplus::Pen pen(borderColor, static_cast<Gdiplus::REAL>(colorSetting.thickness));
358      g.FillRectangle(new Gdiplus::SolidBrush(fillColor), rectangle);
359      g.DrawRectangle(&pen, rectangle);
360  
361      DrawIndex(hdc, rect, index);
362  }
363  
364  constexpr inline BYTE OpacitySettingToAlpha(int opacity)
365  {
366      return static_cast<BYTE>(opacity * 2.55);
367  }
368  
369  COLORREF ParseColor(const std::wstring& zoneColor)
370  {
371      // Skip the leading # and convert to long
372      const auto color = zoneColor;
373      const auto tmp   = std::stol(color.substr(1), nullptr, 16);
374      const auto nR    = (tmp & 0xFF0000) >> 16;
375      const auto nG    = (tmp & 0xFF00) >> 8;
376      const auto nB    = (tmp & 0xFF);
377      return RGB(nR, nG, nB);
378  }
379  
380  static int highlightedIdx = -1;
381  
382  void OnPaint(HDC hdc)
383  {
384      int zoneOpacity = 50;
385      std::wstring zoneColor = L"#0078D7";
386      std::wstring zoneBorderColor = L"#FFFFFF";
387      std::wstring zoneHighlightColor = L"#F5FCFF";
388  
389      ColorSetting color{ OpacitySettingToAlpha(zoneOpacity),
390                                              ParseColor(zoneColor),
391                                              255,
392                                              ParseColor(zoneBorderColor),
393                                              -2 };
394  
395      ColorSetting highlight{ OpacitySettingToAlpha(zoneOpacity),
396                                                      ParseColor(zoneHighlightColor),
397                                                      255,
398                                                      ParseColor(zoneBorderColor),
399                                                      -2 };
400  
401      HMONITOR monitor = MonitorFromWindow(nullptr, MONITOR_DEFAULTTOPRIMARY);
402      MONITORINFOEX mi;
403      mi.cbSize = sizeof(mi);
404      GetMonitorInfo(monitor, &mi);
405  
406      HDC hdcMem{ nullptr };
407      HPAINTBUFFER bufferedPaint = BeginBufferedPaint(hdc, &mi.rcWork, BPBF_TOPDOWNDIB, nullptr, &hdcMem);
408      if (bufferedPaint)
409      {
410          DrawBackdrop(hdcMem, mi.rcWork);
411          for (size_t i = 0; i < zones.size(); ++i)
412          {
413              if (highlighted[i])
414              {
415                  DrawZone(hdcMem, color, zones[i], i);
416              }
417              else
418              {
419                  DrawZone(hdcMem, highlight, zones[i], i);
420              }
421          }
422          EndBufferedPaint(bufferedPaint, TRUE);
423      }
424  }
425  
426  LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
427  {
428      switch (message)
429      {
430      case WM_NCDESTROY:
431      {
432          DefWindowProc(mainWindow, message, wParam, lParam);
433          SetWindowLongPtr(mainWindow, GWLP_USERDATA, 0);
434      }
435      break;
436  
437      case WM_ERASEBKGND:
438          return 1;
439  
440      case WM_PRINTCLIENT:
441      case WM_PAINT:
442      {
443          PAINTSTRUCT ps;
444          HDC hdc = BeginPaint(hWnd, &ps);
445          OnPaint(hdc);
446          EndPaint(hWnd, &ps);
447      }
448      break;
449      case WM_DESTROY:
450          PostQuitMessage(0);
451          break;
452      default:
453          return DefWindowProc(hWnd, message, wParam, lParam);
454      }
455      return 0;
456  }