88#include " ../types/inc/utils.hpp"
99#include " TerminalThemeHelpers.h"
1010
11- extern " C" IMAGE_DOS_HEADER __ImageBase;
12-
1311using namespace winrt ::Windows::UI;
1412using namespace winrt ::Windows::UI::Composition;
1513using namespace winrt ::Windows::UI::Xaml;
@@ -32,6 +30,135 @@ NonClientIslandWindow::~NonClientIslandWindow()
3230{
3331}
3432
33+ static constexpr const wchar_t * dragBarClassName{ L" DRAG_BAR_WINDOW_CLASS" };
34+
35+ [[nodiscard]] LRESULT __stdcall NonClientIslandWindow::_StaticInputSinkWndProc (HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept
36+ {
37+ WINRT_ASSERT (window);
38+
39+ if (WM_NCCREATE == message)
40+ {
41+ auto cs = reinterpret_cast <CREATESTRUCT*>(lparam);
42+ auto nonClientIslandWindow{ reinterpret_cast <NonClientIslandWindow*>(cs->lpCreateParams ) };
43+ SetWindowLongPtr (window, GWLP_USERDATA, reinterpret_cast <LONG_PTR>(nonClientIslandWindow));
44+ // fall through to default window procedure
45+ }
46+ else if (auto nonClientIslandWindow{ reinterpret_cast <NonClientIslandWindow*>(GetWindowLongPtr (window, GWLP_USERDATA)) })
47+ {
48+ return nonClientIslandWindow->_InputSinkMessageHandler (message, wparam, lparam);
49+ }
50+
51+ return DefWindowProc (window, message, wparam, lparam);
52+ }
53+
54+ void NonClientIslandWindow::MakeWindow () noexcept
55+ {
56+ IslandWindow::MakeWindow ();
57+
58+ static ATOM dragBarWindowClass{ []() {
59+ WNDCLASSEX wcEx{};
60+ wcEx.cbSize = sizeof (wcEx);
61+ wcEx.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS;
62+ wcEx.lpszClassName = dragBarClassName;
63+ wcEx.hbrBackground = reinterpret_cast <HBRUSH>(GetStockObject (BLACK_BRUSH));
64+ wcEx.hCursor = LoadCursor (nullptr , IDC_ARROW);
65+ wcEx.lpfnWndProc = &NonClientIslandWindow::_StaticInputSinkWndProc;
66+ wcEx.hInstance = wil::GetModuleInstanceHandle ();
67+ wcEx.cbWndExtra = sizeof (NonClientIslandWindow*);
68+ return RegisterClassEx (&wcEx);
69+ }() };
70+
71+ // The drag bar window is a child window of the top level window that is put
72+ // right on top of the drag bar. The XAML island window "steals" our mouse
73+ // messages which makes it hard to implement a custom drag area. By putting
74+ // a window on top of it, we prevent it from "stealing" the mouse messages.
75+ _dragBarWindow.reset (CreateWindowExW (WS_EX_LAYERED | WS_EX_NOREDIRECTIONBITMAP,
76+ dragBarClassName,
77+ L" " ,
78+ WS_CHILD,
79+ 0 ,
80+ 0 ,
81+ 0 ,
82+ 0 ,
83+ GetWindowHandle (),
84+ nullptr ,
85+ wil::GetModuleInstanceHandle (),
86+ this ));
87+ THROW_HR_IF_NULL (E_UNEXPECTED, _dragBarWindow);
88+ }
89+
90+ // Function Description:
91+ // - The window procedure for the drag bar forwards clicks on its client area to its parent as non-client clicks.
92+ LRESULT __stdcall NonClientIslandWindow::_InputSinkMessageHandler (UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept
93+ {
94+ std::optional<UINT> nonClientMessage{ std::nullopt };
95+
96+ // translate WM_ messages on the window to WM_NC* on the top level window
97+ switch (message)
98+ {
99+ case WM_LBUTTONDOWN:
100+ nonClientMessage = WM_NCLBUTTONDOWN;
101+ break ;
102+ case WM_LBUTTONDBLCLK:
103+ nonClientMessage = WM_NCLBUTTONDBLCLK;
104+ break ;
105+ case WM_LBUTTONUP:
106+ nonClientMessage = WM_NCLBUTTONUP;
107+ break ;
108+ case WM_RBUTTONDOWN:
109+ nonClientMessage = WM_NCRBUTTONDOWN;
110+ break ;
111+ case WM_RBUTTONDBLCLK:
112+ nonClientMessage = WM_NCRBUTTONDBLCLK;
113+ break ;
114+ case WM_RBUTTONUP:
115+ nonClientMessage = WM_NCRBUTTONUP;
116+ break ;
117+ }
118+
119+ if (nonClientMessage.has_value ())
120+ {
121+ const POINT clientPt{ GET_X_LPARAM (lparam), GET_Y_LPARAM (lparam) };
122+ POINT screenPt{ clientPt };
123+ if (ClientToScreen (_dragBarWindow.get (), &screenPt))
124+ {
125+ auto parentWindow{ GetWindowHandle () };
126+
127+ // Hit test the parent window at the screen coordinates the user clicked in the drag input sink window,
128+ // then pass that click through as an NC click in that location.
129+ const LRESULT hitTest{ SendMessage (parentWindow, WM_NCHITTEST, 0 , MAKELPARAM (screenPt.x , screenPt.y )) };
130+ SendMessage (parentWindow, nonClientMessage.value (), hitTest, 0 );
131+
132+ return 0 ;
133+ }
134+ }
135+
136+ return DefWindowProc (_dragBarWindow.get (), message, wparam, lparam);
137+ }
138+
139+ // Method Description:
140+ // - Resizes and shows/hides the drag bar input sink window.
141+ // This window is used to capture clicks on the non-client area.
142+ void NonClientIslandWindow::_ResizeDragBarWindow () noexcept
143+ {
144+ const til::rectangle rect{ _GetDragAreaRect () };
145+ if (_IsTitlebarVisible () && rect.size ().area () > 0 )
146+ {
147+ SetWindowPos (_dragBarWindow.get (),
148+ HWND_TOP,
149+ rect.left <int >(),
150+ rect.top <int >() + _GetTopBorderHeight (),
151+ rect.width <int >(),
152+ rect.height <int >(),
153+ SWP_NOACTIVATE | SWP_SHOWWINDOW);
154+ SetLayeredWindowAttributes (_dragBarWindow.get (), 0 , 255 , LWA_ALPHA);
155+ }
156+ else
157+ {
158+ SetWindowPos (_dragBarWindow.get (), HWND_BOTTOM, 0 , 0 , 0 , 0 , SWP_HIDEWINDOW | SWP_NOMOVE | SWP_NOSIZE);
159+ }
160+ }
161+
35162// Method Description:
36163// - Called when the app's size changes. When that happens, the size of the drag
37164// bar may have changed. If it has, we'll need to update the WindowRgn of the
@@ -41,9 +168,9 @@ NonClientIslandWindow::~NonClientIslandWindow()
41168// Return Value:
42169// - <none>
43170void NonClientIslandWindow::_OnDragBarSizeChanged (winrt::Windows::Foundation::IInspectable /* sender*/ ,
44- winrt::Windows::UI::Xaml::SizeChangedEventArgs /* eventArgs*/ ) const
171+ winrt::Windows::UI::Xaml::SizeChangedEventArgs /* eventArgs*/ )
45172{
46- _UpdateIslandRegion ();
173+ _ResizeDragBarWindow ();
47174}
48175
49176void NonClientIslandWindow::OnAppInitialized ()
@@ -141,7 +268,7 @@ int NonClientIslandWindow::_GetTopBorderHeight() const noexcept
141268
142269RECT NonClientIslandWindow::_GetDragAreaRect () const noexcept
143270{
144- if (_dragBar)
271+ if (_dragBar && _dragBar. Visibility () == Visibility::Visible )
145272 {
146273 const auto scale = GetCurrentDpiScale ();
147274 const auto transform = _dragBar.TransformToVisual (_rootGrid);
@@ -230,7 +357,6 @@ void NonClientIslandWindow::_UpdateIslandPosition(const UINT windowWidth, const
230357
231358 const COORD newIslandPos = { 0 , topBorderHeight };
232359
233- // I'm not sure that HWND_BOTTOM does anything different than HWND_TOP for us.
234360 winrt::check_bool (SetWindowPos (_interopWindowHandle,
235361 HWND_BOTTOM,
236362 newIslandPos.X ,
@@ -248,63 +374,12 @@ void NonClientIslandWindow::_UpdateIslandPosition(const UINT windowWidth, const
248374 // NonClientIslandWindow::OnDragBarSizeChanged method because this
249375 // method is only called when the position of the drag bar changes
250376 // **inside** the island which is not the case here.
251- _UpdateIslandRegion ();
377+ _ResizeDragBarWindow ();
252378
253379 _oldIslandPos = { newIslandPos };
254380 }
255381}
256382
257- // Method Description:
258- // - Update the region of our window that is the draggable area. This happens in
259- // response to a OnDragBarSizeChanged event. We'll calculate the areas of the
260- // window that we want to display XAML content in, and set the window region
261- // of our child xaml-island window to that region. That way, the parent window
262- // will still get NCHITTEST'ed _outside_ the XAML content area, for things
263- // like dragging and resizing.
264- // - We won't cut this region out if we're fullscreen/borderless. Instead, we'll
265- // make sure to update our region to take the entirety of the window.
266- // Arguments:
267- // - <none>
268- // Return Value:
269- // - <none>
270- void NonClientIslandWindow::_UpdateIslandRegion () const
271- {
272- if (!_interopWindowHandle || !_dragBar)
273- {
274- return ;
275- }
276-
277- // If we're showing the titlebar (when we're not fullscreen/borderless), cut
278- // a region of the window out for the drag bar. Otherwise we want the entire
279- // window to be given to the XAML island
280- if (_IsTitlebarVisible ())
281- {
282- RECT rcIsland;
283- winrt::check_bool (::GetWindowRect (_interopWindowHandle, &rcIsland));
284- const auto islandWidth = rcIsland.right - rcIsland.left ;
285- const auto islandHeight = rcIsland.bottom - rcIsland.top ;
286- const auto totalRegion = wil::unique_hrgn (CreateRectRgn (0 , 0 , islandWidth, islandHeight));
287-
288- const auto rcDragBar = _GetDragAreaRect ();
289- const auto dragBarRegion = wil::unique_hrgn (CreateRectRgn (rcDragBar.left , rcDragBar.top , rcDragBar.right , rcDragBar.bottom ));
290-
291- // island region = total region - drag bar region
292- const auto islandRegion = wil::unique_hrgn (CreateRectRgn (0 , 0 , 0 , 0 ));
293- winrt::check_bool (CombineRgn (islandRegion.get (), totalRegion.get (), dragBarRegion.get (), RGN_DIFF));
294-
295- winrt::check_bool (SetWindowRgn (_interopWindowHandle, islandRegion.get (), true ));
296- }
297- else
298- {
299- const auto windowRect = GetWindowRect ();
300- const auto width = windowRect.right - windowRect.left ;
301- const auto height = windowRect.bottom - windowRect.top ;
302-
303- auto windowRegion = wil::unique_hrgn (CreateRectRgn (0 , 0 , width, height));
304- winrt::check_bool (SetWindowRgn (_interopWindowHandle, windowRegion.get (), true ));
305- }
306- }
307-
308383// Method Description:
309384// - Returns the height of the little space at the top of the window used to
310385// resize the window.
@@ -475,6 +550,46 @@ int NonClientIslandWindow::_GetResizeHandleHeight() const noexcept
475550 return HTCAPTION;
476551}
477552
553+ // Method Description:
554+ // - Sets the cursor to the sizing cursor when we hit-test the top sizing border.
555+ // We need to do this because we've covered it up with a child window.
556+ [[nodiscard]] LRESULT NonClientIslandWindow::_OnSetCursor (WPARAM wParam, LPARAM lParam) const noexcept
557+ {
558+ if (LOWORD (lParam) == HTCLIENT)
559+ {
560+ // Get the cursor position from the _last message_ and not from
561+ // `GetCursorPos` (which returns the cursor position _at the
562+ // moment_) because if we're lagging behind the cursor's position,
563+ // we still want to get the cursor position that was associated
564+ // with that message at the time it was sent to handle the message
565+ // correctly.
566+ const auto screenPtLparam{ GetMessagePos () };
567+ const LRESULT hitTest{ SendMessage (GetWindowHandle (), WM_NCHITTEST, 0 , screenPtLparam) };
568+ if (hitTest == HTTOP)
569+ {
570+ // We have to set the vertical resize cursor manually on
571+ // the top resize handle because Windows thinks that the
572+ // cursor is on the client area because it asked the asked
573+ // the drag window with `WM_NCHITTEST` and it returned
574+ // `HTCLIENT`.
575+ // We don't want to modify the drag window's `WM_NCHITTEST`
576+ // handling to return `HTTOP` because otherwise, the system
577+ // would resize the drag window instead of the top level
578+ // window!
579+ SetCursor (LoadCursor (nullptr , IDC_SIZENS));
580+ return TRUE ;
581+ }
582+ else
583+ {
584+ // reset cursor
585+ SetCursor (LoadCursor (nullptr , IDC_ARROW));
586+ return TRUE ;
587+ }
588+ }
589+
590+ return DefWindowProc (GetWindowHandle (), WM_SETCURSOR, wParam, lParam);
591+ }
592+
478593// Method Description:
479594// - Gets the difference between window and client area size.
480595// Arguments:
@@ -558,10 +673,12 @@ void NonClientIslandWindow::_UpdateFrameMargins() const noexcept
558673{
559674 switch (message)
560675 {
676+ case WM_SETCURSOR:
677+ return _OnSetCursor (wParam, lParam);
561678 case WM_DISPLAYCHANGE:
562679 // GH#4166: When the DPI of the monitor changes out from underneath us,
563680 // resize our drag bar, to reflect its newly scaled size.
564- _UpdateIslandRegion ();
681+ _ResizeDragBarWindow ();
565682 return 0 ;
566683 case WM_NCCALCSIZE:
567684 return _OnNcCalcSize (wParam, lParam);
@@ -575,10 +692,12 @@ void NonClientIslandWindow::_UpdateFrameMargins() const noexcept
575692}
576693
577694// Method Description:
578- // - This method is called when the window receives the WM_PAINT message. It
579- // paints the background of the window to the color of the drag bar because
580- // the drag bar cannot be painted on the window by the XAML Island (see
581- // NonClientIslandWindow::_UpdateIslandRegion).
695+ // - This method is called when the window receives the WM_PAINT message.
696+ // - It paints the client area with the color of the title bar to hide the
697+ // system's title bar behind the XAML Islands window during a resize.
698+ // Indeed, the XAML Islands window doesn't resize at the same time than
699+ // the top level window
700+ // (see https://github.com/microsoft/microsoft-ui-xaml/issues/759).
582701// Return Value:
583702// - The value returned from the window proc.
584703[[nodiscard]] LRESULT NonClientIslandWindow::_OnPaint () noexcept
@@ -720,7 +839,7 @@ void NonClientIslandWindow::_SetIsFullscreen(const bool fullscreenEnabled)
720839 // always get another window message to trigger us to remove the drag bar.
721840 // So, make sure to update the size of the drag region here, so that it
722841 // _definitely_ goes away.
723- _UpdateIslandRegion ();
842+ _ResizeDragBarWindow ();
724843}
725844
726845// Method Description:
0 commit comments