laf icon indicating copy to clipboard operation
laf copied to clipboard

Handle screen DPI changes on Windows

Open lampysprites opened this issue 1 year ago • 4 comments

Issue aseprite/aseprite#4606, also possibly avoids having aseprite/aseprite#2740, and I think fixes aseprite/aseprite#4495.

On several displays with diffferent scale dragging the window from one screen to another makes everything either tiny or huge, depending on the direction it's dragged. This can be an issue when the screen with higher resolution is smaller in size, like a laptop with external monitor, or a pc with a screen tablet.

I tested these changes on 100%/200% and 150%/300% screen combination, windowed/maximized window, menu and preview windows, moving window with mouse and shift+wid+arrow key, and changing screen scale in settings w/ app running. As far as I can see it behaves same as other applications (namely, notepad) now. Except, rounding of 150% scale and 1x scale setting to 1x resolution feelt a bit confusing (personally) so i rounded it up.

PR aseprite/aseprite#4934 is needed to handle these changes properly.

Window decorations

  • Laf's WinAPI constructor (laf/os/win/winapi.cpp) tries to set process' dpi awareness to PER_MONITOR_AWARE_V2, and then and to PER_MONITOR_AWARE if first one fails. In Aseprite however both actually fail with "access denied", because it can only be set once, and src/main/settings.manifest already set it to V1.
  • In addition to enabling DPI awareness V1, window needs to call EnableNonClientDpiScaling() in create event to make titlebar resize.
  • GetSystemMetric and AdjustWindowRectEx need to be replaced with dpi-aware variants when available.

Corresponding commit to Aseprite repo changes manifest file to enable V2 dpi awareness, although when it's not available V1 works identical to it. Also theorethically this can be applied without the next commit if it's too messy.

Window content

I think it's important that window content is intact when changing screen. Think of moving a preview window - doing additional zoom/pan would be annoying. It means we need to:

  • Scale window borders proportional to dpi change.
  • Adjust display scale factor, not UI scale.
  • Not clamp scale factor at 4x when drawing.

The easiest wai I think is to scale pixel coordinates in laf window as scale = baseScale * displayDPI / defaultDPI, where baseScale is now the scaling chosen by user in the settings. Scale is used for input and painting same as before, while baseScale replaces it in window settings and initial values. This however breaks backwards compatibility, and I can't think of a simple way to keep it (e.g. need to handle window sizing outside window class for that).

There's also a few ways to sprovie API for this - i picked one with fewer changes. And maybe Window::setScale should now be setBaseScale but I don't think i got naming right anyway.

Corresponding commit to Aseprite repo replaces some scale() calls with baseScale() when needed.


I agree that my contributions are licensed under the MIT License. You can find a copy of this license at https://opensource.org/licenses/MIT

lampysprites avatar Jan 11 '25 11:01 lampysprites

Thanks a lot @lampysprites for the dedication to see this issue. I'll try to review this soon, not for the next release but probably for the other, and I'll try to merge this on beta branch (so we can release a preview of the fix to a small number of users first).

dacap avatar Jan 23 '25 13:01 dacap

This is basically a default Windows Desktop Application generated by Microsoft Visual Studio Community 2022 with some modifications:

  • Removal of some unneeded things (like the about dialog, and some code that is not needed to prove the issue).
  • It sets the SetProcessDpiAwarenessContext to DPI_AWARENESS_CONTEXT_PER_MONITOR_AWARE_V2.
  • Replace the use of CreateWindowW by CreateWindowEx, to set the WS_EX_TOOLWINDOW style of the window.

dpiaware_issue.zip

This solution demonstrates that the WM_DPICHANGED message is not arriving when the user changes the display's DPI and the window has the WS_EX_TOOLWINDOW style set. If this style is not set, it works as expected.

Will try to send this to Microsoft and see what happens...

martincapello avatar Mar 28 '25 18:03 martincapello

Thanks a lot for spending time on this! I'm really sorry it turned into this kind of troubleshooting🥲 I've applied your changes with std::ceil and updated laf hash in the aseprite pr with that, is there something else?

lampysprites avatar Mar 30 '25 02:03 lampysprites

Thanks a lot for spending time on this! I'm really sorry it turned into this kind of troubleshooting🥲

No worries! Thanks to you actually for bringing this new feature.

I've applied your changes with std::ceil and updated laf hash in the aseprite pr with that, is there something else?

Nothing else by me, maybe after David's review he could point out some changes.

martincapello avatar Mar 30 '25 11:03 martincapello