Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Windows: Fix invisible gap bug in window positioning at screen edges #4111

Open
wants to merge 17 commits into
base: master
Choose a base branch
from

Conversation

eugenesvk
Copy link

@eugenesvk eugenesvk commented Feb 3, 2025

Removes invisible gaps between the screen and window borders due to an invisible resize border that win32 APIs consider to be part of the outer window box, so when winit uses 0,0 position, this is applied to the win box = visible window+invisible resize border, so the visible window border is positioned to the right of 0

(without a caption, the top resize border becomes visible, but is treated the same for consistency)

this is on top of #4100 to test interactions with the border-removing functionality introduced there

Questions:

  • how to make this border adjustment only work in a specific Windows version? My guess is this should work incorrectly in Windows 7 since those windows had visible resize borders, only Win10 introduced invisible resize borders
  • as an alternative fix: is there a way to simply determine which border pixels for a given window are transparent? Then this could resolve the Win version, but also improve the API itself to reflect the base desire to move the visible window and have no gaps
  • is there a way to test GUIs with winit, e.g., launch a window with specific styles and inspect which pixels are drawn where on the screen?

Before: Gap = RegularBorder + ResizeBorder
ToplessBefore Gap = RegularBorder + ResizeBorder
ToplessAfter Gap = RegularBorder
After: Gap = RegularBorder (in this case it's transparent, but for some other windows it's not, this PR doesn't touch it because it can't determine which case it is)

  • Tested on all platforms changed. Only on Windows 10, see the questions above
  • Added an entry to the changelog module if knowledge of this change could be valuable to users
  • Updated documentation to reflect any user-facing changes, including notes of platform-specific behavior
  • Created or updated an example program if it would help users understand this functionality. The example from the previous PR should suffice

closes #4107

partially addresses #4106

set WS_CAPTION to allow implementing custom titlebar while maintaining resize borders.

Currently MARKER_DECORATIONS internal window flag is used for both WS_CAPTION and WS_BORDER
for windows without title bars to allow implementing custom title bars that would handle their own resizing from the top.

via TOP_RESIZE_BORDER flag and the corresponding methods to set it. Requires manuall override of WM_NCCALCSIZE and WM_NCACTIVATE to change the border of the non-client window area to 0 and prevent it from being redrawn on window activation events
A minimal example of a window supporting a custom title bar

allowing toggling the default title bar on/off, while maintaining control over the resize borders overall and the top resize border specifically

The custom title bar itself is not implemented and left as an exercise for the reader
to avoid issues with various styling interops and get a consistent border sizes
and position 0,0 window at the corner
if window styles are set externally, it's ok?
@eugenesvk eugenesvk requested a review from notgull as a code owner February 3, 2025 13:58
@fredizzimo
Copy link

I think you should be able to query the full size using DwmGetWindowAttribute with DWMWA_EXTENDED_FRAME_BOUNDS. But the Window has to be created first.

@eugenesvk
Copy link
Author

Thanks for the tip, but this still leaves a tiny invisible gap even if the window has no thin borders

(though that API could potentially fix one issue I had with invisible thin borders, at least judging from the description, but it's only win11)

@fredizzimo
Copy link

It't available from Windows Vista up https://learn.microsoft.com/en-us/windows/win32/api/dwmapi/ne-dwmapi-dwmwindowattribute

It can do all kinds of windows, including ones with clientside decorations, I myself use it in this PR TimUntersberger/nog#293

Its also frequently recommended on stackoverflow for similar problems for example https://stackoverflow.com/questions/65599491/both-movewindow-and-setwindowpos-result-in-incorrect-window-position-size and https://stackoverflow.com/questions/34139450/getwindowrect-returns-a-size-including-invisible-borders

Note, that I think you have to do some dpi calculation to if I remember correctly

@eugenesvk
Copy link
Author

eugenesvk commented Feb 5, 2025

The api is old, but the attribute I needed is win 11 DWMWA_VISIBLE_FRAME_BORDER_THICKNESS to solve the second issue

And the gap can't be explained by dpi, x0 y0 position as reported by DWMWA_EXTENDED_FRAME_BOUNDS leaves 1 px on the left, which is also confirmed by reported client area coordinates x1

Or for a window without ws_border, where client area is the visible one, positioning client area at x0 y0 reports extended frame at x-1

(and from SO this API is in physical pixels)

With your patched win manager, try to position a window at 0,0 with a bright desktop

Or you can use my topless example from this fork https://github.com/eugenesvk/winit/tree/dev_degap_helper:

  • press E to remove caption
  • press 1 to position window at 0,0
  • press 5 to print debug info

Look for the 3 window, client, ext_frame lines at the bottom to see that when

  • a client is at0,0
  • extended is -1,-X
    where X is your (now visible) TOP resize border height and is excluded in this PR for consistency (another weird MS decision to make the border visible all of the sudden, but only at the top)

and the window now has no thin border (ws_border) either that could explain the gap

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

Successfully merging this pull request may close these issues.

0,0 window position adds an invisible gap between a screen a window
2 participants