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, ¶m)) != 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 }