[Common][PowerDisplay][QuickAccess] Shared flyout positioning helper#47097
[Common][PowerDisplay][QuickAccess] Shared flyout positioning helper#47097
Conversation
Introduces FlyoutWindowHelper in Common.UI.Controls and migrates both PowerDisplay and QuickAccess to it. Fixes two pre-existing flyout bugs: 1. PowerDisplay flyout could overlap the taskbar at 100% scaling, because the previous PowerDisplay-only positioning math anchored to the screen bounds rather than the work area on certain monitor configurations. 2. QuickAccess flyout rendered with the wrong physical size (and ended up partially off-screen) after switching system scaling between 150% and 100%. The previous code passed WindowEx.Width/Height into MoveAndResize on every summon; those properties are computed live from the current physical size and current DPI, so they drift across DPI transitions and were then double-scaled by MoveAndResize. The window now caches the XAML design size once at construction and uses that as the source of truth. The shared helper: - Uses absolute screen coordinates against DisplayArea.WorkArea so it handles non-primary and negatively-positioned monitors correctly. - Performs a 1x1 'teleport' onto the target display before the final MoveAndResize call. This avoids crossing a DPI boundary during the visible-size call, so WM_DPICHANGED never fires on the rendered window and the previous WndProc-subclass workaround is no longer required. - Exposes overloads for bottom-right anchoring (used by both flyouts) and centered placement (used by PowerDisplay's IdentifyWindow). Cleanup: - Removes the PowerDisplay-only DpiSuppressor (the DPI-suppression code path is now dead). The WndProc-subclass piece that routed WM_HOTKEY into HotkeyService is preserved as a generic WindowMessageHook in Common.UI.Controls/Window/, since PowerDisplay still needs in-process hotkey handling. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
This comment has been minimized.
@check-spelling-bot Report🔴 Please reviewSee the 📂 files view, the 📜action log, 👼 SARIF report, or 📝 job summary for details.Unrecognized words (1)DEFAULTTONEAREST These words are not needed and should be removeddefaulttonearest diu IPREVIEW ITHUMBNAIL LPCFHOOKPROC LUMA MAXDWORD MRT suntimes timespan traies udit VSyncTo accept these unrecognized words as correct and remove the previously acknowledged and now absent words, you could run the following commands... in a clone of the git@github.com:microsoft/PowerToys.git repository curl -s -S -L 'https://raw.githubusercontent.com/check-spelling/check-spelling/cfb6f7e75bbfc89c71eaa30366d0c166f1bd9c8c/apply.pl' |
perl - 'https://github.com/microsoft/PowerToys/actions/runs/24668186258/attempts/1' &&
git commit -m 'Update check-spelling metadata'OR To have the bot accept them for you, comment in the PR quoting the following line: If the flagged items are 🤯 false positivesIf items relate to a ...
|
moooyo
left a comment
There was a problem hiding this comment.
LGTM, tested on my local env.
Summary
Introduces a shared FlyoutWindowHelper in Common.UI.Controls and migrates both PowerDisplay and QuickAccess to it, eliminating two pre-existing flyout positioning bugs and removing duplicated math.
Bugs fixed
1. PowerDisplay flyout overlapped the taskbar at 100% scaling
The previous PowerDisplay-only positioning math anchored to the screen bounds rather than
DisplayArea.WorkAreaon certain monitor configurations, so the bottom edge of the flyout could land on top of the taskbar.2. QuickAccess flyout rendered too large / partially off-screen after switching DPI between 150% and 100%
The previous code passed
WindowEx.Width/HeightintoMoveAndResizeon every summon. Those properties are not the XAML literals — they are computed live asAppWindow.Size / GetDpiForWindow() * 96. After a system-scaling switch, the runtime size has drifted, that wrong "DIP" value got fed intoMoveAndResize, and the destination DPI multiplier scaled it again → wrong size, and the wrong size shifted the bottom-right anchor off-screen.QuickAccess now caches the XAML design size once at construction (when the values are still trustworthy) and uses the cache as the source of truth.
How the helper works
DisplayArea.WorkArea, so it handles non-primary and negatively-positioned monitors correctly.MoveAndResize"teleport" onto the target display before the final visible-size call. The 1×1 jump may cross a DPI boundary, but it's invisible; the second call sets the real size while the window is already on the destination monitor, so no DPI boundary is crossed for the rendered size andWM_DPICHANGEDnever fires on a visible window.IdentifyWindow).This teleport-then-size approach matches the technique the original Settings.UI flyout used for years before it was removed.
Cleanup
DpiSuppressor— its WM_DPICHANGED-suppression code path is now dead because the helper sidesteps the message entirely.DpiSuppressorclass also doubled as a generic WndProc subclass to routeWM_HOTKEYintoHotkeyService. That piece is preserved asWindowMessageHookinCommon.UI.Controls/Window/since PowerDisplay still needs in-process hotkey handling.Validation
Common.UI.Controls,PowerDisplay, andQuickAccessbuild clean (x64/Debug).DpiSuppressor→WindowMessageHookrename.