melak47 / BorderlessWindow

basic win32 example of a borderless window (with aero shadows)
Creative Commons Zero v1.0 Universal
485 stars 75 forks source link

adjust_maximized_client_rect fails when restore maximized window from taskbar #21

Open KennyProgrammer opened 1 year ago

KennyProgrammer commented 1 year ago

adjust_maximized_client_rect for me works fine when i minimize/maximize and restore window within my window, but problem appears when i hide (minimize) window using taskbar, and then again restore it with taskbar, size of the window still not fits correctly (it not fills hole monitor but a make size of my window a little larger ,and shift window down).

  1. Not maximized window (normal) image

  2. Maximized window (by double clicking on nc area or my maximize button). image

  3. But when window maximized and minimized by taskbar and restore it fails. image

And that happens on all sides of the window (i.e it little bigger that usual).

But when i resize taskbar, window again fits currently. image

I try everything, send message WM_NCCALCSIZE, when message WM_SHOWWINDOW process, or manually move and change size of the window, but result still the same.

NOTE: In my window i use the same example as BordelessWindow only i change a little the HitTest, but that not causing this problem i've checked.

LRESULT HitTest(POINT p, HWND hwnd, bool handleResize) {
        // identify borders and corners to allow resizing the window.
        // Note: On Windows 10, windows behave differently and
        // allow resizing outside the visible window frame.
        // This implementation does not replicate that behavior.
        const POINT border{
            ::GetSystemMetrics(SM_CXFRAME) + ::GetSystemMetrics(SM_CXPADDEDBORDER),
            ::GetSystemMetrics(SM_CYFRAME) + ::GetSystemMetrics(SM_CXPADDEDBORDER)
        };
        RECT window;
        if (!::GetWindowRect(hwnd, &window)) {
            return HTNOWHERE;
        }

        bool handleDragForClient = FE_FALSE;
        bool handleDrag = FE_TRUE;

        const auto drag = handleDrag ? HTCAPTION : HTCLIENT;
        const auto dragClient = handleDragForClient ? HTCAPTION : HTCLIENT;

        enum region_mask {
            client = 0b0000,
            left = 0b0001,
            right = 0b0010,
            top = 0b0100,
            bottom = 0b1000,
        };

        const auto result = left * (p.x < (window.left + border.x)) |
            right * (p.x >= (window.right - border.x)) |
            top * (p.y < (window.top + border.y)) |
            bottom * (p.y >= (window.bottom - border.y));

        switch (result) {
        case left:           return handleResize ? HTLEFT : drag;
        case right:          return handleResize ? HTRIGHT : drag;
        case top:            return handleResize ? HTTOP : drag;
        case bottom:         return handleResize ? HTBOTTOM : drag;
        case top | left:  return handleResize ? HTTOPLEFT : drag;
        case top | right: return handleResize ? HTTOPRIGHT : drag;
        case bottom | left:  return handleResize ? HTBOTTOMLEFT : drag;
        case bottom | right: return handleResize ? HTBOTTOMRIGHT : drag;
        case client: {
            // Define the No Client Area Where we can drag. In this case is:
            // 0 -- window.right - 117 
            // |                     |
            // |                     |
            // window.top + 40 -------
            // 
            // NOTE: Client Area implement here using in Force Editor as PanelNonClientArea.
            if (
                // First rect.
                p.x >= 0 && p.x <= window.right - 117 &&
                p.y >= 0 && p.y <= window.top + 32 ||

                // Second rect.
                p.x >= (window.right / 2) + 100 && p.x <= window.right &&
                p.y >= window.top + 32 && p.y <= window.top + 64
                )
                return HTCAPTION;

            return dragClient;
        }
        default:             return HTNOWHERE;
        }
    }

Edit 1: I look up to the #14 issue, it shoud fix my problem, but it didn't.

KennyProgrammer commented 1 year ago

After few hours digging out Win API, i finally found fix for this problem. It was in adjust_maximized_client_rect, in MonitorFromWindow , where its flag was set to MONITOR_DEFAULTTONULL, witch retrieve monitor only if window is intersect that monitor, but when window was minimized by task bar it seems to not intersect with monitor and returns NULL. So to fix this need just set MONITOR_DEFAULTTOPRIMARY instead, in that case that invalid NULL handle to monitor will be retrieved always as primary monitor.

melak47 commented 1 year ago

That's curious. I did some debugging, and I see the same behavior. I guess when WM_NCCALCSIZE gets sent, the system is giving us the proposed window rect, meaning it's not the actual window rect yet. Calling GetWindowRect from WM_NCCALCSIZE seems to confirm that:

Code_DnkIM0p1aG

So it makes sense that MonitorFromWindow can't determine which monitor this window intersects.

Using MONITOR_DEFAULTTOPRIMARY kind of works, but only if the window was maximized on the primary monitor. Otherwise, the monitor work rect and window rect might not even overlap, and you have effectively no client area.

However, we do know the rectangle the window is about to occupy (which is the maximized rect the window is being restored to), so we can use that to determine the right monitor:

- auto monitor = ::MonitorFromWindow(window, MONITOR_DEFAULTTONULL);
+ auto monitor = ::MonitorFromRect(&rect, MONITOR_DEFAULTTONULL);
z4none commented 10 months ago

Maybe this code can solve the problem

/* Adjust client rect to not spill over monitor edges when maximized.
 * rect(in/out): in: proposed window rect, out: calculated client rect
 * Does nothing if the window is not maximized.
 */
auto adjust_maximized_client_rect(HWND window, RECT& rect) -> void {
    if (!maximized(window)) {
        return;
    }

    // get the screen that the window is supposed to be on
    WINDOWPLACEMENT wp{};
    if (!::GetWindowPlacement(window, &wp)) {
        return;
    }

    POINT center = {
        rect.left + (rect.right - rect.left) / 2,
        rect.top + (rect.bottom - rect.top) / 2
    };

    auto monitor = ::MonitorFromPoint(center, MONITOR_DEFAULTTONULL);
    if (!monitor) {
        return;
    }

    MONITORINFO monitor_info{};
    monitor_info.cbSize = sizeof(monitor_info);
    if (!::GetMonitorInfoW(monitor, &monitor_info)) {
        return;
    }

    // when maximized, make the client area fill just the monitor (without task bar) rect,
    // not the whole window rect which extends beyond the monitor.
    rect = monitor_info.rcWork;
}