You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
[Windows 11] Snap layouts for custom maximize buttons #70
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.
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.
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.
Activity
Let a custom maximize button bring back snap layouts (#70)
Serve a window for the real snap layout check (#70)
lijy91 commentedon Oct 7, 2026
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 ofShowSystemMenu; an empty rectangle removes it),GetMaximizeButtonBounds(),IsMaximizeButtonBoundsSupported(). Windows only (not withNATIVEAPI_ENABLE_WINUI3); every other platform returnsfalse.Windows
WM_NCHITTESTwithHTMAXBUTTONover the button; content child windows (a Flutter view) returnHTTRANSPARENTthere, and the resize border still wins where it overlaps.WM_NCMOUSEMOVE/WM_NCLBUTTONDOWN/WM_NCLBUTTONUP/WM_NCMOUSELEAVEover 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:
MaximizeButtonAreainnativeapi_flutterreports 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 getsWM_MOUSELEAVE.MaximizeButtonAreawidget 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.
lijy91 commentedon Oct 7, 2026
End to end in a real Flutter app, verified in libnativeapi/nativeapi@52b96df:
flutter_window_title_bar_examplenow draws a Maximize / Restore chip in its strip while the title bar is Hidden, wrapped inMaximizeButtonArea(the label follows the window's maximized state however it changes).tools/gui/flutter_window_title_bar_snap_test.ps1drives 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:With the core hit-test/forwarding checks and the real-hover check from the previous comment, closing.