Skip to content

Commit 7c9bbbc

Browse files
Merge branch 'api-NCHitTestPoint-draft' into api-NCHitTestPoint
2 parents 14d50ef + d26f6fc commit 7c9bbbc

File tree

1 file changed

+141
-77
lines changed

1 file changed

+141
-77
lines changed

specs/NonClientHitTestKind.md

Lines changed: 141 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -17,63 +17,58 @@ the CoreWebView2 via visual hosting and for UI framework WebView2 controls that
1717
hosting, like the WinUI2 and WinUI3 WebView2 control.If your app uses windowed hosting,
1818
draggable regions are supported by default, no need to follow this guide.
1919

20-
NOTE: Your app may already be forwarding mouse messages to WebView2, if so the bulk
21-
of the code below is likely already in your app. One thing you need to do specifically
22-
for this feature is to add the check for w_param == HTCAPTION as a clause for capturing
23-
the mouse and forwarding messages. It is necessary for the system menu to show. If you
24-
choose not to include this you would have to handle the logic for showing the system menu
25-
yourself.
26-
27-
NOTE: This API is not currently supported for environments that use a CoreWindow like UWP
20+
NOTE: This API is not currently supported for environments that use a CoreWindow like UWP.
2821

2922
#### Win32 C++
3023
```cpp
3124
LRESULT CALLBACK WndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam)
3225
{
3326
....
34-
// The following code forwards mouse messages to the WebView2 - Start
27+
// We need to forward relevant mouse messages to the webview
3528

36-
// handle the range of mouse messages & WM_NCRBUTTONDOWN
37-
if (message >= WM_MOUSEFIRST && message <= WM_MOUSELAST
38-
|| message == WM_NCRBUTTONDOWN || message == WM_NCRBUTTONUP)
39-
{
40-
POINT point {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
41-
// IsPointInWebview is an example of a function which your app can implement that checks if
42-
// the mouse message point lies inside the rect of the visual that hosts the WebView2
43-
if (IsPointInWebView(point) || message == WM_MOUSELEAVE)
29+
// handle the range of mouse messages & WM_NCRBUTTONDOWN
30+
if (message == WM_NCRBUTTONDOWN || message == WM_NCRBUTTONUP)
4431
{
45-
// forward mouse messages to WebView2
46-
CHECK_FAILURE(m_compositionController->SendMouseInput(
47-
static_cast<COREWEBVIEW2_MOUSE_EVENT_KIND>(message),
48-
static_cast<COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS>(GET_KEYSTATE_WPARAM(wParam)),
49-
mouseData, point));
50-
return 0;
51-
}
52-
53-
}
54-
// The following code forwards mouse messages to the WebView2 - End
55-
56-
switch(message)
57-
{
58-
....
59-
// Handling this message is important for enabling dragging
60-
case WM_NCHITTEST:
6132
POINT point {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
62-
COREWEBVIEW2_HIT_TEST_RESULT result = NULL;
63-
CHECK_FAILURE(m_compositionController->GetHitTestResultAtPoint(&point, &result));
64-
65-
if (result == COREWEBVIEW2_HIT_TEST_RESULT_HTCAPTION)
33+
// WM_NCRBUTTONDOWN/UP have poitns in screen coordinates we need
34+
// to convert the point to client coordinates to match the others
35+
ScreenToClient(hwnd, &point);
36+
37+
// IsPointInWebview is an example of a function which your app can implement that checks if
38+
// the mouse message point lies inside the rect of the visual that hosts the WebView2
39+
if (IsPointInWebView(point))
6640
{
67-
return HTCAPTION;
41+
// Adjust the point from app client coordinates to webview client coordinates.
42+
// m_webViewBounds is a rect that represents the bounds that you app has set for
43+
// the webveiw
44+
point.x -= m_webViewBounds.left;
45+
point.y -= m_webViewBounds.top;
46+
47+
// forward mouse messages to WebView2
48+
CHECK_FAILURE(m_compositionController->SendMouseInput(
49+
static_cast<COREWEBVIEW2_MOUSE_EVENT_KIND>(message),
50+
static_cast<COREWEBVIEW2_MOUSE_EVENT_VIRTUAL_KEYS>(GET_KEYSTATE_WPARAM(wParam)),
51+
0, point));
52+
return 0;
6853
}
54+
55+
}
6956

70-
break;
71-
....
72-
}
57+
switch(message)
58+
{
59+
....
60+
// Handling this message is important for enabling dragging
61+
case WM_NCHITTEST:
62+
POINT point {GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)};
63+
COREWEBVIEW2_NON_CLIENT_REGION_KIND result = COREWEBVIEW2_NON_CLIENT_REGION_KIND_NOWHERE;
64+
CHECK_FAILURE(m_compositionController->GetNonClientRegionAtPoint(&point, &result));
65+
return result;
66+
....
67+
}
7368
....
7469
}
7570
```
76-
#### C#/WinRT
71+
#### C#
7772
```c#
7873
protected override void WndProc(ref Message m)
7974
{
@@ -82,32 +77,41 @@ protected override void WndProc(ref Message m)
8277
if (m.Msg == WM_NCHITTEST)
8378
{
8479
Point point = new Point(GET_X_LPARAM(m.lParam), GET_Y_LPARAM(m.lParam));
85-
if (m_compositionController.GetHitTestResultAtPoint(point) ==
86-
CoreWebView2HitTestResult.Htcaption)
80+
if (m_compositionController.GetNonClientRegionAtPoint(point) ==
81+
CoreWebView2NonClientRegionKind.Caption)
8782
{
8883
m.Result = (IntPtr)HTCAPTION;
8984
}
9085
}
91-
// The following code forwards mouse messages to the WebView2 - Start
92-
else if (m.msg >= WM_MOUSEFIRST && m.msg <= WM_MOUSELAST
93-
|| m.msg == WM_NCRBUTTONDOWN || m.msg == WM_NCRBUTTONUP)
86+
else if (m.msg == WM_NCRBUTTONDOWN || m.msg == WM_NCRBUTTONUP)
9487
{
9588
9689
Point point = new Point(GET_X_LPARAM(m.lParam), GET_Y_LPARAM(m.lParam));
90+
91+
// WM_NCRBUTTONDOWN/UP have poitns in screen coordinates we need
92+
// to convert the point to client coordinates to match the others
93+
ScreenToClient(m.hwnd, ref point);
94+
95+
9796
// IsPointInWebview is an example of a function which your app can implement that
9897
// checks if the mouse message point lies inside the rect of the visual that hosts
9998
// the WebView.
100-
if (IsPointInWebView(point) || m.msg == WM_MOUSELEAVE)
99+
if (IsPointInWebView(point))
101100
{
101+
// Adjust the point from app client coordinates to webview client coordinates.
102+
// m_webViewBounds is a rect that represents the bounds that you app has set for
103+
// the webveiw
104+
point.x -= m_webViewBounds.left;
105+
point.y -= m_webViewBounds.top;
102106
103107
// forward mouse messages to WebView2
104108
m_compositionController.SendMouseInput(
105-
(CoreWebView2MouseEventKind)message,
109+
(CoreWebView2MouseEventKind)m.msg,
106110
(CoreWebView2MouseEventVirtualKeys)GET_KEYSTATE_WPARAM(wParam),
107-
mouseData,
111+
0,
108112
point);
109113
110-
m.Result = 0
114+
m.Result = 0;
111115
return;
112116
}
113117
@@ -116,6 +120,19 @@ else if (m.msg >= WM_MOUSEFIRST && m.msg <= WM_MOUSELAST
116120
....
117121
}
118122
```
123+
#### C#/WinRT
124+
125+
```c#
126+
void DraggableRegionSample() {
127+
128+
m_compositionController.NonClientRegionChanged += (object sender, CoreWebView2NonClientRegionChangedEventArgs arg) => {
129+
CoreWebView2NonClientRegionKind kind = arg.Region;
130+
IVector<Windows.Foundation.Rect> region_rects = m_compositionController.QueryNonClientRegion(kind);
131+
// use kind & region_rects to ConfigureRegion on the InputNonClientPointerSource
132+
}
133+
134+
}
135+
```
119136
# API Notes
120137
## Marking regions In Your App
121138
Draggable regions in WebView2 are HTML elements marked with the CSS style
@@ -129,44 +146,83 @@ See Documentation [here](https://learn.microsoft.com/en-us/microsoft-edge/progre
129146
```cpp
130147
/// This enum contains values representing possible regions a given
131148
/// point lies within
132-
typedef enum COREWEBVIEW2_HIT_TEST_RESULT {
149+
typedef enum COREWEBVIEW2_NON_CLIENT_REGION_KIND {
133150
/// A hit test region in the WebView2 which has the CSS style
134151
/// `-webkit-app-region: drag` set. Web content should use this CSS
135152
/// style to identify regions that should be treated like the app
136153
/// window's title bar. This has the same value as the Win32 HTCAPTION
137154
/// constant.
138-
COREWEBVIEW2_HIT_TEST_RESULT_CAPTION = 2,
155+
COREWEBVIEW2_NON_CLIENT_REGION_KIND_CAPTION = 2,
139156
/// A hit test region in the WebView2 which does not have the CSS style
140157
/// `-webkit-app-region: drag` set. This is normal web content that should not be
141158
/// considered part of the app window's title bar. This has the same value
142159
/// as the Win32 HTCLIENT constant.
143-
COREWEBVIEW2_HIT_TEST_RESULT_CLIENT = 1,
160+
COREWEBVIEW2_NON_CLIENT_REGION_KIND_CLIENT = 1,
144161
/// A hit test region out of bounds of the WebView2.
145162
/// This has the same value as the Win32 HTNOWHERE
146-
COREWEBVIEW2_HIT_TEST_RESULT_NOWHERE = 0
147-
} COREWEBVIEW2_HIT_TEST_RESULT;
163+
COREWEBVIEW2_NON_CLIENT_REGION_KIND_NOWHERE = 0
164+
} COREWEBVIEW2_NON_CLIENT_REGION_KIND;
165+
166+
[uuid(0BEA1283-39DC-48D1-9893-16275505CBBC), object, pointer_default(unique)]
167+
interface ICoreWebView2NonClientRegionChangedEventHandler : IUnknown {
168+
/// Called to provide the implementer with the event args for the
169+
/// corresponding event.
170+
HRESULT Invoke(
171+
[in] ICoreWebView2StagingCompositionController* sender,
172+
[in] ICoreWebView2NonClientRegionChangedEventArgs* args);
173+
}
174+
175+
/// Interface for status bar text change event args
176+
[uuid(E404B00C-669E-43EC-BE43-FB8CD7DBB480), object, pointer_default(unique)]
177+
interface ICoreWebView2NonClientRegionChangedEventArgs : IUnknown {
178+
[propget] HRESULT Region(
179+
[out, retval] COREWEBVIEW2_NON_CLIENT_REGION_KIND* value);
180+
}
181+
// Interface that Represents a Collection of Regoin Rects
182+
[uuid(A990DA9D-4243-4FCD-B895-2E7E87EAAB14), object, pointer_default(unique)]
183+
interface ICoreWebView2RegionRectCollection : IUnknown {
184+
/// Gets the number of `RegionRect` objects contained in the `RegionRectCollection`.
185+
[propget] HRESULT Count([out, retval] UINT32* value);
186+
187+
/// Gets the `RegionRect` at the specified index.
188+
HRESULT GetValueAtIndex([in] UINT32 index,
189+
[out, retval] RECT* value);
190+
191+
}
148192

149193
/// This interface includes the new API for enabling WebView2 support for hit-testing regions
150194
[uuid(42BF7BA5-917A-4C27-ADA1-EA6969854C16), object, pointer_default(unique)]
151195
interface ICoreWebView2CompositionController4 : ICoreWebView2CompositionController3 {
152-
/// If you are hosting a WebView2 using CoreWebView2CompositionController, you can call
153-
/// this method in your Win32 WndProc to determine if the mouse is moving over or
154-
/// clicking on WebView2 web content that should be considered part of the app window's
155-
/// title bar.
156-
157-
/// [in] Point is expected to be in the client coordinate space of WebView2.
158-
/// [out, retval] The method returns:
159-
/// - COREWEBVIEW2_HIT_TEST_RESULT_CAPTION when point corresponds to
160-
/// a region (HTML element) within the WebView2 with
161-
/// `-webkit-app-region: drag` CSS style set
162-
/// - COREWEBVIEW2_HIT_TEST_RESULT_CLIENT when point corresponds to
163-
/// a region (HTML element) within the WebView2 without
164-
/// `-webkit-app-region: drag` CSS style set
165-
/// - COREWEBVIEW2_HIT_TEST_RESULT_NOWHERE when point is not within the WebView2
166-
167-
HRESULT GetHitTestResultAtPoint(
168-
[in] POINT point,
169-
[out, retval] COREWEBVIEW2_HIT_TEST_RESULT* val);
196+
/// If you are hosting a WebView2 using CoreWebView2CompositionController, you can call
197+
/// this method in your Win32 WndProc to determine if the mouse is moving over or
198+
/// clicking on WebView2 web content that should be considered part of the app window's
199+
/// title bar.
200+
201+
/// The point parameter is expected to be in the client coordinate space of WebView2.
202+
/// The method sets the out parameter value as follows:
203+
/// - COREWEBVIEW2_NON_CLIENT_REGION_KIND_CAPTION when point corresponds to
204+
/// a region (HTML element) within the WebView2 with
205+
/// `-webkit-app-region: drag` CSS style set
206+
/// - COREWEBVIEW2_NON_CLIENT_REGION_KIND_CLIENT when point corresponds to
207+
/// a region (HTML element) within the WebView2 without
208+
/// `-webkit-app-region: drag` CSS style set
209+
/// - COREWEBVIEW2_NON_CLIENT_REGION_KIND_NOWHERE when point is not within the WebView2
210+
HRESULT GetNonClientRegionAtPoint(
211+
[in] POINT point,
212+
[out, retval] COREWEBVIEW2_NON_CLIENT_REGION_KIND* value);
213+
214+
HRESULT QueryNonClientRegion(
215+
[in] COREWEBVIEW2_NON_CLIENT_REGION_KIND kind,
216+
[out, retval] ICoreWebView2RegionRectCollection** rects);
217+
218+
/// Use to add a listener to be notified when NonClientRegion change
219+
HRESULT add_NonClientRegionChanged(
220+
[in] ICoreWebView2NonClientRegionChangedEventHandler* eventHandler,
221+
[out] EventRegistrationToken* token);
222+
223+
/// Removing an event handler for `NonClientRegionChanged` event
224+
HRESULT remove_NonClientRegionChanged(
225+
[in] EventRegistrationToken token);
170226
}
171227

172228
/// Mouse event type used by SendMouseInput to convey the type of mouse event
@@ -176,7 +232,6 @@ typedef enum COREWEBVIEW2_MOUSE_EVENT_KIND {
176232
....
177233
/// Mouse Right Button Down event over a nonclient area, WM_NCRBUTTONDOWN.
178234
COREWEBVIEW2_MOUSE_EVENT_KIND_NON_CLIENT_RIGHT_BUTTON_DOWN = 0x00A4,
179-
....
180235
/// Mouse Right Button up event over a nonclient area, WM_NCRBUTTONUP.
181236
COREWEBVIEW2_MOUSE_EVENT_KIND_NON_CLIENT_RIGHT_BUTTON_UP = 0x00A5,
182237
....
@@ -185,7 +240,7 @@ typedef enum COREWEBVIEW2_MOUSE_EVENT_KIND {
185240
## .Net/ WinRT
186241
```c#
187242
namespace Microsoft.Web.WebView2.Core {
188-
enum CoreWebView2HitTestResult
243+
enum CoreWebView2NonClientRegionKind
189244
{
190245
Caption = 2,
191246
Client = 1,
@@ -198,11 +253,20 @@ namespace Microsoft.Web.WebView2.Core {
198253
NonClientRightButtonUp = 0x00A5,
199254
...
200255
}
256+
runtimeclass CoreWebView2NonClientRegionChangedEventArgs
257+
{
258+
// ICoreWebView2NonClientRegionChangedEventArgs members
259+
CoreWebView2NonClientRegionKind Region { get; };
260+
261+
}
201262
runtimeclass CoreWebView2CompositionController {
202263

203264
[interface_name("Microsoft.Web.WebView2.Core.ICoreWebView2CompositionController4")]
204265
{
205-
CoreWebView2HitTestResult GetHitTestResultAtPoint(Windows.Foundation.Point point);
266+
/// ICoreWebView2CompositionController4 members
267+
event Windows.Foundation.TypedEventHandler<CoreWebView2CompositionController, CoreWebView2NonClientRegionChangedEventArgs> NonClientRegionChanged;
268+
CoreWebView2NonClientRegionKind GetNonClientRegionAtPoint(Windows.Foundation.Point point);
269+
IVector<Windows.Foundation.Rect> QueryNonClientRegion(CoreWebView2NonClientRegionKind Kind);
206270
}
207271
}
208272
}

0 commit comments

Comments
 (0)