Skip to content

[Windows 11] Snap layouts for custom maximize buttons #70

Closed

Description

@lijy91

On Windows 11, hovering the maximize button shows the snap layout flyout. Apps that hide the native title bar and draw their own caption buttons lose it, unless the window answers WM_NCHITTEST with HTMAXBUTTON over their maximize button.

Proposal: let the app tell the window where its maximize button is (for example Window::SetMaximizeButtonRect(Rect), in content coordinates), and have the Windows hit-testing return HTMAXBUTTON there, with the hover and press states forwarded so the Flutter button can still render them.

Background:

Activity

  1. lijy91 commented on Oct 7, 2026

    @lijy91
    MemberAuthor

    Implemented in 45b0cbb (bindings + Flutter widget: libnativeapi/nativeapi@f56add5; real-hover check: 87095d0, libnativeapi/nativeapi@b3f92fa).

    API: bool Window::SetMaximizeButtonBounds(Rectangle) in content-area logical coordinates (the space of ShowSystemMenu; an empty rectangle removes it), GetMaximizeButtonBounds(), IsMaximizeButtonBoundsSupported(). Windows only (not with NATIVEAPI_ENABLE_WINUI3); every other platform returns false.

    Windows

    • The window answers WM_NCHITTEST with HTMAXBUTTON over the button; content child windows (a Flutter view) return HTTRANSPARENT there, and the resize border still wins where it overlaps.
    • WM_NCMOUSEMOVE / WM_NCLBUTTONDOWN / WM_NCLBUTTONUP / WM_NCMOUSELEAVE over the button go to the content window under the pointer as the client messages, so the app's button keeps its hover and pressed states and handles the click itself; DefWindowProc's caption-button loop never runs (it would draw a classic button and maximize on its own).

    Flutter: MaximizeButtonArea in nativeapi_flutter reports its child as the maximize button, follows it as it moves or resizes, and removes it with it.

    Verified

    • tests/window_maximize_button_test, Windows 11: 22 checks with messages sent to the windows (hit test, child stepping aside, resize border, forwarding of move/press/release/leave, no system maximize, invalid input, clearing).
    • tools/gui/core_window_snap_layout_test.ps1, Windows 11 build 26200, real hover (cursor moves only, no clicks), 3/3: resting on the button opens the snap layouts (an explorer window over ours), the content receives the moves inside the button, moving away closes the flyout and the content gets WM_MOUSELEAVE.
    • MaximizeButtonArea widget tests: reports in content coordinates, only on change, clears on removal, moves between windows.

    Still open: an end-to-end run in a real Flutter Windows app (snap layouts over a Flutter-drawn button, and its click maximizing/restoring). Keeping this open until then.

  2. lijy91 commented on Oct 7, 2026

    @lijy91
    MemberAuthor

    End to end in a real Flutter app, verified in libnativeapi/nativeapi@52b96df:

    flutter_window_title_bar_example now draws a Maximize / Restore chip in its strip while the title bar is Hidden, wrapped in MaximizeButtonArea (the label follows the window's maximized state however it changes). tools/gui/flutter_window_title_bar_snap_test.ps1 drives it on Windows 11 (build 26200, Flutter 3.47.6 debug) with owner-guarded clicks on the example's own window and no keyboard input. 3/3 runs, 9 checks each:

    • Hidden puts the Maximize chip into the strip.
    • Resting the cursor on it opens the snap layouts over the window (explorer's flyout below the chip); moving away along the strip closes them.
    • Clicking it reaches Flutter and maximizes the window; the chip reads Restore.
    • Clicking Restore restores it to the earlier width (952 → 952); the chip reads Maximize again.

    With the core hit-test/forwarding checks and the real-hover check from the previous comment, closing.

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions