/ src / modules / ShortcutGuide / ShortcutGuide / overlay_window.cpp
overlay_window.cpp
  1  #include "pch.h"
  2  #include "overlay_window.h"
  3  #include <common/display/monitors.h>
  4  #include "tasklist_positions.h"
  5  #include "start_visible.h"
  6  #include <common/utils/resources.h>
  7  #include <common/utils/window.h>
  8  #include <common/utils/MsWindowsSettings.h>
  9  
 10  #include "shortcut_guide.h"
 11  #include "trace.h"
 12  #include "Generated Files/resource.h"
 13  
 14  namespace
 15  {
 16      // Gets position of given window.
 17      std::optional<RECT> get_window_pos(HWND hwnd)
 18      {
 19          RECT window;
 20          if (DwmGetWindowAttribute(hwnd, DWMWA_EXTENDED_FRAME_BOUNDS, &window, sizeof(window)) == S_OK)
 21          {
 22              return window;
 23          }
 24          else
 25          {
 26              return {};
 27          }
 28      }
 29  
 30      enum WindowState
 31      {
 32          UNKNOWN,
 33          MINIMIZED,
 34          MAXIMIZED,
 35          SNAPPED_TOP_LEFT,
 36          SNAPPED_LEFT,
 37          SNAPPED_BOTTOM_LEFT,
 38          SNAPPED_TOP_RIGHT,
 39          SNAPPED_RIGHT,
 40          SNAPPED_BOTTOM_RIGHT,
 41          RESTORED
 42      };
 43  
 44      inline WindowState get_window_state(HWND hwnd)
 45      {
 46          WINDOWPLACEMENT placement;
 47          placement.length = sizeof(WINDOWPLACEMENT);
 48  
 49          if (GetWindowPlacement(hwnd, &placement) == 0)
 50          {
 51              return UNKNOWN;
 52          }
 53  
 54          if (placement.showCmd == SW_MINIMIZE || placement.showCmd == SW_SHOWMINIMIZED || IsIconic(hwnd))
 55          {
 56              return MINIMIZED;
 57          }
 58  
 59          if (placement.showCmd == SW_MAXIMIZE || placement.showCmd == SW_SHOWMAXIMIZED)
 60          {
 61              return MAXIMIZED;
 62          }
 63  
 64          auto rectp = get_window_pos(hwnd);
 65          if (!rectp)
 66          {
 67              return UNKNOWN;
 68          }
 69  
 70          auto rect = *rectp;
 71          MONITORINFO monitor;
 72          monitor.cbSize = sizeof(MONITORINFO);
 73          auto h_monitor = MonitorFromWindow(hwnd, MONITOR_DEFAULTTONEAREST);
 74          GetMonitorInfo(h_monitor, &monitor);
 75          bool top_left = monitor.rcWork.top == rect.top && monitor.rcWork.left == rect.left;
 76          bool bottom_left = monitor.rcWork.bottom == rect.bottom && monitor.rcWork.left == rect.left;
 77          bool top_right = monitor.rcWork.top == rect.top && monitor.rcWork.right == rect.right;
 78          bool bottom_right = monitor.rcWork.bottom == rect.bottom && monitor.rcWork.right == rect.right;
 79  
 80          if (top_left && bottom_left)
 81              return SNAPPED_LEFT;
 82          if (top_left)
 83              return SNAPPED_TOP_LEFT;
 84          if (bottom_left)
 85              return SNAPPED_BOTTOM_LEFT;
 86          if (top_right && bottom_right)
 87              return SNAPPED_RIGHT;
 88          if (top_right)
 89              return SNAPPED_TOP_RIGHT;
 90          if (bottom_right)
 91              return SNAPPED_BOTTOM_RIGHT;
 92  
 93          return RESTORED;
 94      }
 95  
 96  }
 97  
 98  D2DOverlaySVG& D2DOverlaySVG::load(const std::wstring& filename, ID2D1DeviceContext5* d2d_dc)
 99  {
100      D2DSVG::load(filename, d2d_dc);
101      window_group = nullptr;
102      thumbnail_top_left = {};
103      thumbnail_bottom_right = {};
104      thumbnail_scaled_rect = {};
105      return *this;
106  }
107  
108  D2DOverlaySVG& D2DOverlaySVG::resize(int x, int y, int width, int height, float fill, float max_scale)
109  {
110      D2DSVG::resize(x, y, width, height, fill, max_scale);
111      if (thumbnail_bottom_right.x != 0 && thumbnail_bottom_right.y != 0)
112      {
113          auto scaled_top_left = transform.TransformPoint(thumbnail_top_left);
114          auto scanled_bottom_right = transform.TransformPoint(thumbnail_bottom_right);
115          thumbnail_scaled_rect.left = static_cast<int>(scaled_top_left.x);
116          thumbnail_scaled_rect.top = static_cast<int>(scaled_top_left.y);
117          thumbnail_scaled_rect.right = static_cast<int>(scanled_bottom_right.x);
118          thumbnail_scaled_rect.bottom = static_cast<int>(scanled_bottom_right.y);
119      }
120      return *this;
121  }
122  
123  D2DOverlaySVG& D2DOverlaySVG::find_thumbnail(const std::wstring& id)
124  {
125      winrt::com_ptr<ID2D1SvgElement> thumbnail_box;
126      winrt::check_hresult(svg->FindElementById(id.c_str(), thumbnail_box.put()));
127      winrt::check_hresult(thumbnail_box->GetAttributeValue(L"x", &thumbnail_top_left.x));
128      winrt::check_hresult(thumbnail_box->GetAttributeValue(L"y", &thumbnail_top_left.y));
129      winrt::check_hresult(thumbnail_box->GetAttributeValue(L"width", &thumbnail_bottom_right.x));
130      thumbnail_bottom_right.x += thumbnail_top_left.x;
131      winrt::check_hresult(thumbnail_box->GetAttributeValue(L"height", &thumbnail_bottom_right.y));
132      thumbnail_bottom_right.y += thumbnail_top_left.y;
133      return *this;
134  }
135  
136  D2DOverlaySVG& D2DOverlaySVG::find_window_group(const std::wstring& id)
137  {
138      window_group = nullptr;
139      winrt::check_hresult(svg->FindElementById(id.c_str(), window_group.put()));
140      return *this;
141  }
142  
143  ScaleResult D2DOverlaySVG::get_thumbnail_rect_and_scale(int x_offset, int y_offset, int window_cx, int window_cy, float fill)
144  {
145      if (thumbnail_bottom_right.x == 0 && thumbnail_bottom_right.y == 0)
146      {
147          return {};
148      }
149      int thumbnail_scaled_rect_width = thumbnail_scaled_rect.right - thumbnail_scaled_rect.left;
150      int thumbnail_scaled_rect_heigh = thumbnail_scaled_rect.bottom - thumbnail_scaled_rect.top;
151      if (thumbnail_scaled_rect_heigh == 0 || thumbnail_scaled_rect_width == 0 ||
152          window_cx == 0 || window_cy == 0)
153      {
154          return {};
155      }
156      float scale_h = fill * thumbnail_scaled_rect_width / window_cx;
157      float scale_v = fill * thumbnail_scaled_rect_heigh / window_cy;
158      float use_scale = std::min(scale_h, scale_v);
159      RECT thumb_rect;
160      thumb_rect.left = thumbnail_scaled_rect.left + static_cast<int>(thumbnail_scaled_rect_width - use_scale * window_cx) / 2 + x_offset;
161      thumb_rect.right = thumbnail_scaled_rect.right - static_cast<int>(thumbnail_scaled_rect_width - use_scale * window_cx) / 2 + x_offset;
162      thumb_rect.top = thumbnail_scaled_rect.top + static_cast<int>(thumbnail_scaled_rect_heigh - use_scale * window_cy) / 2 + y_offset;
163      thumb_rect.bottom = thumbnail_scaled_rect.bottom - static_cast<int>(thumbnail_scaled_rect_heigh - use_scale * window_cy) / 2 + y_offset;
164      ScaleResult result;
165      result.scale = use_scale;
166      result.rect = thumb_rect;
167      return result;
168  }
169  
170  winrt::com_ptr<ID2D1SvgElement> D2DOverlaySVG::find_element(const std::wstring& id)
171  {
172      winrt::com_ptr<ID2D1SvgElement> element;
173      winrt::check_hresult(svg->FindElementById(id.c_str(), element.put()));
174      return element;
175  }
176  
177  D2DOverlaySVG& D2DOverlaySVG::toggle_window_group(bool active)
178  {
179      if (window_group)
180      {
181          window_group->SetAttributeValue(L"fill-opacity", active ? 1.0f : 0.3f);
182      }
183      return *this;
184  }
185  
186  D2D1_RECT_F D2DOverlaySVG::get_maximize_label() const
187  {
188      D2D1_RECT_F result;
189      auto height = thumbnail_scaled_rect.bottom - thumbnail_scaled_rect.top;
190      auto width = thumbnail_scaled_rect.right - thumbnail_scaled_rect.left;
191      if (width >= height)
192      {
193          result.top = thumbnail_scaled_rect.bottom + height * 0.210f;
194          result.bottom = thumbnail_scaled_rect.bottom + height * 0.310f;
195          result.left = thumbnail_scaled_rect.left + width * 0.009f;
196          result.right = thumbnail_scaled_rect.right + width * 0.009f;
197      }
198      else
199      {
200          result.top = thumbnail_scaled_rect.top + height * 0.323f;
201          result.bottom = thumbnail_scaled_rect.top + height * 0.398f;
202          result.left = static_cast<float>(thumbnail_scaled_rect.right);
203          result.right = thumbnail_scaled_rect.right + width * 1.45f;
204      }
205      return result;
206  }
207  D2D1_RECT_F D2DOverlaySVG::get_minimize_label() const
208  {
209      D2D1_RECT_F result;
210      auto height = thumbnail_scaled_rect.bottom - thumbnail_scaled_rect.top;
211      auto width = thumbnail_scaled_rect.right - thumbnail_scaled_rect.left;
212      if (width >= height)
213      {
214          result.top = thumbnail_scaled_rect.bottom + height * 0.8f;
215          result.bottom = thumbnail_scaled_rect.bottom + height * 0.9f;
216          result.left = thumbnail_scaled_rect.left + width * 0.009f;
217          result.right = thumbnail_scaled_rect.right + width * 0.009f;
218      }
219      else
220      {
221          result.top = thumbnail_scaled_rect.top + height * 0.725f;
222          result.bottom = thumbnail_scaled_rect.top + height * 0.800f;
223          result.left =static_cast<float>(thumbnail_scaled_rect.right);
224          result.right = thumbnail_scaled_rect.right + width * 1.45f;
225      }
226      return result;
227  }
228  D2D1_RECT_F D2DOverlaySVG::get_snap_left() const
229  {
230      D2D1_RECT_F result;
231      auto height = thumbnail_scaled_rect.bottom - thumbnail_scaled_rect.top;
232      auto width = thumbnail_scaled_rect.right - thumbnail_scaled_rect.left;
233      if (width >= height)
234      {
235          result.top = thumbnail_scaled_rect.bottom + height * 0.5f;
236          result.bottom = thumbnail_scaled_rect.bottom + height * 0.6f;
237          result.left = thumbnail_scaled_rect.left + width * 0.009f;
238          result.right = thumbnail_scaled_rect.left + width * 0.339f;
239      }
240      else
241      {
242          result.top = thumbnail_scaled_rect.top + height * 0.523f;
243          result.bottom = thumbnail_scaled_rect.top + height * 0.598f;
244          result.left = static_cast<float>(thumbnail_scaled_rect.right);
245          result.right = thumbnail_scaled_rect.right + width * 0.450f;
246      }
247      return result;
248  }
249  D2D1_RECT_F D2DOverlaySVG::get_snap_right() const
250  {
251      D2D1_RECT_F result;
252      auto height = thumbnail_scaled_rect.bottom - thumbnail_scaled_rect.top;
253      auto width = thumbnail_scaled_rect.right - thumbnail_scaled_rect.left;
254      if (width >= height)
255      {
256          result.top = thumbnail_scaled_rect.bottom + height * 0.5f;
257          result.bottom = thumbnail_scaled_rect.bottom + height * 0.6f;
258          result.left = thumbnail_scaled_rect.left + width * 0.679f;
259          result.right = thumbnail_scaled_rect.right + width * 1.009f;
260      }
261      else
262      {
263          result.top = thumbnail_scaled_rect.top + height * 0.523f;
264          result.bottom = thumbnail_scaled_rect.top + height * 0.598f;
265          result.left = static_cast<float>(thumbnail_scaled_rect.right + width);
266          result.right = thumbnail_scaled_rect.right + width * 1.45f;
267      }
268      return result;
269  }
270  
271  D2DOverlayWindow::D2DOverlayWindow() :
272      total_screen({}),
273      D2DWindow()
274  {
275      BOOL isEnabledAnimations = GetAnimationsEnabled();
276      background_animation = isEnabledAnimations? 0.3f : 0.f;
277      global_windows_shortcuts_animation = isEnabledAnimations ? 0.3f : 0.f;
278      taskbar_icon_shortcuts_animation = isEnabledAnimations ? 0.3f : 0.f;
279      tasklist_thread = std::thread([&] {
280          while (running)
281          {
282              // Removing <std::mutex> causes C3538 on std::unique_lock lock(mutex); in show(..)
283              std::unique_lock<std::mutex> task_list_lock(tasklist_cv_mutex);
284              tasklist_cv.wait(task_list_lock, [&] { return !running || tasklist_update; });
285              if (!running)
286                  return;
287              task_list_lock.unlock();
288              while (running && tasklist_update)
289              {
290                  std::vector<TasklistButton> buttons;
291                  if (tasklist.update_buttons(buttons))
292                  {
293                      std::unique_lock lock(mutex);
294                      tasklist_buttons.swap(buttons);
295                  }
296                  std::this_thread::sleep_for(std::chrono::milliseconds(500));
297              }
298          }
299      });
300  }
301  
302  void D2DOverlayWindow::show(HWND window, bool snappable)
303  {
304      std::unique_lock lock(mutex);
305      hidden = false;
306      tasklist_buttons.clear();
307      active_window = window;
308      active_window_snappable = snappable;
309      auto old_bck = colors.start_color_menu;
310      auto colors_updated = colors.update();
311      auto new_light_mode = (theme_setting == Light) || (theme_setting == System && colors.light_mode);
312      if (initialized && (colors_updated || light_mode != new_light_mode))
313      {
314          // update background colors
315          landscape.recolor(old_bck, colors.start_color_menu);
316          portrait.recolor(old_bck, colors.start_color_menu);
317          for (auto& arrow : arrows)
318          {
319              arrow.recolor(old_bck, colors.start_color_menu);
320          }
321          light_mode = new_light_mode;
322          if (light_mode)
323          {
324              landscape.recolor(0xDDDDDD, 0x222222);
325              portrait.recolor(0xDDDDDD, 0x222222);
326              for (auto& arrow : arrows)
327              {
328                  arrow.recolor(0xDDDDDD, 0x222222);
329              }
330          }
331          else
332          {
333              landscape.recolor(0x222222, 0xDDDDDD);
334              portrait.recolor(0x222222, 0xDDDDDD);
335              for (auto& arrow : arrows)
336              {
337                  arrow.recolor(0x222222, 0xDDDDDD);
338              }
339          }
340      }
341      monitors = MonitorInfo::GetMonitors(true);
342      // calculate the rect covering all the screens
343      total_screen = monitors[0].GetScreenSize(true);
344      for (auto& monitor : monitors)
345      {
346          const auto monitorSize = monitor.GetScreenSize(true);
347          total_screen.rect.left = std::min(total_screen.left(), monitorSize.left());
348          total_screen.rect.top = std::min(total_screen.top(), monitorSize.top());
349          total_screen.rect.right = std::max(total_screen.right(), monitorSize.right());
350          total_screen.rect.bottom = std::max(total_screen.bottom(), monitorSize.bottom());
351      }
352      // make sure top-right corner of all the monitor rects is (0,0)
353      monitor_dx = -total_screen.left();
354      monitor_dy = -total_screen.top();
355      total_screen.rect.left += monitor_dx;
356      total_screen.rect.right += monitor_dx;
357      total_screen.rect.top += monitor_dy;
358      total_screen.rect.bottom += monitor_dy;
359      tasklist.update();
360      if (window)
361      {
362          // Ignore errors, if this fails we will just not show the thumbnail
363          DwmRegisterThumbnail(hwnd, window, &thumbnail);
364      }
365  
366      background_animation.reset();
367  
368      if (milliseconds_press_time_for_global_windows_shortcuts < milliseconds_press_time_for_taskbar_icon_shortcuts)
369      {
370          global_windows_shortcuts_shown = true;
371          taskbar_icon_shortcuts_shown = false;
372          global_windows_shortcuts_animation.reset();
373      }
374      else if (milliseconds_press_time_for_global_windows_shortcuts > milliseconds_press_time_for_taskbar_icon_shortcuts)
375      {
376          global_windows_shortcuts_shown = false;
377          taskbar_icon_shortcuts_shown = true;
378          taskbar_icon_shortcuts_animation.reset();
379      }
380      else
381      {
382          global_windows_shortcuts_shown = true;
383          taskbar_icon_shortcuts_shown = true;
384          global_windows_shortcuts_animation.reset();
385          taskbar_icon_shortcuts_animation.reset();
386      }
387  
388      auto primary_size = MonitorInfo::GetPrimaryMonitor().GetScreenSize(false);
389      shown_start_time = std::chrono::steady_clock::now();
390      lock.unlock();
391      D2DWindow::show(primary_size.left(), primary_size.top(), primary_size.width(), primary_size.height());
392      // Check if taskbar is auto-hidden. If so, don't display the number arrows
393      APPBARDATA param = {};
394      param.cbSize = sizeof(APPBARDATA);
395      if (static_cast<UINT>(SHAppBarMessage(ABM_GETSTATE, &param)) != ABS_AUTOHIDE)
396      {
397          tasklist_cv_mutex.lock();
398          tasklist_update = true;
399          tasklist_cv_mutex.unlock();
400          tasklist_cv.notify_one();
401      }
402  }
403  
404  void D2DOverlayWindow::on_show()
405  {
406      // show override does everything
407  }
408  
409  void D2DOverlayWindow::on_hide()
410  {
411      Logger::trace("D2DOverlayWindow::on_hide()");
412      tasklist_cv_mutex.lock();
413      tasklist_update = false;
414      tasklist_cv_mutex.unlock();
415      tasklist_cv.notify_one();
416      if (thumbnail)
417      {
418          DwmUnregisterThumbnail(thumbnail);
419      }
420      std::chrono::steady_clock::time_point shown_end_time = std::chrono::steady_clock::now();
421      // Trace the event only if the overlay window was visible.
422      if (shown_start_time.time_since_epoch().count() > 0)
423      {
424          auto duration = std::chrono::duration_cast<std::chrono::milliseconds>(shown_end_time - shown_start_time).count();
425          Logger::trace(L"Duration: {}. Close Type: {}", duration, windowCloseType);
426          Trace::SendGuideSession(duration, windowCloseType.c_str());
427          shown_start_time = {};
428      }
429  }
430  
431  D2DOverlayWindow::~D2DOverlayWindow()
432  {
433      tasklist_cv_mutex.lock();
434      running = false;
435      tasklist_cv_mutex.unlock();
436      tasklist_cv.notify_one();
437      tasklist_thread.join();
438  }
439  
440  void D2DOverlayWindow::apply_overlay_opacity(float opacity)
441  {
442      if (opacity <= 0.0f)
443      {
444          opacity = 0.0f;
445      }
446      if (opacity >= 1.0f)
447      {
448          opacity = 1.0f;
449      }
450      overlay_opacity = opacity;
451  }
452  
453  void D2DOverlayWindow::apply_press_time_for_global_windows_shortcuts(int press_time)
454  {
455      milliseconds_press_time_for_global_windows_shortcuts = std::max(press_time, 0);
456  }
457  
458  void D2DOverlayWindow::apply_press_time_for_taskbar_icon_shortcuts(int press_time)
459  {
460      milliseconds_press_time_for_taskbar_icon_shortcuts = std::max(press_time, 0);
461  }
462  
463  void D2DOverlayWindow::set_theme(const std::wstring& theme)
464  {
465      if (theme == L"light")
466      {
467          theme_setting = Light;
468      }
469      else if (theme == L"dark")
470      {
471          theme_setting = Dark;
472      }
473      else
474      {
475          theme_setting = System;
476      }
477  }
478  
479  /* Hide the window but do not call on_hide(). Use this to quickly hide the window when needed.
480     Note, that a proper hide should be made after this before showing the window again.
481  */
482  void D2DOverlayWindow::quick_hide()
483  {
484      ShowWindow(hwnd, SW_HIDE);
485      if (thumbnail)
486      {
487          DwmUnregisterThumbnail(thumbnail);
488      }
489  }
490  
491  HWND D2DOverlayWindow::get_window_handle()
492  {
493      return hwnd;
494  }
495  
496  float D2DOverlayWindow::get_overlay_opacity()
497  {
498      return overlay_opacity;
499  }
500  
501  void D2DOverlayWindow::init()
502  {
503      colors.update();
504      landscape.load(L"Assets\\ShortcutGuide\\overlay.svg", d2d_dc.get())
505          .find_thumbnail(L"monitorRect")
506          .find_window_group(L"WindowControlsGroup")
507          .recolor(0x2582FB, colors.start_color_menu);
508      portrait.load(L"Assets\\ShortcutGuide\\overlay_portrait.svg", d2d_dc.get())
509          .find_thumbnail(L"monitorRect")
510          .find_window_group(L"WindowControlsGroup")
511          .recolor(0x2582FB, colors.start_color_menu);
512      no_active.load(L"Assets\\ShortcutGuide\\no_active_window.svg", d2d_dc.get());
513      arrows.resize(10);
514      for (unsigned i = 0; i < arrows.size(); ++i)
515      {
516          arrows[i].load(L"Assets\\ShortcutGuide\\" + std::to_wstring((i + 1) % 10) + L".svg", d2d_dc.get()).recolor(0x2582FB, colors.start_color_menu);
517      }
518      light_mode = (theme_setting == Light) || (theme_setting == System && colors.light_mode);
519      if (light_mode)
520      {
521          landscape.recolor(0x2E17FC, 0x000000);
522          portrait.recolor(0x2E17FC, 0x000000);
523          for (auto& arrow : arrows)
524          {
525              arrow.recolor(0x222222, 0x000000);
526          }
527      }
528      else
529      {
530          landscape.recolor(0x2E17FC, 0xFFFFFF);
531          portrait.recolor(0x2E17FC, 0xFFFFFF);
532          for (auto& arrow : arrows)
533          {
534              arrow.recolor(0x222222, 0xFFFFFF);
535          }
536      }
537  }
538  
539  void D2DOverlayWindow::resize()
540  {
541      window_rect = *get_window_pos(hwnd);
542      float no_active_scale, font;
543      if (window_width >= window_height)
544      { // portrait is broke right now
545          use_overlay = &landscape;
546          no_active_scale = 0.3f;
547          font = 12.0f;
548      }
549      else
550      {
551          use_overlay = &portrait;
552          no_active_scale = 0.5f;
553          font = 13.0f;
554      }
555      use_overlay->resize(0, 0, window_width, window_height, 0.8f);
556      auto thumb_no_active_rect = use_overlay->get_thumbnail_rect_and_scale(0, 0, no_active.width(), no_active.height(), no_active_scale).rect;
557      no_active.resize(thumb_no_active_rect.left,
558                       thumb_no_active_rect.top,
559                       thumb_no_active_rect.right - thumb_no_active_rect.left,
560                       thumb_no_active_rect.bottom - thumb_no_active_rect.top,
561                       1.0f);
562      text.resize(font, use_overlay->get_scale());
563  }
564  
565  void render_arrow(D2DSVG& arrow, TasklistButton& button, RECT window, float max_scale, ID2D1DeviceContext5* d2d_dc, int x_offset, int y_offset)
566  {
567      int dx = 0, dy = 0;
568      // Calculate taskbar orientation
569      arrow.toggle_element(L"left", false);
570      arrow.toggle_element(L"right", false);
571      arrow.toggle_element(L"top", false);
572      arrow.toggle_element(L"bottom", false);
573      if (button.x <= window.left)
574      { // taskbar on left
575          dx = 1;
576          arrow.toggle_element(L"left", true);
577      }
578      if (button.x >= window.right)
579      { // taskbar on right
580          dx = -1;
581          arrow.toggle_element(L"right", true);
582      }
583      if (button.y <= window.top)
584      { // taskbar on top
585          dy = 1;
586          arrow.toggle_element(L"top", true);
587      }
588      if (button.y >= window.bottom)
589      { // taskbar on bottom
590          dy = -1;
591          arrow.toggle_element(L"bottom", true);
592      }
593      double arrow_ratio = static_cast<double>(arrow.height()) / arrow.width();
594      if (dy != 0)
595      {
596          // assume button is 25% wider than taller, +10% to make room for each of the arrows that are hidden
597          auto render_arrow_width = static_cast<int>(button.height * 1.25f * 1.2f);
598          auto render_arrow_height = static_cast<int>(render_arrow_width * arrow_ratio);
599          arrow.resize((button.x + (button.width - render_arrow_width) / 2) + x_offset,
600                       (dy == -1 ? button.y - render_arrow_height : 0) + y_offset,
601                       render_arrow_width,
602                       render_arrow_height,
603                       0.95f,
604                       max_scale)
605              .render(d2d_dc);
606      }
607      else
608      {
609          // same as above - make room for the hidden arrow
610          auto render_arrow_height = static_cast<int>(button.height * 1.2f);
611          auto render_arrow_width = static_cast<int>(render_arrow_height / arrow_ratio);
612          arrow.resize((dx == -1 ? button.x - render_arrow_width : 0) + x_offset,
613                       (button.y + (button.height - render_arrow_height) / 2) + y_offset,
614                       render_arrow_width,
615                       render_arrow_height,
616                       0.95f,
617                       max_scale)
618              .render(d2d_dc);
619      }
620  }
621  
622  bool D2DOverlayWindow::show_thumbnail(const RECT& rect, double alpha)
623  {
624      if (!thumbnail)
625      {
626          return false;
627      }
628      DWM_THUMBNAIL_PROPERTIES thumb_properties;
629      thumb_properties.dwFlags = DWM_TNP_SOURCECLIENTAREAONLY | DWM_TNP_VISIBLE | DWM_TNP_RECTDESTINATION | DWM_TNP_OPACITY;
630      thumb_properties.fSourceClientAreaOnly = FALSE;
631      thumb_properties.fVisible = TRUE;
632      thumb_properties.opacity = static_cast<BYTE>(255 * alpha);
633      thumb_properties.rcDestination = rect;
634      if (DwmUpdateThumbnailProperties(thumbnail, &thumb_properties) != S_OK)
635      {
636          return false;
637      }
638      return true;
639  }
640  
641  void D2DOverlayWindow::hide_thumbnail()
642  {
643      DWM_THUMBNAIL_PROPERTIES thumb_properties;
644      thumb_properties.dwFlags = DWM_TNP_VISIBLE;
645      thumb_properties.fVisible = FALSE;
646      DwmUpdateThumbnailProperties(thumbnail, &thumb_properties);
647  }
648  
649  void D2DOverlayWindow::render(ID2D1DeviceContext5* d2d_device_context)
650  {
651      if (!hidden && !overlay_window_instance->overlay_visible())
652      {
653          hide();
654          return;
655      }
656  
657      d2d_device_context->Clear();
658      int taskbar_icon_shortcuts_x_offset = 0, taskbar_icon_shortcuts_y_offset = 0;
659  
660      double current_background_anim_value = background_animation.value(Animation::AnimFunctions::LINEAR);
661      double current_global_windows_shortcuts_anim_value = global_windows_shortcuts_animation.value(Animation::AnimFunctions::LINEAR);
662      double pos_global_windows_shortcuts_anim_value = 1 - global_windows_shortcuts_animation.value(Animation::AnimFunctions::EASE_OUT_EXPO);
663      double pos_taskbar_icon_shortcuts_anim_value = 1 - taskbar_icon_shortcuts_animation.value(Animation::AnimFunctions::EASE_OUT_EXPO);
664  
665      // Draw background
666      SetLayeredWindowAttributes(hwnd, 0, static_cast<byte>(255 * current_background_anim_value), LWA_ALPHA);
667      winrt::com_ptr<ID2D1SolidColorBrush> brush;
668      float brush_opacity = get_overlay_opacity();
669      D2D1_COLOR_F brushColor = light_mode ? D2D1::ColorF(1.0f, 1.0f, 1.0f, brush_opacity) : D2D1::ColorF(0, 0, 0, brush_opacity);
670      winrt::check_hresult(d2d_device_context->CreateSolidColorBrush(brushColor, brush.put()));
671      D2D1_RECT_F background_rect = {};
672      background_rect.bottom = static_cast<float>(window_height);
673      background_rect.right = static_cast<float>(window_width);
674      d2d_device_context->SetTransform(D2D1::Matrix3x2F::Identity());
675      d2d_device_context->FillRectangle(background_rect, brush.get());
676  
677      // Draw the taskbar shortcuts (the arrows with numbers)
678      if (taskbar_icon_shortcuts_shown)
679      {
680          if (!tasklist_buttons.empty())
681          {
682              if (tasklist_buttons[0].x <= window_rect.left)
683              {
684                  // taskbar on left
685                  taskbar_icon_shortcuts_x_offset = static_cast<int>(-pos_taskbar_icon_shortcuts_anim_value * use_overlay->width() * use_overlay->get_scale());
686              }
687              if (tasklist_buttons[0].x >= window_rect.right)
688              {
689                  // taskbar on right
690                  taskbar_icon_shortcuts_x_offset = static_cast<int>(pos_taskbar_icon_shortcuts_anim_value * use_overlay->width() * use_overlay->get_scale());
691              }
692              if (tasklist_buttons[0].y <= window_rect.top)
693              {
694                  // taskbar on top
695                  taskbar_icon_shortcuts_y_offset = static_cast<int>(-pos_taskbar_icon_shortcuts_anim_value * use_overlay->height() * use_overlay->get_scale());
696              }
697              if (tasklist_buttons[0].y >= window_rect.bottom)
698              {
699                  // taskbar on bottom
700                  taskbar_icon_shortcuts_y_offset = static_cast<int>(pos_taskbar_icon_shortcuts_anim_value * use_overlay->height() * use_overlay->get_scale());
701              }
702              for (auto&& button : tasklist_buttons)
703              {
704                  if (static_cast<size_t>(button.keynum) - 1 >= arrows.size())
705                  {
706                      continue;
707                  }
708                  render_arrow(arrows[static_cast<size_t>(button.keynum) - 1], button, window_rect, use_overlay->get_scale(), d2d_device_context, taskbar_icon_shortcuts_x_offset, taskbar_icon_shortcuts_y_offset);
709              }
710          }
711      }
712      else
713      {
714          auto time_since_start = std::chrono::high_resolution_clock::now() - shown_start_time;
715          if (time_since_start.count() / 1000000 > milliseconds_press_time_for_taskbar_icon_shortcuts - milliseconds_press_time_for_global_windows_shortcuts)
716          {
717              taskbar_icon_shortcuts_shown = true;
718              taskbar_icon_shortcuts_animation.reset();
719          }
720      }
721  
722      if (global_windows_shortcuts_shown)
723      {
724          // Thumbnail logic:
725          auto window_state = get_window_state(active_window);
726          auto thumb_window = get_window_pos(active_window);
727          if (!thumb_window.has_value())
728          {
729              thumb_window = RECT();
730          }
731  
732          bool miniature_shown = active_window != nullptr && thumbnail != nullptr && thumb_window && window_state != MINIMIZED;
733          RECT client_rect;
734          if (thumb_window && GetClientRect(active_window, &client_rect))
735          {
736              int dx = ((thumb_window->right - thumb_window->left) - (client_rect.right - client_rect.left)) / 2;
737              int dy = ((thumb_window->bottom - thumb_window->top) - (client_rect.bottom - client_rect.top)) / 2;
738              thumb_window->left += dx;
739              thumb_window->right -= dx;
740              thumb_window->top += dy;
741              thumb_window->bottom -= dy;
742          }
743          if (miniature_shown && thumb_window->right - thumb_window->left <= 0 || thumb_window->bottom - thumb_window->top <= 0)
744          {
745              miniature_shown = false;
746          }
747          bool render_monitors = true;
748          auto total_monitor_with_screen = total_screen;
749          if (thumb_window)
750          {
751              total_monitor_with_screen.rect.left = std::min(total_monitor_with_screen.rect.left, thumb_window->left + monitor_dx);
752              total_monitor_with_screen.rect.top = std::min(total_monitor_with_screen.rect.top, thumb_window->top + monitor_dy);
753              total_monitor_with_screen.rect.right = std::max(total_monitor_with_screen.rect.right, thumb_window->right + monitor_dx);
754              total_monitor_with_screen.rect.bottom = std::max(total_monitor_with_screen.rect.bottom, thumb_window->bottom + monitor_dy);
755          }
756          // Only allow the new rect being slight bigger.
757          if (total_monitor_with_screen.width() - total_screen.width() > (thumb_window->right - thumb_window->left) / 2 ||
758              total_monitor_with_screen.height() - total_screen.height() > (thumb_window->bottom - thumb_window->top) / 2)
759          {
760              render_monitors = false;
761          }
762          if (window_state == MINIMIZED)
763          {
764              total_monitor_with_screen = total_screen;
765          }
766          auto rect_and_scale = use_overlay->get_thumbnail_rect_and_scale(0, 0, total_monitor_with_screen.width(), total_monitor_with_screen.height(), 1);
767          if (miniature_shown)
768          {
769              RECT thumbnail_pos;
770              if (render_monitors)
771              {
772                  thumbnail_pos.left = static_cast<int>((thumb_window->left + monitor_dx) * rect_and_scale.scale + rect_and_scale.rect.left);
773                  thumbnail_pos.top = static_cast<int>((thumb_window->top + monitor_dy) * rect_and_scale.scale + rect_and_scale.rect.top);
774                  thumbnail_pos.right = static_cast<int>((thumb_window->right + monitor_dx) * rect_and_scale.scale + rect_and_scale.rect.left);
775                  thumbnail_pos.bottom = static_cast<int>((thumb_window->bottom + monitor_dy) * rect_and_scale.scale + rect_and_scale.rect.top);
776              }
777              else
778              {
779                  thumbnail_pos = use_overlay->get_thumbnail_rect_and_scale(0, 0, thumb_window->right - thumb_window->left, thumb_window->bottom - thumb_window->top, 1).rect;
780              }
781              // If the animation is done show the thumbnail
782              //   we cannot animate the thumbnail, the animation lags behind
783              miniature_shown = show_thumbnail(thumbnail_pos, current_global_windows_shortcuts_anim_value);
784          }
785          else
786          {
787              hide_thumbnail();
788          }
789          if (window_state == MINIMIZED)
790          {
791              render_monitors = true;
792          }
793          // render the monitors
794          if (render_monitors)
795          {
796              brushColor = D2D1::ColorF(colors.start_color_menu, miniature_shown ? static_cast<float>(current_global_windows_shortcuts_anim_value * 0.9) : static_cast<float>(current_global_windows_shortcuts_anim_value * 0.3));
797              brush = nullptr;
798              winrt::check_hresult(d2d_device_context->CreateSolidColorBrush(brushColor, brush.put()));
799              for (auto& monitor : monitors)
800              {
801                  D2D1_RECT_F monitor_rect;
802                  const auto monitor_size = monitor.GetScreenSize(true);
803                  monitor_rect.left = static_cast<float>((monitor_size.left() + monitor_dx) * rect_and_scale.scale + rect_and_scale.rect.left);
804                  monitor_rect.top = static_cast<float>((monitor_size.top() + monitor_dy) * rect_and_scale.scale + rect_and_scale.rect.top);
805                  monitor_rect.right = static_cast<float>((monitor_size.right() + monitor_dx) * rect_and_scale.scale + rect_and_scale.rect.left);
806                  monitor_rect.bottom = static_cast<float>((monitor_size.bottom() + monitor_dy) * rect_and_scale.scale + rect_and_scale.rect.top);
807                  d2d_device_context->SetTransform(D2D1::Matrix3x2F::Identity());
808                  d2d_device_context->FillRectangle(monitor_rect, brush.get());
809              }
810          }
811          // Finalize the overlay - dim the buttons if no thumbnail is present and show "No active window"
812          use_overlay->toggle_window_group(miniature_shown || window_state == MINIMIZED);
813          if (!miniature_shown && window_state != MINIMIZED)
814          {
815              no_active.render(d2d_device_context);
816              window_state = UNKNOWN;
817          }
818  
819          // Set the animation - move the draw window according to animation step
820          int global_windows_shortcuts_y_offset = static_cast<int>(pos_global_windows_shortcuts_anim_value * use_overlay->height() * use_overlay->get_scale());
821          auto popIn = D2D1::Matrix3x2F::Translation(0, static_cast<float>(global_windows_shortcuts_y_offset));
822          d2d_device_context->SetTransform(popIn);
823  
824          // Animate keys
825          for (unsigned id = 0; id < key_animations.size();)
826          {
827              auto& animation = key_animations[id];
828              D2D1_COLOR_F color;
829              auto value = static_cast<float>(animation.animation.value(Animation::AnimFunctions::EASE_OUT_EXPO));
830              color.a = 1.0f;
831              color.r = animation.original.r + (1.0f - animation.original.r) * value;
832              color.g = animation.original.g + (1.0f - animation.original.g) * value;
833              color.b = animation.original.b + (1.0f - animation.original.b) * value;
834              animation.button->SetAttributeValue(L"fill", color);
835              if (animation.animation.done())
836              {
837                  if (value == 1)
838                  {
839                      animation.animation.reset(0.05, 1, 0);
840                      animation.animation.value(Animation::AnimFunctions::EASE_OUT_EXPO);
841                  }
842                  else
843                  {
844                      key_animations.erase(key_animations.begin() + id);
845                      continue;
846                  }
847              }
848              ++id;
849          }
850          // Finally: render the overlay...
851          use_overlay->render(d2d_device_context);
852          // ... window arrows texts ...
853          std::wstring left, right, up, down;
854          bool left_disabled = false;
855          bool right_disabled = false;
856          bool up_disabled = false;
857          bool down_disabled = false;
858          switch (window_state)
859          {
860          case MINIMIZED:
861              left = GET_RESOURCE_STRING(IDS_NO_ACTION);
862              left_disabled = true;
863              right = GET_RESOURCE_STRING(IDS_NO_ACTION);
864              right_disabled = true;
865              up = GET_RESOURCE_STRING(IDS_RESTORE);
866              down = GET_RESOURCE_STRING(IDS_NO_ACTION);
867              down_disabled = true;
868              break;
869          case MAXIMIZED:
870              left = GET_RESOURCE_STRING(IDS_SNAP_LEFT);
871              right = GET_RESOURCE_STRING(IDS_SNAP_RIGHT);
872              up = GET_RESOURCE_STRING(IDS_NO_ACTION);
873              up_disabled = true;
874              down = GET_RESOURCE_STRING(IDS_RESTORE);
875              break;
876          case SNAPPED_TOP_LEFT:
877              left = GET_RESOURCE_STRING(IDS_SNAP_UPPER_RIGHT);
878              right = GET_RESOURCE_STRING(IDS_SNAP_UPPER_RIGHT);
879              up = GET_RESOURCE_STRING(IDS_MAXIMIZE);
880              down = GET_RESOURCE_STRING(IDS_SNAP_LEFT);
881              break;
882          case SNAPPED_LEFT:
883              left = GET_RESOURCE_STRING(IDS_SNAP_RIGHT);
884              right = GET_RESOURCE_STRING(IDS_RESTORE);
885              up = GET_RESOURCE_STRING(IDS_SNAP_UPPER_LEFT);
886              down = GET_RESOURCE_STRING(IDS_SNAP_LOWER_LEFT);
887              break;
888          case SNAPPED_BOTTOM_LEFT:
889              left = GET_RESOURCE_STRING(IDS_SNAP_LOWER_RIGHT);
890              right = GET_RESOURCE_STRING(IDS_SNAP_LOWER_RIGHT);
891              up = GET_RESOURCE_STRING(IDS_SNAP_LEFT);
892              down = GET_RESOURCE_STRING(IDS_MINIMIZE);
893              break;
894          case SNAPPED_TOP_RIGHT:
895              left = GET_RESOURCE_STRING(IDS_SNAP_UPPER_LEFT);
896              right = GET_RESOURCE_STRING(IDS_SNAP_UPPER_LEFT);
897              up = GET_RESOURCE_STRING(IDS_MAXIMIZE);
898              down = GET_RESOURCE_STRING(IDS_SNAP_RIGHT);
899              break;
900          case SNAPPED_RIGHT:
901              left = GET_RESOURCE_STRING(IDS_RESTORE);
902              right = GET_RESOURCE_STRING(IDS_SNAP_LEFT);
903              up = GET_RESOURCE_STRING(IDS_SNAP_UPPER_RIGHT);
904              down = GET_RESOURCE_STRING(IDS_SNAP_LOWER_RIGHT);
905              break;
906          case SNAPPED_BOTTOM_RIGHT:
907              left = GET_RESOURCE_STRING(IDS_SNAP_LOWER_LEFT);
908              right = GET_RESOURCE_STRING(IDS_SNAP_LOWER_LEFT);
909              up = GET_RESOURCE_STRING(IDS_SNAP_RIGHT);
910              down = GET_RESOURCE_STRING(IDS_MINIMIZE);
911              break;
912          case RESTORED:
913              left = GET_RESOURCE_STRING(IDS_SNAP_LEFT);
914              right = GET_RESOURCE_STRING(IDS_SNAP_RIGHT);
915              up = GET_RESOURCE_STRING(IDS_MAXIMIZE);
916              down = GET_RESOURCE_STRING(IDS_MINIMIZE);
917              break;
918          default:
919              left = GET_RESOURCE_STRING(IDS_NO_ACTION);
920              left_disabled = true;
921              right = GET_RESOURCE_STRING(IDS_NO_ACTION);
922              right_disabled = true;
923              up = GET_RESOURCE_STRING(IDS_NO_ACTION);
924              up_disabled = true;
925              down = GET_RESOURCE_STRING(IDS_NO_ACTION);
926              down_disabled = true;
927          }
928          auto text_color = D2D1::ColorF(light_mode ? 0x222222 : 0xDDDDDD, active_window_snappable && (miniature_shown || window_state == MINIMIZED) ? 1.0f : 0.3f);
929          use_overlay->find_element(L"KeyUpGroup")->SetAttributeValue(L"fill-opacity", up_disabled ? 0.3f : 1.0f);
930          text.set_alignment_center().write(d2d_device_context, text_color, use_overlay->get_maximize_label(), up);
931          use_overlay->find_element(L"KeyDownGroup")->SetAttributeValue(L"fill-opacity", down_disabled ? 0.3f : 1.0f);
932          text.write(d2d_device_context, text_color, use_overlay->get_minimize_label(), down);
933          use_overlay->find_element(L"KeyLeftGroup")->SetAttributeValue(L"fill-opacity", left_disabled ? 0.3f : 1.0f);
934          text.set_alignment_right().write(d2d_device_context, text_color, use_overlay->get_snap_left(), left);
935          use_overlay->find_element(L"KeyRightGroup")->SetAttributeValue(L"fill-opacity", right_disabled ? 0.3f : 1.0f);
936          text.set_alignment_left().write(d2d_device_context, text_color, use_overlay->get_snap_right(), right);
937      }
938      else
939      {
940          auto time_since_start = std::chrono::high_resolution_clock::now() - shown_start_time;
941          if (time_since_start.count() / 1000000 > milliseconds_press_time_for_global_windows_shortcuts - milliseconds_press_time_for_taskbar_icon_shortcuts)
942          {
943              global_windows_shortcuts_shown = true;
944              global_windows_shortcuts_animation.reset();
945          }
946      }
947  }