Skip to content

Commit 25137ce

Browse files
savely-krasovskyGitHub Actionsgithub-actions[bot]leaanthony
authored
feat(windows): add support for custom hit-test logic for non-client regions (#5462)
* feat(windows): add support for custom hit-test logic for non-client regions on Windows * fix(windows): address review comments * refactor: simplify non-client hit test handling logic * fix: remove handling for non-client right mouse button events * feat(windows): add cursor handling * feat(windows): handle right-click on window caption to display the system menu * fix(windows): enable non-client region tracking only is the feature is enabled * fix(windows): preserve native hover state for non-client buttons * fix(windows): address review comments * fix(runtime): rename app region css tracking internals * chore(windows): document non-client hit test ordering * feat(runtime): add runtime-config-ready event to improve initialization consistency * feat(windows): document `NonClientRegionSupport` and `WebView2CompositionHosting` options with examples and video * docs: correct video asset path in frameless windows documentation * docs: clarify `WebView2CompositionHosting` experimental status * docs: fix alignment issue in `WebView2CompositionHosting` example * docs: clarify Snap Layouts behavior and add reference to native non-client regions * docs(windows): replace `--wails-app-region` with `--wails-non-client-region` in examples and descriptions * fix(runtime): ensure runtime initialization checks for DOM environment * [skip ci] Publish @wailsio/runtime v3.0.0-alpha.96 * fix(windows): add `extendFrameIntoClientArea` helper for improved frame extension logic * fix(windows): clarify 32-bit API limitations and retain 64-bit `SetWindowLongPtrW`/`GetWindowLongPtrW` usage * fix(windows): handle `WM_SETCURSOR` with `compositionCursor` to ensure correct cursor display in client area * feat(webview2): add support for `ICoreWebView2NavigationStartingEventHandler` and navigation starting event handling * fix(webview2): add fallback to HWND controller when composition hosting fails and implement resource cleanup * fix(windows): use DPI-aware system metrics for non-client area calculations and remove hardcoded resize border fields * fix(webview2): improve DPI handling for composition-hosted and HWND-hosted WebViews, ensure correct rasterization scale updates * fix(windows): track mouse leave events in non-client and client areas to improve input handling * fix(runtime): add comment to clarify * [skip ci] Publish @wailsio/runtime v3.0.0-alpha.97 * fix(windows): remove duplicate non-client state * chore(v3): bump webview2 to v1.0.24 * chore(v3): bump to v3.0.0-alpha2.114 and update changelog [skip ci] * [skip ci] Publish @wailsio/runtime v3.0.0-alpha.96 * chore(v3): bump to v3.0.0-alpha2.114 and update changelog [skip ci] * revert: drop fork-CI artifacts (runtime alpha.96 bump + typedoc regen, fork-minted alpha2.114 release) These were committed to this branch by the fork's own GitHub Actions (publish-npm on push, nightly-release-v3 on cron), not by anyone working on the PR. The typedoc regen stamps fork source links into 90+ files and the release bump collides with upstream's real v3.0.0-alpha2.114. * fix(runtime): append scanned elements without spreading the NodeList Spreading document.body.querySelectorAll("*") into push() passes every element as a call argument, which overflows the engine's argument limit on very large documents. Reported by Copilot review. --------- Co-authored-by: GitHub Actions <github-actions@github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: taliesin-ai <lea.anthony@gmail.com>
1 parent aea50fb commit 25137ce

41 files changed

Lines changed: 2459 additions & 99 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.
Binary file not shown.

docs/src/content/docs/features/windows/basics.mdx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -403,7 +403,7 @@ mainWindow.AttachModal(childWindow)
403403
There is no per-window `SetIcon` — the application icon is set on the app via `app.SetIcon([]byte)` (or for a Linux-specific window icon, the `application.LinuxWindow.Icon` field at window creation).
404404

405405
**Snap Assist:**
406-
Shows Windows 11 snap layout options for the window.
406+
Shows Windows 11 snap layout options through the system shortcut path. For a custom HTML maximize button with native hover Snap Layouts, use [Native Non-Client Regions on Windows](/features/windows/frameless#native-non-client-regions-on-windows) instead.
407407

408408
**Taskbar flashing:**
409409
Useful for notifications when window is minimised.

docs/src/content/docs/features/windows/frameless.mdx

Lines changed: 152 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ sidebar:
66
---
77

88
import { Tabs, TabItem, Card, CardGrid } from "@astrojs/starlight/components";
9+
import wailsAppRegionVideo from "../../../../assets/windows-native-non-client-regions/wails-app-region.mp4";
910

1011
## Frameless Windows
1112

@@ -173,6 +174,156 @@ document.querySelector('.maximize').addEventListener('click', () => Window.Maxim
173174
document.querySelector('.close').addEventListener('click', () => Window.Close())
174175
```
175176

177+
## Native Non-Client Regions on Windows
178+
179+
Windows can treat parts of a custom title bar as native non-client areas. This lets you draw the title bar and caption buttons with any HTML/CSS design while keeping native Windows behavior: the caption area drags the window, the maximize button can show Windows 11 Snap Assist / Snap Layouts, and the minimize, maximize, and close buttons receive native hit testing and mouse state.
180+
181+
The video below shows a custom HTML/CSS title bar using native Windows hit testing, including Windows 11 Snap Assist / Snap Layouts on a custom maximize button.
182+
183+
<video src={wailsAppRegionVideo} controls muted playsInline></video>
184+
185+
Wails supports two Windows-specific mechanisms:
186+
187+
- `app-region` through WebView2's native non-client region support
188+
- `--wails-non-client-region` through Wails runtime tracking for custom caption buttons
189+
190+
### Choosing a Mode
191+
192+
:::caution[Experimental]
193+
`WebView2CompositionHosting` changes how the window hosts and interacts with WebView2 under the hood. Instead of the default HWND-hosted WebView2 controller, Wails uses composition controller hosting and forwards input explicitly. This mode may have rendering, input, focus, or WebView2 Runtime compatibility issues. Enable it only when you need native custom caption-button behavior, and test your app carefully on the Windows and WebView2 Runtime versions you support.
194+
:::
195+
196+
Choose based on what you need from Windows:
197+
198+
- Use `NonClientRegionSupport` for simple native app dragging with WebView2's `app-region: drag` and `app-region: no-drag`.
199+
- Use `WebView2CompositionHosting` when your custom minimize, maximize, and close buttons should behave like native Windows caption buttons.
200+
- Enable both when the same window needs WebView2-native `app-region` support and Wails-managed custom caption-button regions.
201+
202+
`NonClientRegionSupport` is the lightweight native alternative to Wails' `--wails-draggable` tracking. You mark draggable and non-draggable areas with CSS, WebView2 decides which pixels belong to the caption, and Wails asks WebView2 for the native region when hit testing.
203+
204+
That is the full scope of this mode today. It does not make custom minimize, maximize, or close buttons behave like native Windows caption buttons, and it does not enable Windows 11 Snap Assist / Snap Layouts for a custom maximize button. Use it when you need simple native app dragging without the extra machinery of `--wails-draggable`.
205+
206+
`WebView2CompositionHosting` is for custom caption buttons with native behavior. Wails tracks DOM rectangles marked with `--wails-non-client-region`, maps them to Windows hit-test values such as `HTMINBUTTON`, `HTMAXBUTTON`, and `HTCLOSE`, and forwards mouse input back into the composition-hosted WebView2 surface. That is what allows a custom maximize button to participate in Windows 11 Snap Assist / Snap Layouts while keeping any visual design you choose.
207+
208+
Put another way: `NonClientRegionSupport` is WebView2-native CSS region support. `WebView2CompositionHosting` is Wails taking responsibility for host-owned composition and custom non-client hit testing.
209+
210+
### WebView2 app-region
211+
212+
Enable WebView2's native non-client region support for the window:
213+
214+
```go
215+
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
216+
Frameless: true,
217+
Windows: application.WindowsWindow{
218+
NonClientRegionSupport: true,
219+
},
220+
})
221+
```
222+
223+
Then mark draggable areas with the CSS `app-region` property:
224+
225+
```css
226+
.titlebar {
227+
app-region: drag;
228+
}
229+
230+
.titlebar button,
231+
.titlebar input,
232+
.titlebar select,
233+
.titlebar textarea {
234+
app-region: no-drag;
235+
}
236+
```
237+
238+
Use this when you only need native caption dragging and your title bar controls are handled with normal frontend clicks.
239+
240+
The limitation of this mode is that it is constrained by WebView2's own non-client region support. In current WebView2 releases, that means drag and no-drag regions only. It is not intended to model fully custom frontend caption buttons with distinct native minimize, maximize, and close roles.
241+
242+
### Custom Caption Buttons with Native Behavior
243+
244+
For custom minimize, maximize, and close buttons that should behave like system caption buttons, enable composition hosting:
245+
246+
:::caution[Experimental]
247+
`WebView2CompositionHosting` uses WebView2 composition controller hosting with DirectComposition. See [Choosing a Mode](#choosing-a-mode) before enabling it.
248+
:::
249+
250+
```go
251+
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
252+
Frameless: true,
253+
Windows: application.WindowsWindow{
254+
WebView2CompositionHosting: true,
255+
},
256+
})
257+
```
258+
259+
Then mark each frontend region with `--wails-non-client-region`:
260+
261+
```html
262+
<div class="titlebar">
263+
<div class="title">My Application</div>
264+
<div class="window-controls">
265+
<button class="window-button minimize" aria-label="Minimize"></button>
266+
<button class="window-button maximize" aria-label="Maximize"></button>
267+
<button class="window-button close" aria-label="Close"></button>
268+
</div>
269+
</div>
270+
```
271+
272+
```css
273+
.titlebar {
274+
--wails-non-client-region: caption;
275+
height: 40px;
276+
}
277+
278+
.window-controls {
279+
display: flex;
280+
height: 100%;
281+
}
282+
283+
.window-button {
284+
width: 46px;
285+
border: 0;
286+
background: transparent;
287+
}
288+
289+
.window-button.minimize {
290+
--wails-non-client-region: minimize;
291+
}
292+
293+
.window-button.maximize {
294+
--wails-non-client-region: maximize;
295+
}
296+
297+
.window-button.close {
298+
--wails-non-client-region: close;
299+
}
300+
```
301+
302+
Supported `--wails-non-client-region` values:
303+
304+
- `caption` - draggable caption area
305+
- `minimize` - native minimize button hit target
306+
- `maximize` - native maximize button hit target, including Windows 11 Snap Assist / Snap Layouts hover behavior
307+
- `close` - native close button hit target
308+
309+
The Wails runtime observes DOM, style, size, scroll, and viewport changes, then sends region snapshots to the native window. Region geometry is measured in CSS pixels and converted to physical pixels for Windows hit testing.
310+
311+
The visual design remains entirely yours. The regions only tell Windows what each rectangle means; the button shape, icon, color, spacing, hover styling, and layout still come from your frontend.
312+
313+
### Combining Both
314+
315+
You can enable both options when you want WebView2 `app-region` support and Wails-managed caption button regions in the same window:
316+
317+
```go
318+
window := app.Window.NewWithOptions(application.WebviewWindowOptions{
319+
Frameless: true,
320+
Windows: application.WindowsWindow{
321+
NonClientRegionSupport: true,
322+
WebView2CompositionHosting: true,
323+
},
324+
})
325+
```
326+
176327
## System Buttons
177328

178329
### Implementing Close/Minimise/Maximise
@@ -358,6 +509,7 @@ body {
358509
// Trigger Windows 11 Snap Assist
359510
window.SnapAssist()
360511
```
512+
This triggers Snap Layouts through the Windows hotkey path. For a custom HTML maximize button with native hover Snap Layouts, use [Native Non-Client Regions on Windows](#native-non-client-regions-on-windows) instead.
361513

362514
**Custom title bar height:**
363515
Windows automatically detects drag regions from CSS.

docs/src/content/docs/features/windows/options.mdx

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -948,6 +948,8 @@ Windows: application.WindowsWindow{
948948
BackdropType: application.Auto,
949949
CustomTheme: application.ThemeSettings{},
950950
DisableFramelessWindowDecorations: false,
951+
NonClientRegionSupport: false,
952+
WebView2CompositionHosting: false,
951953
},
952954
```
953955

@@ -973,6 +975,15 @@ There are no `WindowsBackdropTypeMica`-style constants — use `application.Mica
973975
**DisableFramelessWindowDecorations** (`bool`)
974976
- Disable default frameless decorations (Aero shadow, rounded corners).
975977

978+
**NonClientRegionSupport** (`bool`)
979+
- Enables WebView2-native `app-region: drag` / `app-region: no-drag` support for frameless custom title bars.
980+
- This is for simple native app dragging only. It does not provide native custom caption-button behavior or Windows 11 Snap Assist / Snap Layouts for custom maximize buttons.
981+
982+
**WebView2CompositionHosting** (`bool`)
983+
- Enables Wails-managed `--wails-non-client-region` support for custom caption buttons with native Windows behavior, including Windows 11 Snap Assist / Snap Layouts on custom maximize buttons.
984+
- Experimental. This hosts WebView2 through `ICoreWebView2CompositionController` and DirectComposition instead of the default HWND-hosted controller.
985+
- Can be combined with `NonClientRegionSupport` when a window needs both WebView2-native `app-region` support and Wails-managed custom caption-button regions.
986+
976987
**Example:**
977988

978989
```go
@@ -982,6 +993,17 @@ Windows: application.WindowsWindow{
982993
},
983994
```
984995

996+
**Example - Custom Windows title bar regions:**
997+
998+
```go
999+
Windows: application.WindowsWindow{
1000+
NonClientRegionSupport: true,
1001+
WebView2CompositionHosting: true,
1002+
},
1003+
```
1004+
1005+
See [Frameless Windows](/features/windows/frameless#native-non-client-regions-on-windows) for the detailed behavior, tradeoffs, and matching CSS.
1006+
9851007
### Linux Options (per-window)
9861008

9871009
The per-window struct is `application.LinuxWindow`**not** `LinuxOptions`.

0 commit comments

Comments
 (0)