Shortcut Guide: Replace dual windows with single transparent overlay and add holding windows button (#48683)

## Summary

Refactors Shortcut Guide from two separate `WindowEx` instances
(`MainWindow` + `TaskbarWindow`) into a single full-monitor transparent
`OverlayWindow` that hosts both surfaces as XAML UserControls. This
enables shared animations and a more polished visual experience, and
makes the taskbar shortcut indicators **edge-aware** for Windows 11's
top/bottom/left/right taskbar positioning.



https://github.com/user-attachments/assets/e40a25f6-4ab3-4073-b1a8-906ef7782877


<img width="507" height="968" alt="image"
src="https://github.com/user-attachments/assets/2e06a3d9-32d9-482e-90fe-1f0f8a7d7598"
/>


## Changes

Closes: #48435
Closes #48491
Closes: #49200
Closes: #48552 (theme flash on Light/System theme + shortcut-list scroll
flutter)
Closes: #48773

### Architecture
- **OverlayWindow**: Single transparent host covering the full monitor
work area, using `TransparentTintBackdrop`
- **MainPaneControl**: The shortcut list pseudo-window, reusing the
shared `TransientSurface` control for chrome (acrylic backdrop, theme
shadow, rounded corners)
- **TaskbarPaneControl + TaskbarIndicator**: Tooltip-style indicators
with triangle tails, positioned above taskbar buttons

### Edge-aware taskbar indicators (Windows 11 top/bottom/left/right)
- Detects the taskbar edge via the public, documented `SHAppBarMessage`
/ `ABM_GETTASKBARPOS` API (the same API CmdPal Dock uses)
- Indicators lay out along the correct axis — horizontally for a
top/bottom taskbar, vertically for a left/right taskbar — with the
triangle tail always pointing toward the taskbar (4-direction tail +
per-edge slide-in animation)
- For a left/right taskbar, the main pane is inset so the order reads
**taskbar | indicators | pane**
- **Adaptive sizing**: each indicator's body size is derived from the
actual measured UIA taskbar button rect, so the bubbles shrink when
Windows uses small icons or combines buttons (many apps open). Uses the
smallest button slot (clamped to a readable range) so neighbouring
bubbles never overlap; the font scales with it

### Visual polish
- Windows 11 system flyout entry/exit animations (slide + fade, ~367ms
entrance / ~200ms exit with cubic easing)
- Animation direction is position-aware (slides from left when
left-aligned, from right when right-aligned)
- Taskbar indicators slide in from the taskbar edge with the same timing
- Close button on the main flyout title bar

### Robustness
- Multi-monitor DPI handling via WM_DPICHANGED suppression (prevents
double-scaling on cross-monitor moves)
- Win11 phantom border elimination (comprehensive DWM/style stripping)
- Click-outside-to-close with animated exit transition
- Process lifetime fix (`Application.Current.Exit()` on close)

## Validation
- Build clean (x64 Debug, exit 0, empty errors log)
- Tested on multi-monitor mixed-DPI setup (150% + 100%)
- Tested with the taskbar docked to each edge (top/bottom/left/right)
and with small/combined taskbar icons

---------

Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Noraa Junker <noraa.junker@outlook.com>
Co-authored-by: Clint Rutkas <clint@rutkas.com>
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
This commit is contained in:
Niels Laute
2026-07-27 12:21:35 +02:00
committed by GitHub
parent d7afa69048
commit 346b498fb5
26 changed files with 2090 additions and 609 deletions

View File

@@ -10,7 +10,6 @@
#include "../interface/powertoy_module_interface.h"
#include "Generated Files/resource.h"
#include <common/SettingsAPI/settings_objects.h>
#include <common/utils/EventWaiter.h>
BOOL APIENTRY DllMain(HMODULE /*hModule*/, DWORD /*ul_reason_for_call*/, LPVOID /*lpReserved*/)
{
@@ -37,9 +36,10 @@ public:
}
triggerEvent = CreateEvent(nullptr, false, false, CommonSharedConstants::SHORTCUT_GUIDE_TRIGGER_EVENT);
triggerEventWaiter.start(CommonSharedConstants::SHORTCUT_GUIDE_TRIGGER_EVENT, [this](DWORD) {
OnHotkeyEx();
});
if (!triggerEvent)
{
Logger::warn(L"Failed to create {} event. {}", CommonSharedConstants::SHORTCUT_GUIDE_TRIGGER_EVENT, get_last_error_or_default(GetLastError()));
}
InitSettings();
}
@@ -91,6 +91,7 @@ public:
if (!_enabled)
{
_enabled = true;
StartProcess();
}
else
{
@@ -127,6 +128,10 @@ public:
{
CloseHandle(exitEvent);
}
if (triggerEvent)
{
CloseHandle(triggerEvent);
}
delete this;
}
@@ -145,19 +150,12 @@ public:
return;
}
if (IsProcessActive())
if (!IsProcessActive())
{
TerminateProcess(m_hProcess, 0);
return;
StartProcess();
}
if (m_hProcess)
{
CloseHandle(m_hProcess);
m_hProcess = nullptr;
}
StartProcess();
SetEvent(triggerEvent);
}
virtual void send_settings_telemetry() override
@@ -168,6 +166,8 @@ public:
Logger::error("Failed to create a process to send settings telemetry");
}
}
virtual bool keep_track_of_pressed_win_key() override { return true; }
virtual UINT milliseconds_win_key_must_be_pressed() override { return 900; }
private:
std::wstring app_name;
@@ -187,7 +187,6 @@ private:
HANDLE triggerEvent;
HANDLE exitEvent;
EventWaiter triggerEventWaiter;
bool StartProcess(std::wstring args = L"")
{
@@ -196,6 +195,11 @@ private:
ResetEvent(exitEvent);
}
if (triggerEvent)
{
ResetEvent(triggerEvent);
}
unsigned long powertoys_pid = GetCurrentProcessId();
std::wstring executable_args = L"";
executable_args.append(std::to_wstring(powertoys_pid));
@@ -310,6 +314,14 @@ private:
m_hotkey.vkCode = VK_OEM_2;
}
}
void WindowsKeyPressBehavior()
{
if (IsProcessActive())
{
TerminateProcess(m_hProcess, 0);
}
}
};
extern "C" __declspec(dllexport) PowertoyModuleIface* __cdecl powertoy_create()