From aa8b29f85413080a1809af20983e4df652c13270 Mon Sep 17 00:00:00 2001 From: Greg Depoire--Ferrer Date: Sun, 1 Mar 2020 22:13:03 +0100 Subject: [PATCH 01/17] Kill HRGN --- src/cascadia/WindowsTerminal/IslandWindow.h | 2 +- .../WindowsTerminal/NonClientIslandWindow.cpp | 219 ++++++++++-------- .../WindowsTerminal/NonClientIslandWindow.h | 10 +- 3 files changed, 129 insertions(+), 102 deletions(-) diff --git a/src/cascadia/WindowsTerminal/IslandWindow.h b/src/cascadia/WindowsTerminal/IslandWindow.h index 432b78c3142..9923062930f 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.h +++ b/src/cascadia/WindowsTerminal/IslandWindow.h @@ -17,7 +17,7 @@ class IslandWindow : IslandWindow() noexcept; virtual ~IslandWindow() override; - void MakeWindow() noexcept; + virtual void MakeWindow() noexcept; void Close(); virtual void OnSize(const UINT width, const UINT height); diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp index fc85e78f977..79402836d27 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp @@ -20,7 +20,6 @@ using namespace ::Microsoft::Console::Types; NonClientIslandWindow::NonClientIslandWindow(const ElementTheme& requestedTheme) noexcept : IslandWindow{}, - _backgroundBrushColor{ RGB(0, 0, 0) }, _theme{ requestedTheme }, _isMaximized{ false } { @@ -30,6 +29,38 @@ NonClientIslandWindow::~NonClientIslandWindow() { } +void NonClientIslandWindow::MakeWindow() noexcept +{ + IslandWindow::MakeWindow(); + + WNDCLASS wc{}; + wc.hCursor = LoadCursor(nullptr, IDC_ARROW); + wc.hInstance = reinterpret_cast(&__ImageBase); + wc.lpszClassName = L"DRAG_BAR_WINDOW_CLASS"; + wc.style = CS_DBLCLKS; + wc.lpfnWndProc = DefWindowProc; + WINRT_ASSERT(RegisterClass(&wc) != 0); + + const auto ret = CreateWindowEx(WS_EX_LAYERED | WS_EX_NOREDIRECTIONBITMAP, + wc.lpszClassName, + L"", + WS_CHILD | WS_CLIPSIBLINGS, + 0, + 0, + 0, + 0, + GetWindowHandle(), + nullptr, + wc.hInstance, + 0); + if (ret == NULL) + { + winrt::throw_last_error(); + } + + _dragBarWindow = wil::unique_hwnd(ret); +} + // Method Description: // - Called when the app's size changes. When that happens, the size of the drag // bar may have changed. If it has, we'll need to update the WindowRgn of the @@ -41,7 +72,7 @@ NonClientIslandWindow::~NonClientIslandWindow() void NonClientIslandWindow::_OnDragBarSizeChanged(winrt::Windows::Foundation::IInspectable /*sender*/, winrt::Windows::UI::Xaml::SizeChangedEventArgs /*eventArgs*/) const { - _UpdateIslandRegion(); + _UpdateDragBarWindowPosition(); } void NonClientIslandWindow::OnAppInitialized() @@ -236,13 +267,18 @@ void NonClientIslandWindow::_UpdateIslandPosition(const UINT windowWidth, const // NonClientIslandWindow::OnDragBarSizeChanged method because this // method is only called when the position of the drag bar changes // **inside** the island which is not the case here. - _UpdateIslandRegion(); + _UpdateDragBarWindowPosition(); _oldIslandPos = { newIslandPos }; } } // Method Description: +// TODO DOC +// TODO DOC +// TODO DOC +// TODO DOC +// TODO DOC // - Update the region of our window that is the draggable area. This happens in // response to a OnDragBarSizeChanged event. We'll calculate the areas of the // window that we want to display XAML content in, and set the window region @@ -255,7 +291,7 @@ void NonClientIslandWindow::_UpdateIslandPosition(const UINT windowWidth, const // - // Return Value: // - -void NonClientIslandWindow::_UpdateIslandRegion() const +void NonClientIslandWindow::_UpdateDragBarWindowPosition() const { if (!_interopWindowHandle || !_dragBar) { @@ -267,29 +303,29 @@ void NonClientIslandWindow::_UpdateIslandRegion() const // window to be given to the XAML island if (_IsTitlebarVisible()) { - RECT rcIsland; - winrt::check_bool(::GetWindowRect(_interopWindowHandle, &rcIsland)); - const auto islandWidth = rcIsland.right - rcIsland.left; - const auto islandHeight = rcIsland.bottom - rcIsland.top; - const auto totalRegion = wil::unique_hrgn(CreateRectRgn(0, 0, islandWidth, islandHeight)); - - const auto rcDragBar = _GetDragAreaRect(); - const auto dragBarRegion = wil::unique_hrgn(CreateRectRgn(rcDragBar.left, rcDragBar.top, rcDragBar.right, rcDragBar.bottom)); - - // island region = total region - drag bar region - const auto islandRegion = wil::unique_hrgn(CreateRectRgn(0, 0, 0, 0)); - winrt::check_bool(CombineRgn(islandRegion.get(), totalRegion.get(), dragBarRegion.get(), RGN_DIFF)); + // in island space coordinates + const auto dragBarRect = _GetDragAreaRect(); + + // in client space coordinates + const auto topBorderHeight = _GetTopBorderHeight(); + const RECT clientDragBarRect = { + dragBarRect.left, + dragBarRect.top + topBorderHeight, + dragBarRect.right, + dragBarRect.bottom + topBorderHeight, + }; - winrt::check_bool(SetWindowRgn(_interopWindowHandle, islandRegion.get(), true)); + winrt::check_bool(SetWindowPos(_dragBarWindow.get(), + HWND_TOP, + clientDragBarRect.left, + clientDragBarRect.top, + clientDragBarRect.right - clientDragBarRect.left, + clientDragBarRect.bottom - clientDragBarRect.top, + SWP_SHOWWINDOW | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOREDRAW)); } else { - const auto windowRect = GetWindowRect(); - const auto width = windowRect.right - windowRect.left; - const auto height = windowRect.bottom - windowRect.top; - - auto windowRegion = wil::unique_hrgn(CreateRectRgn(0, 0, width, height)); - winrt::check_bool(SetWindowRgn(_interopWindowHandle, windowRegion.get(), true)); + winrt::check_bool(ShowWindow(_dragBarWindow.get(), SW_HIDE)); } } @@ -479,87 +515,80 @@ void NonClientIslandWindow::_UpdateFrameMargins() const noexcept { switch (message) { + case WM_PARENTNOTIFY: + if (wParam == WM_LBUTTONDOWN) + { + POINT clientPt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; + + POINT screenPt = clientPt; + if (ClientToScreen(GetWindowHandle(), &screenPt)) + { + std::optional cmd; + + LRESULT hitTest = SendMessage(GetWindowHandle(), WM_NCHITTEST, 0, MAKELPARAM(screenPt.x, screenPt.y)); + switch (hitTest) + { + case HTCAPTION: + cmd = { SC_MOVE + hitTest }; + break; + case HTTOP: + cmd = { SC_SIZE + WMSZ_TOP }; + break; + } + + if (cmd.has_value()) + { + PostMessage(GetWindowHandle(), WM_SYSCOMMAND, cmd.value(), MAKELPARAM(screenPt.x, screenPt.y)); + } + } + } + break; + case WM_SETCURSOR: + { + if (LOWORD(lParam) == HTCLIENT) + { + // Get the cursor position from the _last message_ and not from + // `GetCursorPos` (which returns the cursor position _at the + // moment_) because if we're lagging behind the cursor's position, + // we still want to get the cursor position that was associated + // with that message at the time it was sent to handle the message + // correctly. + const auto screenPtDword = GetMessagePos(); + POINT screenPt = { GET_X_LPARAM(screenPtDword), GET_Y_LPARAM(screenPtDword) }; + + LRESULT hitTest = SendMessage(GetWindowHandle(), WM_NCHITTEST, 0, MAKELPARAM(screenPt.x, screenPt.y)); + if (hitTest == HTTOP) + { + // We have to set the vertical resize cursor manually on + // the top resize handle because Windows thinks that the + // cursor is on the client area because it asked the asked + // the drag window with `WM_NCHITTEST` and it returned + // `HTCLIENT`. + // We don't want to modify the drag window's `WM_NCHITTEST` + // handling to return `HTTOP` because otherwise, the system + // would resize the drag window instead of the top level + // window! + SetCursor(LoadCursor(nullptr, IDC_SIZENS)); + return TRUE; + } + else + { + // reset cursor + SetCursor(LoadCursor(nullptr, IDC_ARROW)); + return TRUE; + } + } + break; + } case WM_NCCALCSIZE: return _OnNcCalcSize(wParam, lParam); case WM_NCHITTEST: return _OnNcHitTest({ GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }); - case WM_PAINT: - return _OnPaint(); } return IslandWindow::MessageHandler(message, wParam, lParam); } -// Method Description: -// - This method is called when the window receives the WM_PAINT message. It -// paints the background of the window to the color of the drag bar because -// the drag bar cannot be painted on the window by the XAML Island (see -// NonClientIslandWindow::_UpdateIslandRegion). -// Return Value: -// - The value returned from the window proc. -[[nodiscard]] LRESULT NonClientIslandWindow::_OnPaint() noexcept -{ - if (!_titlebar) - { - return 0; - } - - PAINTSTRUCT ps{ 0 }; - const auto hdc = wil::BeginPaint(_window.get(), &ps); - if (!hdc) - { - return 0; - } - - const auto topBorderHeight = _GetTopBorderHeight(); - - if (ps.rcPaint.top < topBorderHeight) - { - RECT rcTopBorder = ps.rcPaint; - rcTopBorder.bottom = topBorderHeight; - - // To show the original top border, we have to paint on top of it with - // the alpha component set to 0. This page recommends to paint the area - // in black using the stock BLACK_BRUSH to do this: - // https://docs.microsoft.com/en-us/windows/win32/dwm/customframe#extending-the-client-frame - ::FillRect(hdc.get(), &rcTopBorder, GetStockBrush(BLACK_BRUSH)); - } - - if (ps.rcPaint.bottom > topBorderHeight) - { - RECT rcRest = ps.rcPaint; - rcRest.top = topBorderHeight; - - const auto backgroundBrush = _titlebar.Background(); - const auto backgroundSolidBrush = backgroundBrush.as(); - const auto backgroundColor = backgroundSolidBrush.Color(); - const auto color = RGB(backgroundColor.R, backgroundColor.G, backgroundColor.B); - - if (!_backgroundBrush || color != _backgroundBrushColor) - { - // Create brush for titlebar color. - _backgroundBrush = wil::unique_hbrush(CreateSolidBrush(color)); - } - - // To hide the original title bar, we have to paint on top of it with - // the alpha component set to 255. This is a hack to do it with GDI. - // See NonClientIslandWindow::_UpdateFrameMargins for more information. - HDC opaqueDc; - BP_PAINTPARAMS params = { sizeof(params), BPPF_NOCLIP | BPPF_ERASE }; - HPAINTBUFFER buf = BeginBufferedPaint(hdc.get(), &rcRest, BPBF_TOPDOWNDIB, ¶ms, &opaqueDc); - if (!buf || !opaqueDc) - { - winrt::throw_last_error(); - } - - ::FillRect(opaqueDc, &rcRest, _backgroundBrush.get()); - ::BufferedPaintSetAlpha(buf, NULL, 255); - ::EndBufferedPaint(buf, TRUE); - } - - return 0; -} - // Method Description: // - This method is called when the window receives the WM_NCCREATE message. // Return Value: diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h index 6fdec25c827..00be20b5f44 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h @@ -32,6 +32,8 @@ class NonClientIslandWindow : public IslandWindow NonClientIslandWindow(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme) noexcept; virtual ~NonClientIslandWindow() override; + virtual void MakeWindow() noexcept override; + virtual void OnSize(const UINT width, const UINT height) override; [[nodiscard]] virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override; @@ -51,11 +53,8 @@ class NonClientIslandWindow : public IslandWindow winrt::TerminalApp::TitlebarControl _titlebar{ nullptr }; winrt::Windows::UI::Xaml::UIElement _clientContent{ nullptr }; - wil::unique_hbrush _backgroundBrush; - COLORREF _backgroundBrushColor; - winrt::Windows::UI::Xaml::Controls::Border _dragBar{ nullptr }; - wil::unique_hrgn _dragBarRegion; + wil::unique_hwnd _dragBarWindow; winrt::Windows::UI::Xaml::ElementTheme _theme; @@ -68,7 +67,6 @@ class NonClientIslandWindow : public IslandWindow [[nodiscard]] LRESULT _OnNcCreate(WPARAM wParam, LPARAM lParam) noexcept override; [[nodiscard]] LRESULT _OnNcCalcSize(const WPARAM wParam, const LPARAM lParam) noexcept; [[nodiscard]] LRESULT _OnNcHitTest(POINT ptMouse) const noexcept; - [[nodiscard]] LRESULT _OnPaint() noexcept; void _OnMaximizeChange() noexcept; void _OnDragBarSizeChanged(winrt::Windows::Foundation::IInspectable sender, winrt::Windows::UI::Xaml::SizeChangedEventArgs eventArgs) const; @@ -78,6 +76,6 @@ class NonClientIslandWindow : public IslandWindow void _UpdateFrameMargins() const noexcept; void _UpdateMaximizedState(); void _UpdateIslandPosition(const UINT windowWidth, const UINT windowHeight); - void _UpdateIslandRegion() const; + void _UpdateDragBarWindowPosition() const; void _UpdateFrameTheme() const; }; From c03a1b8409a4fea78e7868d8324652f7d3884be0 Mon Sep 17 00:00:00 2001 From: Greg Depoire--Ferrer Date: Sun, 1 Mar 2020 22:35:50 +0100 Subject: [PATCH 02/17] Update doc --- src/cascadia/TerminalApp/App.xaml | 6 -- .../WindowsTerminal/NonClientIslandWindow.cpp | 85 +++++++++---------- .../WindowsTerminal/NonClientIslandWindow.h | 2 + 3 files changed, 43 insertions(+), 50 deletions(-) diff --git a/src/cascadia/TerminalApp/App.xaml b/src/cascadia/TerminalApp/App.xaml index de12e725eb5..a12620b8db8 100644 --- a/src/cascadia/TerminalApp/App.xaml +++ b/src/cascadia/TerminalApp/App.xaml @@ -41,17 +41,11 @@ the MIT License. See LICENSE in the project root for license information. --> - - diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp index 79402836d27..a2159d16d6f 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp @@ -32,7 +32,21 @@ NonClientIslandWindow::~NonClientIslandWindow() void NonClientIslandWindow::MakeWindow() noexcept { IslandWindow::MakeWindow(); + _MakeDragBarWindow(); +} +// Method Description: +// - Create the drag bar window. +// - The drag bar window is a child window of the top level window that is put +// right on top of the drag bar. The XAML island window "steals" our mouse +// messages which makes it hard to implement a custom drag area. By putting +// a window on top of it, we prevent it from "stealing" the mouse messages. +// Arguments: +// - +// Return Value: +// - +void NonClientIslandWindow::_MakeDragBarWindow() noexcept +{ WNDCLASS wc{}; wc.hCursor = LoadCursor(nullptr, IDC_ARROW); wc.hInstance = reinterpret_cast(&__ImageBase); @@ -53,10 +67,7 @@ void NonClientIslandWindow::MakeWindow() noexcept nullptr, wc.hInstance, 0); - if (ret == NULL) - { - winrt::throw_last_error(); - } + WINRT_ASSERT(ret != NULL); _dragBarWindow = wil::unique_hwnd(ret); } @@ -274,59 +285,45 @@ void NonClientIslandWindow::_UpdateIslandPosition(const UINT windowWidth, const } // Method Description: -// TODO DOC -// TODO DOC -// TODO DOC -// TODO DOC -// TODO DOC -// - Update the region of our window that is the draggable area. This happens in -// response to a OnDragBarSizeChanged event. We'll calculate the areas of the -// window that we want to display XAML content in, and set the window region -// of our child xaml-island window to that region. That way, the parent window -// will still get NCHITTEST'ed _outside_ the XAML content area, for things -// like dragging and resizing. -// - We won't cut this region out if we're fullscreen/borderless. Instead, we'll -// make sure to update our region to take the entirety of the window. +// - Update the position of the window that is put on top of the drag bar. +// - This happens in response to a OnDragBarSizeChanged event. // Arguments: // - // Return Value: // - void NonClientIslandWindow::_UpdateDragBarWindowPosition() const { - if (!_interopWindowHandle || !_dragBar) + if (!_dragBarWindow || !_dragBar) { return; } - // If we're showing the titlebar (when we're not fullscreen/borderless), cut - // a region of the window out for the drag bar. Otherwise we want the entire - // window to be given to the XAML island - if (_IsTitlebarVisible()) - { - // in island space coordinates - const auto dragBarRect = _GetDragAreaRect(); - - // in client space coordinates - const auto topBorderHeight = _GetTopBorderHeight(); - const RECT clientDragBarRect = { - dragBarRect.left, - dragBarRect.top + topBorderHeight, - dragBarRect.right, - dragBarRect.bottom + topBorderHeight, - }; + // in island space coordinates + const auto dragBarRect = _GetDragAreaRect(); - winrt::check_bool(SetWindowPos(_dragBarWindow.get(), - HWND_TOP, - clientDragBarRect.left, - clientDragBarRect.top, - clientDragBarRect.right - clientDragBarRect.left, - clientDragBarRect.bottom - clientDragBarRect.top, - SWP_SHOWWINDOW | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOREDRAW)); - } - else + if (dragBarRect.right - dragBarRect.left == 0 || + dragBarRect.bottom - dragBarRect.top == 0) { - winrt::check_bool(ShowWindow(_dragBarWindow.get(), SW_HIDE)); + ShowWindow(_dragBarWindow.get(), SW_HIDE); + return; } + + // in client space coordinates + const auto topBorderHeight = _GetTopBorderHeight(); + const RECT clientDragBarRect = { + dragBarRect.left, + dragBarRect.top + topBorderHeight, + dragBarRect.right, + dragBarRect.bottom + topBorderHeight, + }; + + winrt::check_bool(SetWindowPos(_dragBarWindow.get(), + HWND_TOP, + clientDragBarRect.left, + clientDragBarRect.top, + clientDragBarRect.right - clientDragBarRect.left, + clientDragBarRect.bottom - clientDragBarRect.top, + SWP_SHOWWINDOW | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOREDRAW)); } // Method Description: diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h index 00be20b5f44..bbd37d43c6c 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h @@ -60,6 +60,8 @@ class NonClientIslandWindow : public IslandWindow bool _isMaximized; + void _MakeDragBarWindow() noexcept; + int _GetResizeHandleHeight() const noexcept; RECT _GetDragAreaRect() const noexcept; int _GetTopBorderHeight() const noexcept; From d78ebaa1e5ecc23d4d4b5b85617ffbb6e21b1e30 Mon Sep 17 00:00:00 2001 From: Greg Depoire--Ferrer Date: Sun, 1 Mar 2020 22:40:05 +0100 Subject: [PATCH 03/17] Fix artifacts on 1px top border --- src/cascadia/WindowsTerminal/IslandWindow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cascadia/WindowsTerminal/IslandWindow.cpp b/src/cascadia/WindowsTerminal/IslandWindow.cpp index 3b5e7244272..348d35e09bf 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/IslandWindow.cpp @@ -45,6 +45,7 @@ void IslandWindow::MakeWindow() noexcept wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; wc.hIcon = LoadIconW(wc.hInstance, MAKEINTRESOURCEW(IDI_APPICON)); + wc.hbrBackground = GetStockBrush(BLACK_BRUSH); RegisterClass(&wc); WINRT_ASSERT(!_window); From bdb7f312c1ecf1adcadd45b6d368b2793bd1acaf Mon Sep 17 00:00:00 2001 From: Greg Depoire--Ferrer Date: Sun, 1 Mar 2020 23:33:37 +0100 Subject: [PATCH 04/17] Fix maximize --- .../WindowsTerminal/NonClientIslandWindow.cpp | 43 ++++++++++++++++++- .../WindowsTerminal/NonClientIslandWindow.h | 2 + 2 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp index a2159d16d6f..1728614f013 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp @@ -51,11 +51,17 @@ void NonClientIslandWindow::_MakeDragBarWindow() noexcept wc.hCursor = LoadCursor(nullptr, IDC_ARROW); wc.hInstance = reinterpret_cast(&__ImageBase); wc.lpszClassName = L"DRAG_BAR_WINDOW_CLASS"; - wc.style = CS_DBLCLKS; + wc.style = 0; wc.lpfnWndProc = DefWindowProc; WINRT_ASSERT(RegisterClass(&wc) != 0); - const auto ret = CreateWindowEx(WS_EX_LAYERED | WS_EX_NOREDIRECTIONBITMAP, + // Description of window styles: + // - Use WS_CLIPSIBLING to clip the XAML Island window to make + // sure that our window is on top of it and receives all mouse + // input. + // - WS_EX_LAYERED is required. If it is not present, then for + // some reason, the window will not receive any mouse input. + const auto ret = CreateWindowEx(WS_EX_LAYERED, wc.lpszClassName, L"", WS_CHILD | WS_CLIPSIBLINGS, @@ -515,6 +521,39 @@ void NonClientIslandWindow::_UpdateFrameMargins() const noexcept case WM_PARENTNOTIFY: if (wParam == WM_LBUTTONDOWN) { + _reallyBadWorkaround = !_reallyBadWorkaround; + + if (_reallyBadWorkaround) + { + break; + } + + const auto timeNow = std::chrono::high_resolution_clock::now(); + + if (_lastDragBarMouseDown.has_value()) + { + const auto delta = timeNow - _lastDragBarMouseDown.value(); + const auto deltaMs = std::chrono::duration_cast(delta).count(); + if (deltaMs <= GetDoubleClickTime()) + { + if (_isMaximized) + { + PostMessage(GetWindowHandle(), WM_SYSCOMMAND, SC_RESTORE, 0); + } + else + { + PostMessage(GetWindowHandle(), WM_SYSCOMMAND, SC_MAXIMIZE, 0); + } + + // reset, so that next click is not interpreted as double click again + _lastDragBarMouseDown = { std::nullopt }; + + break; + } + } + + _lastDragBarMouseDown = { timeNow }; + POINT clientPt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; POINT screenPt = clientPt; diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h index bbd37d43c6c..f9e56543e56 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h @@ -55,6 +55,8 @@ class NonClientIslandWindow : public IslandWindow winrt::Windows::UI::Xaml::Controls::Border _dragBar{ nullptr }; wil::unique_hwnd _dragBarWindow; + std::optional _lastDragBarMouseDown; + bool _reallyBadWorkaround = false; winrt::Windows::UI::Xaml::ElementTheme _theme; From b6e4e34d08dfb6cb589aa3c3f14e5ba859176e28 Mon Sep 17 00:00:00 2001 From: Greg Depoire--Ferrer Date: Mon, 2 Mar 2020 21:45:23 +0100 Subject: [PATCH 05/17] Recreate the drag window on resize to fix input issues (this is hacky!) --- .../WindowsTerminal/NonClientIslandWindow.cpp | 218 +++++++----------- .../WindowsTerminal/NonClientIslandWindow.h | 9 +- 2 files changed, 94 insertions(+), 133 deletions(-) diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp index 1728614f013..a2bc85baf9c 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp @@ -18,6 +18,8 @@ using namespace winrt::Windows::Foundation::Numerics; using namespace ::Microsoft::Console; using namespace ::Microsoft::Console::Types; +ATOM NonClientIslandWindow::_dragBarWindowClass = 0; + NonClientIslandWindow::NonClientIslandWindow(const ElementTheme& requestedTheme) noexcept : IslandWindow{}, _theme{ requestedTheme }, @@ -32,7 +34,59 @@ NonClientIslandWindow::~NonClientIslandWindow() void NonClientIslandWindow::MakeWindow() noexcept { IslandWindow::MakeWindow(); - _MakeDragBarWindow(); +} + +LRESULT __stdcall NonClientIslandWindow::_DragWindowWndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept +{ + if (message == WM_LBUTTONDOWN) + { + POINT clientPt = { GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam) }; + + POINT screenPt = clientPt; + if (ClientToScreen(window, &screenPt)) + { + std::optional cmd; + + const auto parentWindow = GetAncestor(window, GA_PARENT); + WINRT_ASSERT(parentWindow != NULL); + + LRESULT hitTest = SendMessage(parentWindow, WM_NCHITTEST, 0, MAKELPARAM(screenPt.x, screenPt.y)); + switch (hitTest) + { + case HTCAPTION: + cmd = { SC_MOVE + hitTest }; + break; + case HTTOP: + cmd = { SC_SIZE + WMSZ_TOP }; + break; + } + + if (cmd.has_value()) + { + PostMessage(parentWindow, WM_SYSCOMMAND, cmd.value(), MAKELPARAM(screenPt.x, screenPt.y)); + } + } + } + else if (message == WM_LBUTTONDBLCLK) + { + const auto parentWindow = GetAncestor(window, GA_PARENT); + WINRT_ASSERT(parentWindow != NULL); + + WINDOWPLACEMENT placement; + if (GetWindowPlacement(parentWindow, &placement)) + { + if (placement.showCmd == SW_SHOWMAXIMIZED) + { + PostMessage(parentWindow, WM_SYSCOMMAND, SC_RESTORE, 0); + } + else + { + PostMessage(parentWindow, WM_SYSCOMMAND, SC_MAXIMIZE, 0); + } + } + } + + return DefWindowProc(window, message, wparam, lparam); } // Method Description: @@ -47,35 +101,42 @@ void NonClientIslandWindow::MakeWindow() noexcept // - void NonClientIslandWindow::_MakeDragBarWindow() noexcept { - WNDCLASS wc{}; - wc.hCursor = LoadCursor(nullptr, IDC_ARROW); - wc.hInstance = reinterpret_cast(&__ImageBase); - wc.lpszClassName = L"DRAG_BAR_WINDOW_CLASS"; - wc.style = 0; - wc.lpfnWndProc = DefWindowProc; - WINRT_ASSERT(RegisterClass(&wc) != 0); - - // Description of window styles: - // - Use WS_CLIPSIBLING to clip the XAML Island window to make - // sure that our window is on top of it and receives all mouse - // input. - // - WS_EX_LAYERED is required. If it is not present, then for - // some reason, the window will not receive any mouse input. - const auto ret = CreateWindowEx(WS_EX_LAYERED, - wc.lpszClassName, + constexpr const wchar_t* className = L"DRAG_BAR_WINDOW_CLASS"; + + if (_dragBarWindowClass == 0) + { + WNDCLASS wc{}; + wc.hCursor = LoadCursor(nullptr, IDC_ARROW); + wc.hInstance = reinterpret_cast(&__ImageBase); + wc.lpszClassName = className; + wc.style = CS_DBLCLKS; + wc.lpfnWndProc = _DragWindowWndProc; + _dragBarWindowClass = RegisterClass(&wc); + WINRT_ASSERT(_dragBarWindowClass != 0); + } + + const auto dragBarRect = _GetDragAreaRect(); + + // WS_EX_LAYERED is required. If it is not present, then for + // some reason, the window will not receive any mouse input. + const auto ret = CreateWindowEx(WS_EX_LAYERED | WS_EX_NOREDIRECTIONBITMAP, + className, L"", - WS_CHILD | WS_CLIPSIBLINGS, - 0, - 0, - 0, - 0, + WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS, + dragBarRect.left, + dragBarRect.top + _GetTopBorderHeight(), + dragBarRect.right - dragBarRect.left, + dragBarRect.bottom - dragBarRect.top, GetWindowHandle(), nullptr, - wc.hInstance, + reinterpret_cast(&__ImageBase), 0); WINRT_ASSERT(ret != NULL); _dragBarWindow = wil::unique_hwnd(ret); + + // bring on top + WINRT_ASSERT(SetWindowPos(_dragBarWindow.get(), HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOREDRAW)); } // Method Description: @@ -87,9 +148,11 @@ void NonClientIslandWindow::_MakeDragBarWindow() noexcept // Return Value: // - void NonClientIslandWindow::_OnDragBarSizeChanged(winrt::Windows::Foundation::IInspectable /*sender*/, - winrt::Windows::UI::Xaml::SizeChangedEventArgs /*eventArgs*/) const + winrt::Windows::UI::Xaml::SizeChangedEventArgs /*eventArgs*/) { - _UpdateDragBarWindowPosition(); + // We have to recreate it, we can't just move it. Otherwise for some reason + // it doesn't get any window message on the new resized area. + _MakeDragBarWindow(); } void NonClientIslandWindow::OnAppInitialized() @@ -284,54 +347,12 @@ void NonClientIslandWindow::_UpdateIslandPosition(const UINT windowWidth, const // NonClientIslandWindow::OnDragBarSizeChanged method because this // method is only called when the position of the drag bar changes // **inside** the island which is not the case here. - _UpdateDragBarWindowPosition(); + _MakeDragBarWindow(); _oldIslandPos = { newIslandPos }; } } -// Method Description: -// - Update the position of the window that is put on top of the drag bar. -// - This happens in response to a OnDragBarSizeChanged event. -// Arguments: -// - -// Return Value: -// - -void NonClientIslandWindow::_UpdateDragBarWindowPosition() const -{ - if (!_dragBarWindow || !_dragBar) - { - return; - } - - // in island space coordinates - const auto dragBarRect = _GetDragAreaRect(); - - if (dragBarRect.right - dragBarRect.left == 0 || - dragBarRect.bottom - dragBarRect.top == 0) - { - ShowWindow(_dragBarWindow.get(), SW_HIDE); - return; - } - - // in client space coordinates - const auto topBorderHeight = _GetTopBorderHeight(); - const RECT clientDragBarRect = { - dragBarRect.left, - dragBarRect.top + topBorderHeight, - dragBarRect.right, - dragBarRect.bottom + topBorderHeight, - }; - - winrt::check_bool(SetWindowPos(_dragBarWindow.get(), - HWND_TOP, - clientDragBarRect.left, - clientDragBarRect.top, - clientDragBarRect.right - clientDragBarRect.left, - clientDragBarRect.bottom - clientDragBarRect.top, - SWP_SHOWWINDOW | SWP_NOACTIVATE | SWP_NOZORDER | SWP_NOOWNERZORDER | SWP_NOREDRAW)); -} - // Method Description: // - Returns the height of the little space at the top of the window used to // resize the window. @@ -518,67 +539,6 @@ void NonClientIslandWindow::_UpdateFrameMargins() const noexcept { switch (message) { - case WM_PARENTNOTIFY: - if (wParam == WM_LBUTTONDOWN) - { - _reallyBadWorkaround = !_reallyBadWorkaround; - - if (_reallyBadWorkaround) - { - break; - } - - const auto timeNow = std::chrono::high_resolution_clock::now(); - - if (_lastDragBarMouseDown.has_value()) - { - const auto delta = timeNow - _lastDragBarMouseDown.value(); - const auto deltaMs = std::chrono::duration_cast(delta).count(); - if (deltaMs <= GetDoubleClickTime()) - { - if (_isMaximized) - { - PostMessage(GetWindowHandle(), WM_SYSCOMMAND, SC_RESTORE, 0); - } - else - { - PostMessage(GetWindowHandle(), WM_SYSCOMMAND, SC_MAXIMIZE, 0); - } - - // reset, so that next click is not interpreted as double click again - _lastDragBarMouseDown = { std::nullopt }; - - break; - } - } - - _lastDragBarMouseDown = { timeNow }; - - POINT clientPt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; - - POINT screenPt = clientPt; - if (ClientToScreen(GetWindowHandle(), &screenPt)) - { - std::optional cmd; - - LRESULT hitTest = SendMessage(GetWindowHandle(), WM_NCHITTEST, 0, MAKELPARAM(screenPt.x, screenPt.y)); - switch (hitTest) - { - case HTCAPTION: - cmd = { SC_MOVE + hitTest }; - break; - case HTTOP: - cmd = { SC_SIZE + WMSZ_TOP }; - break; - } - - if (cmd.has_value()) - { - PostMessage(GetWindowHandle(), WM_SYSCOMMAND, cmd.value(), MAKELPARAM(screenPt.x, screenPt.y)); - } - } - } - break; case WM_SETCURSOR: { if (LOWORD(lParam) == HTCLIENT) diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h index f9e56543e56..a1cd1295ec5 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h @@ -48,6 +48,8 @@ class NonClientIslandWindow : public IslandWindow void OnApplicationThemeChanged(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme) override; private: + static ATOM _dragBarWindowClass; + std::optional _oldIslandPos; winrt::TerminalApp::TitlebarControl _titlebar{ nullptr }; @@ -55,13 +57,13 @@ class NonClientIslandWindow : public IslandWindow winrt::Windows::UI::Xaml::Controls::Border _dragBar{ nullptr }; wil::unique_hwnd _dragBarWindow; - std::optional _lastDragBarMouseDown; - bool _reallyBadWorkaround = false; winrt::Windows::UI::Xaml::ElementTheme _theme; bool _isMaximized; + [[nodiscard]] static LRESULT __stdcall _DragWindowWndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; + void _MakeDragBarWindow() noexcept; int _GetResizeHandleHeight() const noexcept; @@ -72,7 +74,7 @@ class NonClientIslandWindow : public IslandWindow [[nodiscard]] LRESULT _OnNcCalcSize(const WPARAM wParam, const LPARAM lParam) noexcept; [[nodiscard]] LRESULT _OnNcHitTest(POINT ptMouse) const noexcept; void _OnMaximizeChange() noexcept; - void _OnDragBarSizeChanged(winrt::Windows::Foundation::IInspectable sender, winrt::Windows::UI::Xaml::SizeChangedEventArgs eventArgs) const; + void _OnDragBarSizeChanged(winrt::Windows::Foundation::IInspectable sender, winrt::Windows::UI::Xaml::SizeChangedEventArgs eventArgs); void _SetIsFullscreen(const bool fFullscreenEnabled) override; bool _IsTitlebarVisible() const; @@ -80,6 +82,5 @@ class NonClientIslandWindow : public IslandWindow void _UpdateFrameMargins() const noexcept; void _UpdateMaximizedState(); void _UpdateIslandPosition(const UINT windowWidth, const UINT windowHeight); - void _UpdateDragBarWindowPosition() const; void _UpdateFrameTheme() const; }; From 717c69519602ee3a41091b602e22a95bba8ddc0f Mon Sep 17 00:00:00 2001 From: Greg Depoire--Ferrer Date: Mon, 2 Mar 2020 22:22:35 +0100 Subject: [PATCH 06/17] Polish --- src/cascadia/WindowsTerminal/IslandWindow.h | 2 +- .../WindowsTerminal/NonClientIslandWindow.cpp | 156 +++++++++--------- .../WindowsTerminal/NonClientIslandWindow.h | 5 +- 3 files changed, 79 insertions(+), 84 deletions(-) diff --git a/src/cascadia/WindowsTerminal/IslandWindow.h b/src/cascadia/WindowsTerminal/IslandWindow.h index 9923062930f..432b78c3142 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.h +++ b/src/cascadia/WindowsTerminal/IslandWindow.h @@ -17,7 +17,7 @@ class IslandWindow : IslandWindow() noexcept; virtual ~IslandWindow() override; - virtual void MakeWindow() noexcept; + void MakeWindow() noexcept; void Close(); virtual void OnSize(const UINT width, const UINT height); diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp index a2bc85baf9c..3657131dddc 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp @@ -31,58 +31,48 @@ NonClientIslandWindow::~NonClientIslandWindow() { } -void NonClientIslandWindow::MakeWindow() noexcept -{ - IslandWindow::MakeWindow(); -} - LRESULT __stdcall NonClientIslandWindow::_DragWindowWndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { - if (message == WM_LBUTTONDOWN) + std::optional nonClientMessage{ std::nullopt }; + + // translate WM_ messages on the window to WM_NC* on the top level window + switch (message) + { + case WM_LBUTTONDOWN: + nonClientMessage = { WM_NCLBUTTONDOWN }; + break; + case WM_LBUTTONDBLCLK: + nonClientMessage = { WM_NCLBUTTONDBLCLK }; + break; + case WM_LBUTTONUP: + nonClientMessage = { WM_NCLBUTTONUP }; + break; + case WM_RBUTTONDOWN: + nonClientMessage = { WM_NCRBUTTONDOWN }; + break; + case WM_RBUTTONDBLCLK: + nonClientMessage = { WM_NCRBUTTONDBLCLK }; + break; + case WM_RBUTTONUP: + nonClientMessage = { WM_NCRBUTTONUP }; + break; + } + + if (nonClientMessage.has_value()) { POINT clientPt = { GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam) }; POINT screenPt = clientPt; if (ClientToScreen(window, &screenPt)) { - std::optional cmd; - const auto parentWindow = GetAncestor(window, GA_PARENT); WINRT_ASSERT(parentWindow != NULL); LRESULT hitTest = SendMessage(parentWindow, WM_NCHITTEST, 0, MAKELPARAM(screenPt.x, screenPt.y)); - switch (hitTest) - { - case HTCAPTION: - cmd = { SC_MOVE + hitTest }; - break; - case HTTOP: - cmd = { SC_SIZE + WMSZ_TOP }; - break; - } - - if (cmd.has_value()) - { - PostMessage(parentWindow, WM_SYSCOMMAND, cmd.value(), MAKELPARAM(screenPt.x, screenPt.y)); - } - } - } - else if (message == WM_LBUTTONDBLCLK) - { - const auto parentWindow = GetAncestor(window, GA_PARENT); - WINRT_ASSERT(parentWindow != NULL); - WINDOWPLACEMENT placement; - if (GetWindowPlacement(parentWindow, &placement)) - { - if (placement.showCmd == SW_SHOWMAXIMIZED) - { - PostMessage(parentWindow, WM_SYSCOMMAND, SC_RESTORE, 0); - } - else - { - PostMessage(parentWindow, WM_SYSCOMMAND, SC_MAXIMIZE, 0); - } + SendMessage(parentWindow, nonClientMessage.value(), hitTest, 0); + + return 0; } } @@ -90,16 +80,18 @@ LRESULT __stdcall NonClientIslandWindow::_DragWindowWndProc(HWND const window, U } // Method Description: -// - Create the drag bar window. +// - Create/re-creates the drag bar window. // - The drag bar window is a child window of the top level window that is put // right on top of the drag bar. The XAML island window "steals" our mouse // messages which makes it hard to implement a custom drag area. By putting // a window on top of it, we prevent it from "stealing" the mouse messages. +// - We have to recreate it, we can't just move it. Otherwise for some reason +// it doesn't get any window message on the new resized area. // Arguments: // - // Return Value: // - -void NonClientIslandWindow::_MakeDragBarWindow() noexcept +void NonClientIslandWindow::_RecreateDragBarWindow() noexcept { constexpr const wchar_t* className = L"DRAG_BAR_WINDOW_CLASS"; @@ -135,7 +127,7 @@ void NonClientIslandWindow::_MakeDragBarWindow() noexcept _dragBarWindow = wil::unique_hwnd(ret); - // bring on top + // bring it on top of the XAML Islands window WINRT_ASSERT(SetWindowPos(_dragBarWindow.get(), HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOREDRAW)); } @@ -150,9 +142,7 @@ void NonClientIslandWindow::_MakeDragBarWindow() noexcept void NonClientIslandWindow::_OnDragBarSizeChanged(winrt::Windows::Foundation::IInspectable /*sender*/, winrt::Windows::UI::Xaml::SizeChangedEventArgs /*eventArgs*/) { - // We have to recreate it, we can't just move it. Otherwise for some reason - // it doesn't get any window message on the new resized area. - _MakeDragBarWindow(); + _RecreateDragBarWindow(); } void NonClientIslandWindow::OnAppInitialized() @@ -347,7 +337,7 @@ void NonClientIslandWindow::_UpdateIslandPosition(const UINT windowWidth, const // NonClientIslandWindow::OnDragBarSizeChanged method because this // method is only called when the position of the drag bar changes // **inside** the island which is not the case here. - _MakeDragBarWindow(); + _RecreateDragBarWindow(); _oldIslandPos = { newIslandPos }; } @@ -458,6 +448,45 @@ int NonClientIslandWindow::_GetResizeHandleHeight() const noexcept return HTCAPTION; } +[[nodiscard]] LRESULT NonClientIslandWindow::_OnSetCursor(WPARAM wParam, LPARAM lParam) const noexcept +{ + if (LOWORD(lParam) == HTCLIENT) + { + // Get the cursor position from the _last message_ and not from + // `GetCursorPos` (which returns the cursor position _at the + // moment_) because if we're lagging behind the cursor's position, + // we still want to get the cursor position that was associated + // with that message at the time it was sent to handle the message + // correctly. + const auto screenPtDword = GetMessagePos(); + POINT screenPt = { GET_X_LPARAM(screenPtDword), GET_Y_LPARAM(screenPtDword) }; + + LRESULT hitTest = SendMessage(GetWindowHandle(), WM_NCHITTEST, 0, MAKELPARAM(screenPt.x, screenPt.y)); + if (hitTest == HTTOP) + { + // We have to set the vertical resize cursor manually on + // the top resize handle because Windows thinks that the + // cursor is on the client area because it asked the asked + // the drag window with `WM_NCHITTEST` and it returned + // `HTCLIENT`. + // We don't want to modify the drag window's `WM_NCHITTEST` + // handling to return `HTTOP` because otherwise, the system + // would resize the drag window instead of the top level + // window! + SetCursor(LoadCursor(nullptr, IDC_SIZENS)); + return TRUE; + } + else + { + // reset cursor + SetCursor(LoadCursor(nullptr, IDC_ARROW)); + return TRUE; + } + } + + return DefWindowProc(GetWindowHandle(), WM_SETCURSOR, wParam, lParam); +} + // Method Description: // - Gets the difference between window and client area size. // Arguments: @@ -541,40 +570,7 @@ void NonClientIslandWindow::_UpdateFrameMargins() const noexcept { case WM_SETCURSOR: { - if (LOWORD(lParam) == HTCLIENT) - { - // Get the cursor position from the _last message_ and not from - // `GetCursorPos` (which returns the cursor position _at the - // moment_) because if we're lagging behind the cursor's position, - // we still want to get the cursor position that was associated - // with that message at the time it was sent to handle the message - // correctly. - const auto screenPtDword = GetMessagePos(); - POINT screenPt = { GET_X_LPARAM(screenPtDword), GET_Y_LPARAM(screenPtDword) }; - - LRESULT hitTest = SendMessage(GetWindowHandle(), WM_NCHITTEST, 0, MAKELPARAM(screenPt.x, screenPt.y)); - if (hitTest == HTTOP) - { - // We have to set the vertical resize cursor manually on - // the top resize handle because Windows thinks that the - // cursor is on the client area because it asked the asked - // the drag window with `WM_NCHITTEST` and it returned - // `HTCLIENT`. - // We don't want to modify the drag window's `WM_NCHITTEST` - // handling to return `HTTOP` because otherwise, the system - // would resize the drag window instead of the top level - // window! - SetCursor(LoadCursor(nullptr, IDC_SIZENS)); - return TRUE; - } - else - { - // reset cursor - SetCursor(LoadCursor(nullptr, IDC_ARROW)); - return TRUE; - } - } - break; + return _OnSetCursor(wParam, lParam); } case WM_NCCALCSIZE: return _OnNcCalcSize(wParam, lParam); diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h index a1cd1295ec5..d1f0092626a 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h @@ -32,8 +32,6 @@ class NonClientIslandWindow : public IslandWindow NonClientIslandWindow(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme) noexcept; virtual ~NonClientIslandWindow() override; - virtual void MakeWindow() noexcept override; - virtual void OnSize(const UINT width, const UINT height) override; [[nodiscard]] virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override; @@ -64,7 +62,7 @@ class NonClientIslandWindow : public IslandWindow [[nodiscard]] static LRESULT __stdcall _DragWindowWndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; - void _MakeDragBarWindow() noexcept; + void _RecreateDragBarWindow() noexcept; int _GetResizeHandleHeight() const noexcept; RECT _GetDragAreaRect() const noexcept; @@ -73,6 +71,7 @@ class NonClientIslandWindow : public IslandWindow [[nodiscard]] LRESULT _OnNcCreate(WPARAM wParam, LPARAM lParam) noexcept override; [[nodiscard]] LRESULT _OnNcCalcSize(const WPARAM wParam, const LPARAM lParam) noexcept; [[nodiscard]] LRESULT _OnNcHitTest(POINT ptMouse) const noexcept; + [[nodiscard]] LRESULT _OnSetCursor(WPARAM wParam, LPARAM lParam) const noexcept; void _OnMaximizeChange() noexcept; void _OnDragBarSizeChanged(winrt::Windows::Foundation::IInspectable sender, winrt::Windows::UI::Xaml::SizeChangedEventArgs eventArgs); From c5577e4a387d46a12e095b5052bab41f7e0c3cbf Mon Sep 17 00:00:00 2001 From: Greg Depoire--Ferrer Date: Fri, 6 Mar 2020 14:50:12 +0100 Subject: [PATCH 07/17] Add WM_PAINT handling back to hide system min/max controls and black background during resize --- .../WindowsTerminal/NonClientIslandWindow.cpp | 75 ++++++++++++++++++- .../WindowsTerminal/NonClientIslandWindow.h | 4 + 2 files changed, 78 insertions(+), 1 deletion(-) diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp index da0d05e434d..31b11bd22ad 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp @@ -320,7 +320,6 @@ void NonClientIslandWindow::_UpdateIslandPosition(const UINT windowWidth, const const COORD newIslandPos = { 0, topBorderHeight }; - // I'm not sure that HWND_BOTTOM does anything different than HWND_TOP for us. winrt::check_bool(SetWindowPos(_interopWindowHandle, HWND_BOTTOM, newIslandPos.X, @@ -575,6 +574,8 @@ void NonClientIslandWindow::_UpdateFrameMargins() const noexcept return _OnNcCalcSize(wParam, lParam); case WM_NCHITTEST: return _OnNcHitTest({ GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }); + case WM_PAINT: + return _OnPaint(); case WM_ACTIVATE: // If we do this every time we're activated, it should be close enough to correct. TerminalTrySetDarkTheme(_window.get()); @@ -583,6 +584,78 @@ void NonClientIslandWindow::_UpdateFrameMargins() const noexcept return IslandWindow::MessageHandler(message, wParam, lParam); } +// Method Description: +// - This method is called when the window receives the WM_PAINT message. +// - It paints the client area with the color of the title bar to hide the +// system's title bar behind the XAML Islands window during a resize. +// Indeed, the XAML Islands window doesn't resize at the same time than +// the top level window +// (see https://github.com/microsoft/microsoft-ui-xaml/issues/759). +// Return Value: +// - The value returned from the window proc. +[[nodiscard]] LRESULT NonClientIslandWindow::_OnPaint() noexcept +{ + if (!_titlebar) + { + return 0; + } + + PAINTSTRUCT ps{ 0 }; + const auto hdc = wil::BeginPaint(_window.get(), &ps); + if (!hdc) + { + return 0; + } + + const auto topBorderHeight = _GetTopBorderHeight(); + + if (ps.rcPaint.top < topBorderHeight) + { + RECT rcTopBorder = ps.rcPaint; + rcTopBorder.bottom = topBorderHeight; + + // To show the original top border, we have to paint on top of it with + // the alpha component set to 0. This page recommends to paint the area + // in black using the stock BLACK_BRUSH to do this: + // https://docs.microsoft.com/en-us/windows/win32/dwm/customframe#extending-the-client-frame + ::FillRect(hdc.get(), &rcTopBorder, GetStockBrush(BLACK_BRUSH)); + } + + if (ps.rcPaint.bottom > topBorderHeight) + { + RECT rcRest = ps.rcPaint; + rcRest.top = topBorderHeight; + + const auto backgroundBrush = _titlebar.Background(); + const auto backgroundSolidBrush = backgroundBrush.as(); + const auto backgroundColor = backgroundSolidBrush.Color(); + const auto color = RGB(backgroundColor.R, backgroundColor.G, backgroundColor.B); + + if (!_backgroundBrush || color != _backgroundBrushColor) + { + // Create brush for titlebar color. + _backgroundBrush = wil::unique_hbrush(CreateSolidBrush(color)); + } + + // To hide the original title bar, we have to paint on top of it with + // the alpha component set to 255. This is a hack to do it with GDI. + // See NonClientIslandWindow::_UpdateFrameMargins for more information. + HDC opaqueDc; + BP_PAINTPARAMS params = { sizeof(params), BPPF_NOCLIP | BPPF_ERASE }; + HPAINTBUFFER buf = BeginBufferedPaint(hdc.get(), &rcRest, BPBF_TOPDOWNDIB, ¶ms, &opaqueDc); + if (!buf || !opaqueDc) + { + winrt::throw_last_error(); + } + + ::FillRect(opaqueDc, &rcRest, _backgroundBrush.get()); + ::BufferedPaintSetAlpha(buf, NULL, 255); + ::EndBufferedPaint(buf, TRUE); + } + + return 0; +} + // Method Description: // - This method is called when the window receives the WM_NCCREATE message. // Return Value: diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h index d1f0092626a..db213a802df 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h @@ -53,6 +53,9 @@ class NonClientIslandWindow : public IslandWindow winrt::TerminalApp::TitlebarControl _titlebar{ nullptr }; winrt::Windows::UI::Xaml::UIElement _clientContent{ nullptr }; + wil::unique_hbrush _backgroundBrush; + COLORREF _backgroundBrushColor; + winrt::Windows::UI::Xaml::Controls::Border _dragBar{ nullptr }; wil::unique_hwnd _dragBarWindow; @@ -71,6 +74,7 @@ class NonClientIslandWindow : public IslandWindow [[nodiscard]] LRESULT _OnNcCreate(WPARAM wParam, LPARAM lParam) noexcept override; [[nodiscard]] LRESULT _OnNcCalcSize(const WPARAM wParam, const LPARAM lParam) noexcept; [[nodiscard]] LRESULT _OnNcHitTest(POINT ptMouse) const noexcept; + [[nodiscard]] LRESULT _OnPaint() noexcept; [[nodiscard]] LRESULT _OnSetCursor(WPARAM wParam, LPARAM lParam) const noexcept; void _OnMaximizeChange() noexcept; void _OnDragBarSizeChanged(winrt::Windows::Foundation::IInspectable sender, winrt::Windows::UI::Xaml::SizeChangedEventArgs eventArgs); From f30b8d5ae13d2c96196bc22b82f3a38abfd41149 Mon Sep 17 00:00:00 2001 From: Greg Depoire--Ferrer Date: Fri, 6 Mar 2020 14:52:22 +0100 Subject: [PATCH 08/17] oops --- src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp | 1 + 1 file changed, 1 insertion(+) diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp index 31b11bd22ad..61ba2933de2 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp @@ -23,6 +23,7 @@ ATOM NonClientIslandWindow::_dragBarWindowClass = 0; NonClientIslandWindow::NonClientIslandWindow(const ElementTheme& requestedTheme) noexcept : IslandWindow{}, + _backgroundBrushColor{ RGB(0, 0, 0) }, _theme{ requestedTheme }, _isMaximized{ false } { From 5c4428379b4ca53d59453e7bcfe393464b17f47b Mon Sep 17 00:00:00 2001 From: Greg Depoire--Ferrer Date: Fri, 6 Mar 2020 14:59:39 +0100 Subject: [PATCH 09/17] remove unneeded `wc.hbrBackground = ...` because we paint background ourselves --- src/cascadia/WindowsTerminal/IslandWindow.cpp | 1 - 1 file changed, 1 deletion(-) diff --git a/src/cascadia/WindowsTerminal/IslandWindow.cpp b/src/cascadia/WindowsTerminal/IslandWindow.cpp index 348d35e09bf..3b5e7244272 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/IslandWindow.cpp @@ -45,7 +45,6 @@ void IslandWindow::MakeWindow() noexcept wc.style = CS_HREDRAW | CS_VREDRAW; wc.lpfnWndProc = WndProc; wc.hIcon = LoadIconW(wc.hInstance, MAKEINTRESOURCEW(IDI_APPICON)); - wc.hbrBackground = GetStockBrush(BLACK_BRUSH); RegisterClass(&wc); WINRT_ASSERT(!_window); From 1e69a7c4dbfc5e2480c5088e37157937f4a572c5 Mon Sep 17 00:00:00 2001 From: Greg Depoire--Ferrer Date: Sat, 28 Mar 2020 20:04:18 +0100 Subject: [PATCH 10/17] fix merge --- dep/gsl | 2 +- src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/dep/gsl b/dep/gsl index 7e99e76c976..1212beae777 160000 --- a/dep/gsl +++ b/dep/gsl @@ -1 +1 @@ -Subproject commit 7e99e76c9761d0d0b0848b91f8648830670ee872 +Subproject commit 1212beae777dba02c230ece8c0c0ec12790047ea diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp index 412180a1e30..a1b67966ff6 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp @@ -811,7 +811,7 @@ void NonClientIslandWindow::_SetIsFullscreen(const bool fullscreenEnabled) // always get another window message to trigger us to remove the drag bar. // So, make sure to update the size of the drag region here, so that it // _definitely_ goes away. - _UpdateIslandRegion(); + _RecreateDragBarWindow(); } // Method Description: From c3759587195744a3a2e01e2be4ee44ef1f6ab100 Mon Sep 17 00:00:00 2001 From: Greg Depoire--Ferrer Date: Sat, 28 Mar 2020 20:05:07 +0100 Subject: [PATCH 11/17] fix drag bar in fullscreen --- .../WindowsTerminal/NonClientIslandWindow.cpp | 49 +++++++++++-------- 1 file changed, 28 insertions(+), 21 deletions(-) diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp index a1b67966ff6..0cb4ff0b1d6 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp @@ -112,26 +112,33 @@ void NonClientIslandWindow::_RecreateDragBarWindow() noexcept const auto dragBarRect = _GetDragAreaRect(); - // WS_EX_LAYERED is required. If it is not present, then for - // some reason, the window will not receive any mouse input. - const auto ret = CreateWindowEx(WS_EX_LAYERED | WS_EX_NOREDIRECTIONBITMAP, - className, - L"", - WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS, - dragBarRect.left, - dragBarRect.top + _GetTopBorderHeight(), - dragBarRect.right - dragBarRect.left, - dragBarRect.bottom - dragBarRect.top, - GetWindowHandle(), - nullptr, - reinterpret_cast(&__ImageBase), - 0); - WINRT_ASSERT(ret != NULL); - - _dragBarWindow = wil::unique_hwnd(ret); - - // bring it on top of the XAML Islands window - WINRT_ASSERT(SetWindowPos(_dragBarWindow.get(), HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOREDRAW)); + // delete previous window + _dragBarWindow = nullptr; + + if (dragBarRect.right - dragBarRect.left > 0 && + dragBarRect.bottom - dragBarRect.top > 0) + { + // WS_EX_LAYERED is required. If it is not present, then for + // some reason, the window will not receive any mouse input. + const auto ret = CreateWindowEx(WS_EX_LAYERED | WS_EX_NOREDIRECTIONBITMAP, + className, + L"", + WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS, + dragBarRect.left, + dragBarRect.top + _GetTopBorderHeight(), + dragBarRect.right - dragBarRect.left, + dragBarRect.bottom - dragBarRect.top, + GetWindowHandle(), + nullptr, + reinterpret_cast(&__ImageBase), + 0); + WINRT_ASSERT(ret != NULL); + + _dragBarWindow = wil::unique_hwnd(ret); + + // bring it on top of the XAML Islands window + WINRT_ASSERT(SetWindowPos(_dragBarWindow.get(), HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOREDRAW)); + } } // Method Description: @@ -243,7 +250,7 @@ int NonClientIslandWindow::_GetTopBorderHeight() const noexcept RECT NonClientIslandWindow::_GetDragAreaRect() const noexcept { - if (_dragBar) + if (_dragBar && _dragBar.Visibility() == Visibility::Visible) { const auto scale = GetCurrentDpiScale(); const auto transform = _dragBar.TransformToVisual(_rootGrid); From 9450754e6a5c48c6c17cc928169ed3f05c88b3ed Mon Sep 17 00:00:00 2001 From: Greg Depoire--Ferrer Date: Sat, 28 Mar 2020 20:40:25 +0100 Subject: [PATCH 12/17] spell check --- .../whitelist/c3759587195744a3a2e01e2be4ee44ef1f6ab100.txt | 6 ++++++ .github/actions/spell-check/whitelist/whitelist.txt | 4 ---- 2 files changed, 6 insertions(+), 4 deletions(-) create mode 100644 .github/actions/spell-check/whitelist/c3759587195744a3a2e01e2be4ee44ef1f6ab100.txt diff --git a/.github/actions/spell-check/whitelist/c3759587195744a3a2e01e2be4ee44ef1f6ab100.txt b/.github/actions/spell-check/whitelist/c3759587195744a3a2e01e2be4ee44ef1f6ab100.txt new file mode 100644 index 00000000000..72be933dc0e --- /dev/null +++ b/.github/actions/spell-check/whitelist/c3759587195744a3a2e01e2be4ee44ef1f6ab100.txt @@ -0,0 +1,6 @@ +impl +NCLBUTTONDBLCLK +NCRBUTTONDBLCLK +NOREDIRECTIONBITMAP +SIZENS +xy diff --git a/.github/actions/spell-check/whitelist/whitelist.txt b/.github/actions/spell-check/whitelist/whitelist.txt index 6f102a7b72f..001168e62e8 100644 --- a/.github/actions/spell-check/whitelist/whitelist.txt +++ b/.github/actions/spell-check/whitelist/whitelist.txt @@ -663,7 +663,6 @@ DPICHANGE DPICHANGED dpix dpiy -draggable DRAWFRAME DRAWITEM DRAWITEMSTRUCT @@ -1051,7 +1050,6 @@ HPROPSHEETPAGE HREDRAW HREF hresult -hrgn HRSRC hscroll hsl @@ -1134,7 +1132,6 @@ IIo IList ime Imm -Impl implementingtextandtextrange inbox inclusivity @@ -2868,7 +2865,6 @@ xvalue XVIRTUALSCREEN XWalk xxxx -XY yact YAML YCast From f343e2476c5308df4fa4eba89d8969285c149ec6 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Wed, 22 Apr 2020 22:18:49 -0700 Subject: [PATCH 13/17] Clean up * Don't recreate the window every time; it's possible to use SetWindowPos and show/hide it on-demand * WIL-ize and document a couple things * make the class registration a static inline * make class reg/creation happen as part of MakeWindow * remove the entire tabview color key (let it fall back to the actual color specified in MUX) --- .../actions/spell-check/dictionary/apis.txt | 6 +- ...759587195744a3a2e01e2be4ee44ef1f6ab100.txt | 6 - .../spell-check/whitelist/whitelist.txt | 2 - dep/gsl | 2 +- src/cascadia/WindowsTerminal/IslandWindow.h | 2 +- .../WindowsTerminal/NonClientIslandWindow.cpp | 141 +++++++++--------- .../WindowsTerminal/NonClientIslandWindow.h | 5 +- 7 files changed, 81 insertions(+), 83 deletions(-) delete mode 100644 .github/actions/spell-check/whitelist/c3759587195744a3a2e01e2be4ee44ef1f6ab100.txt diff --git a/.github/actions/spell-check/dictionary/apis.txt b/.github/actions/spell-check/dictionary/apis.txt index cfe14b168cf..1f6a441001b 100644 --- a/.github/actions/spell-check/dictionary/apis.txt +++ b/.github/actions/spell-check/dictionary/apis.txt @@ -1,6 +1,10 @@ -IMap ICustom +IMap IObject LCID NCHITTEST +NCLBUTTONDBLCLK +NCRBUTTONDBLCLK +NOREDIRECTIONBITMAP rfind +SIZENS diff --git a/.github/actions/spell-check/whitelist/c3759587195744a3a2e01e2be4ee44ef1f6ab100.txt b/.github/actions/spell-check/whitelist/c3759587195744a3a2e01e2be4ee44ef1f6ab100.txt deleted file mode 100644 index 72be933dc0e..00000000000 --- a/.github/actions/spell-check/whitelist/c3759587195744a3a2e01e2be4ee44ef1f6ab100.txt +++ /dev/null @@ -1,6 +0,0 @@ -impl -NCLBUTTONDBLCLK -NCRBUTTONDBLCLK -NOREDIRECTIONBITMAP -SIZENS -xy diff --git a/.github/actions/spell-check/whitelist/whitelist.txt b/.github/actions/spell-check/whitelist/whitelist.txt index b575c15dea1..d622624a21e 100644 --- a/.github/actions/spell-check/whitelist/whitelist.txt +++ b/.github/actions/spell-check/whitelist/whitelist.txt @@ -1106,7 +1106,6 @@ ime Imm IMouse impl -implementingtextandtextrange inbox inclusivity INCONTEXT @@ -2815,7 +2814,6 @@ xvalue XVIRTUALSCREEN XWalk xy -xxxx yact YAML YCast diff --git a/dep/gsl b/dep/gsl index 1212beae777..7e99e76c976 160000 --- a/dep/gsl +++ b/dep/gsl @@ -1 +1 @@ -Subproject commit 1212beae777dba02c230ece8c0c0ec12790047ea +Subproject commit 7e99e76c9761d0d0b0848b91f8648830670ee872 diff --git a/src/cascadia/WindowsTerminal/IslandWindow.h b/src/cascadia/WindowsTerminal/IslandWindow.h index 4e3c96aeb14..997ef3bc27f 100644 --- a/src/cascadia/WindowsTerminal/IslandWindow.h +++ b/src/cascadia/WindowsTerminal/IslandWindow.h @@ -17,7 +17,7 @@ class IslandWindow : IslandWindow() noexcept; virtual ~IslandWindow() override; - void MakeWindow() noexcept; + virtual void MakeWindow() noexcept; void Close(); virtual void OnSize(const UINT width, const UINT height); diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp index 4ac1c84d8da..cd37bd8b54d 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp @@ -8,8 +8,6 @@ #include "../types/inc/utils.hpp" #include "TerminalThemeHelpers.h" -extern "C" IMAGE_DOS_HEADER __ImageBase; - using namespace winrt::Windows::UI; using namespace winrt::Windows::UI::Composition; using namespace winrt::Windows::UI::Xaml; @@ -20,8 +18,6 @@ using namespace ::Microsoft::Console::Types; static constexpr int AutohideTaskbarSize = 2; -ATOM NonClientIslandWindow::_dragBarWindowClass = 0; - NonClientIslandWindow::NonClientIslandWindow(const ElementTheme& requestedTheme) noexcept : IslandWindow{}, _backgroundBrushColor{ RGB(0, 0, 0) }, @@ -34,6 +30,45 @@ NonClientIslandWindow::~NonClientIslandWindow() { } +static constexpr const wchar_t* dragBarClassName{ L"DRAG_BAR_WINDOW_CLASS" }; +void NonClientIslandWindow::MakeWindow() noexcept +{ + IslandWindow::MakeWindow(); + + static ATOM dragBarWindowClass{ []() { + WNDCLASSEX wcex{}; + wcex.cbSize = sizeof(wcex); + wcex.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; + wcex.lpszClassName = dragBarClassName; + wcex.hbrBackground = reinterpret_cast(GetStockObject(BLACK_BRUSH)); + wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); + wcex.lpfnWndProc = _DragWindowWndProc; + wcex.hInstance = wil::GetModuleInstanceHandle(); + wcex.cbWndExtra = sizeof(NonClientIslandWindow*); + return RegisterClassEx(&wcex); + }() }; + + // The drag bar window is a child window of the top level window that is put + // right on top of the drag bar. The XAML island window "steals" our mouse + // messages which makes it hard to implement a custom drag area. By putting + // a window on top of it, we prevent it from "stealing" the mouse messages. + _dragBarWindow.reset(CreateWindowExW(WS_EX_LAYERED | WS_EX_NOREDIRECTIONBITMAP, + dragBarClassName, + L"", + WS_CHILD, + 0, + 0, + 0, + 0, + GetWindowHandle(), + nullptr, + wil::GetModuleInstanceHandle(), + 0)); + THROW_HR_IF_NULL(E_UNEXPECTED, _dragBarWindow); +} + +// Function Description: +// - The window procedure for the drag bar forwards clicks on its client area to its parent as non-client clicks. LRESULT __stdcall NonClientIslandWindow::_DragWindowWndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { std::optional nonClientMessage{ std::nullopt }; @@ -42,22 +77,22 @@ LRESULT __stdcall NonClientIslandWindow::_DragWindowWndProc(HWND const window, U switch (message) { case WM_LBUTTONDOWN: - nonClientMessage = { WM_NCLBUTTONDOWN }; + nonClientMessage = WM_NCLBUTTONDOWN; break; case WM_LBUTTONDBLCLK: - nonClientMessage = { WM_NCLBUTTONDBLCLK }; + nonClientMessage = WM_NCLBUTTONDBLCLK; break; case WM_LBUTTONUP: - nonClientMessage = { WM_NCLBUTTONUP }; + nonClientMessage = WM_NCLBUTTONUP; break; case WM_RBUTTONDOWN: - nonClientMessage = { WM_NCRBUTTONDOWN }; + nonClientMessage = WM_NCRBUTTONDOWN; break; case WM_RBUTTONDBLCLK: - nonClientMessage = { WM_NCRBUTTONDBLCLK }; + nonClientMessage = WM_NCRBUTTONDBLCLK; break; case WM_RBUTTONUP: - nonClientMessage = { WM_NCRBUTTONUP }; + nonClientMessage = WM_NCRBUTTONUP; break; } @@ -68,11 +103,12 @@ LRESULT __stdcall NonClientIslandWindow::_DragWindowWndProc(HWND const window, U POINT screenPt = clientPt; if (ClientToScreen(window, &screenPt)) { - const auto parentWindow = GetAncestor(window, GA_PARENT); - WINRT_ASSERT(parentWindow != NULL); - - LRESULT hitTest = SendMessage(parentWindow, WM_NCHITTEST, 0, MAKELPARAM(screenPt.x, screenPt.y)); + const auto parentWindow{ GetAncestor(window, GA_PARENT) }; + THROW_HR_IF_NULL(E_UNEXPECTED, parentWindow); + // Hit test the parent window at the screen coordinates the user clicked in the drag input sink window, + // then pass that click through as an NC click in that location. + LRESULT hitTest{ SendMessage(parentWindow, WM_NCHITTEST, 0, MAKELPARAM(screenPt.x, screenPt.y)) }; SendMessage(parentWindow, nonClientMessage.value(), hitTest, 0); return 0; @@ -83,61 +119,25 @@ LRESULT __stdcall NonClientIslandWindow::_DragWindowWndProc(HWND const window, U } // Method Description: -// - Create/re-creates the drag bar window. -// - The drag bar window is a child window of the top level window that is put -// right on top of the drag bar. The XAML island window "steals" our mouse -// messages which makes it hard to implement a custom drag area. By putting -// a window on top of it, we prevent it from "stealing" the mouse messages. -// - We have to recreate it, we can't just move it. Otherwise for some reason -// it doesn't get any window message on the new resized area. -// Arguments: -// - -// Return Value: -// - -void NonClientIslandWindow::_RecreateDragBarWindow() noexcept +// - Resizes and shows/hides the drag bar input sink window. +// This window is used to capture clicks on the non-client area. +void NonClientIslandWindow::_ResizeDragBarWindow() noexcept { - constexpr const wchar_t* className = L"DRAG_BAR_WINDOW_CLASS"; - - if (_dragBarWindowClass == 0) + const til::rectangle rect{ _GetDragAreaRect() }; + if (_IsTitlebarVisible() && rect.size().area() > 0) { - WNDCLASS wc{}; - wc.hCursor = LoadCursor(nullptr, IDC_ARROW); - wc.hInstance = reinterpret_cast(&__ImageBase); - wc.lpszClassName = className; - wc.style = CS_DBLCLKS; - wc.lpfnWndProc = _DragWindowWndProc; - _dragBarWindowClass = RegisterClass(&wc); - WINRT_ASSERT(_dragBarWindowClass != 0); + SetWindowPos(_dragBarWindow.get(), + HWND_TOP, + rect.left(), + rect.top() + _GetTopBorderHeight(), + rect.width(), + rect.height(), + SWP_NOACTIVATE | SWP_SHOWWINDOW); + SetLayeredWindowAttributes(_dragBarWindow.get(), 0, 255, LWA_ALPHA); } - - const auto dragBarRect = _GetDragAreaRect(); - - // delete previous window - _dragBarWindow = nullptr; - - if (dragBarRect.right - dragBarRect.left > 0 && - dragBarRect.bottom - dragBarRect.top > 0) + else { - // WS_EX_LAYERED is required. If it is not present, then for - // some reason, the window will not receive any mouse input. - const auto ret = CreateWindowEx(WS_EX_LAYERED | WS_EX_NOREDIRECTIONBITMAP, - className, - L"", - WS_CHILD | WS_VISIBLE | WS_CLIPSIBLINGS, - dragBarRect.left, - dragBarRect.top + _GetTopBorderHeight(), - dragBarRect.right - dragBarRect.left, - dragBarRect.bottom - dragBarRect.top, - GetWindowHandle(), - nullptr, - reinterpret_cast(&__ImageBase), - 0); - WINRT_ASSERT(ret != NULL); - - _dragBarWindow = wil::unique_hwnd(ret); - - // bring it on top of the XAML Islands window - WINRT_ASSERT(SetWindowPos(_dragBarWindow.get(), HWND_TOP, 0, 0, 0, 0, SWP_NOSIZE | SWP_NOMOVE | SWP_NOACTIVATE | SWP_NOOWNERZORDER | SWP_NOREDRAW)); + SetWindowPos(_dragBarWindow.get(), HWND_BOTTOM, 0, 0, 0, 0, SWP_HIDEWINDOW | SWP_NOMOVE | SWP_NOSIZE); } } @@ -152,7 +152,7 @@ void NonClientIslandWindow::_RecreateDragBarWindow() noexcept void NonClientIslandWindow::_OnDragBarSizeChanged(winrt::Windows::Foundation::IInspectable /*sender*/, winrt::Windows::UI::Xaml::SizeChangedEventArgs /*eventArgs*/) { - _RecreateDragBarWindow(); + _ResizeDragBarWindow(); } void NonClientIslandWindow::OnAppInitialized() @@ -356,7 +356,7 @@ void NonClientIslandWindow::_UpdateIslandPosition(const UINT windowWidth, const // NonClientIslandWindow::OnDragBarSizeChanged method because this // method is only called when the position of the drag bar changes // **inside** the island which is not the case here. - _RecreateDragBarWindow(); + _ResizeDragBarWindow(); _oldIslandPos = { newIslandPos }; } @@ -532,6 +532,9 @@ int NonClientIslandWindow::_GetResizeHandleHeight() const noexcept return HTCAPTION; } +// Method Description: +// - Sets the cursor to the sizing cursor when we hit-test the top sizing border. +// We need to do this because we've covered it up with a child window. [[nodiscard]] LRESULT NonClientIslandWindow::_OnSetCursor(WPARAM wParam, LPARAM lParam) const noexcept { if (LOWORD(lParam) == HTCLIENT) @@ -659,7 +662,7 @@ void NonClientIslandWindow::_UpdateFrameMargins() const noexcept case WM_DISPLAYCHANGE: // GH#4166: When the DPI of the monitor changes out from underneath us, // resize our drag bar, to reflect its newly scaled size. - _RecreateDragBarWindow(); + _ResizeDragBarWindow(); return 0; case WM_NCCALCSIZE: return _OnNcCalcSize(wParam, lParam); @@ -820,7 +823,7 @@ void NonClientIslandWindow::_SetIsFullscreen(const bool fullscreenEnabled) // always get another window message to trigger us to remove the drag bar. // So, make sure to update the size of the drag region here, so that it // _definitely_ goes away. - _RecreateDragBarWindow(); + _ResizeDragBarWindow(); } // Method Description: diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h index db213a802df..4fa733d9384 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h @@ -32,6 +32,7 @@ class NonClientIslandWindow : public IslandWindow NonClientIslandWindow(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme) noexcept; virtual ~NonClientIslandWindow() override; + void MakeWindow() noexcept override; virtual void OnSize(const UINT width, const UINT height) override; [[nodiscard]] virtual LRESULT MessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept override; @@ -46,8 +47,6 @@ class NonClientIslandWindow : public IslandWindow void OnApplicationThemeChanged(const winrt::Windows::UI::Xaml::ElementTheme& requestedTheme) override; private: - static ATOM _dragBarWindowClass; - std::optional _oldIslandPos; winrt::TerminalApp::TitlebarControl _titlebar{ nullptr }; @@ -65,7 +64,7 @@ class NonClientIslandWindow : public IslandWindow [[nodiscard]] static LRESULT __stdcall _DragWindowWndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; - void _RecreateDragBarWindow() noexcept; + void _ResizeDragBarWindow() noexcept; int _GetResizeHandleHeight() const noexcept; RECT _GetDragAreaRect() const noexcept; From 77c7f2f52eae592322e62f559a06a18cfd6aa140 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Wed, 22 Apr 2020 22:33:00 -0700 Subject: [PATCH 14/17] wxec->wcEx (spelling) --- .../WindowsTerminal/NonClientIslandWindow.cpp | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp index cd37bd8b54d..6f75d62eed0 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp @@ -36,16 +36,16 @@ void NonClientIslandWindow::MakeWindow() noexcept IslandWindow::MakeWindow(); static ATOM dragBarWindowClass{ []() { - WNDCLASSEX wcex{}; - wcex.cbSize = sizeof(wcex); - wcex.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; - wcex.lpszClassName = dragBarClassName; - wcex.hbrBackground = reinterpret_cast(GetStockObject(BLACK_BRUSH)); - wcex.hCursor = LoadCursor(nullptr, IDC_ARROW); - wcex.lpfnWndProc = _DragWindowWndProc; - wcex.hInstance = wil::GetModuleInstanceHandle(); - wcex.cbWndExtra = sizeof(NonClientIslandWindow*); - return RegisterClassEx(&wcex); + WNDCLASSEX wcEx{}; + wcEx.cbSize = sizeof(wcEx); + wcEx.style = CS_HREDRAW | CS_VREDRAW | CS_DBLCLKS; + wcEx.lpszClassName = dragBarClassName; + wcEx.hbrBackground = reinterpret_cast(GetStockObject(BLACK_BRUSH)); + wcEx.hCursor = LoadCursor(nullptr, IDC_ARROW); + wcEx.lpfnWndProc = _DragWindowWndProc; + wcEx.hInstance = wil::GetModuleInstanceHandle(); + wcEx.cbWndExtra = sizeof(NonClientIslandWindow*); + return RegisterClassEx(&wcEx); }() }; // The drag bar window is a child window of the top level window that is put From 13b7bb55a93d93705b20e7b16e520eee18a60d47 Mon Sep 17 00:00:00 2001 From: "Dustin L. Howett" Date: Wed, 22 Apr 2020 22:36:08 -0700 Subject: [PATCH 15/17] remove from allowlist words that were region-specific --- .github/actions/spell-check/whitelist/whitelist.txt | 2 -- 1 file changed, 2 deletions(-) diff --git a/.github/actions/spell-check/whitelist/whitelist.txt b/.github/actions/spell-check/whitelist/whitelist.txt index d622624a21e..0dd7b3477bb 100644 --- a/.github/actions/spell-check/whitelist/whitelist.txt +++ b/.github/actions/spell-check/whitelist/whitelist.txt @@ -780,7 +780,6 @@ fdc fdd fde fdopen -fdpi fdw fea fesb @@ -1487,7 +1486,6 @@ nbsp Nc NCCALCSIZE NCCREATE -NCHITTEST'ed NCLBUTTONDOWN NCLBUTTONUP NCMBUTTONDOWN From 0c183c906505bf0589c0fad0ec7ea6b8ff0a5ada Mon Sep 17 00:00:00 2001 From: Dustin Howett Date: Thu, 23 Apr 2020 11:42:39 -0700 Subject: [PATCH 16/17] make the input sink wndproc a member --- .../WindowsTerminal/NonClientIslandWindow.cpp | 35 ++++++++++++++----- .../WindowsTerminal/NonClientIslandWindow.h | 3 +- 2 files changed, 29 insertions(+), 9 deletions(-) diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp index 6f75d62eed0..18a5e347112 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp @@ -31,6 +31,26 @@ NonClientIslandWindow::~NonClientIslandWindow() } static constexpr const wchar_t* dragBarClassName{ L"DRAG_BAR_WINDOW_CLASS" }; + +[[nodiscard]] LRESULT __stdcall NonClientIslandWindow::_StaticInputSinkWndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept +{ + WINRT_ASSERT(window); + + if (WM_NCCREATE == message) + { + auto cs = reinterpret_cast(lparam); + auto nonClientIslandWindow{ reinterpret_cast(cs->lpCreateParams) }; + SetWindowLongPtr(window, GWLP_USERDATA, reinterpret_cast(nonClientIslandWindow)); + // fall through to default window procedure + } + else if (auto nonClientIslandWindow{ reinterpret_cast(GetWindowLongPtr(window, GWLP_USERDATA)) }) + { + return nonClientIslandWindow->_InputSinkMessageHandler(message, wparam, lparam); + } + + return DefWindowProc(window, message, wparam, lparam); +} + void NonClientIslandWindow::MakeWindow() noexcept { IslandWindow::MakeWindow(); @@ -42,7 +62,7 @@ void NonClientIslandWindow::MakeWindow() noexcept wcEx.lpszClassName = dragBarClassName; wcEx.hbrBackground = reinterpret_cast(GetStockObject(BLACK_BRUSH)); wcEx.hCursor = LoadCursor(nullptr, IDC_ARROW); - wcEx.lpfnWndProc = _DragWindowWndProc; + wcEx.lpfnWndProc = &NonClientIslandWindow::_StaticInputSinkWndProc; wcEx.hInstance = wil::GetModuleInstanceHandle(); wcEx.cbWndExtra = sizeof(NonClientIslandWindow*); return RegisterClassEx(&wcEx); @@ -63,13 +83,13 @@ void NonClientIslandWindow::MakeWindow() noexcept GetWindowHandle(), nullptr, wil::GetModuleInstanceHandle(), - 0)); + this)); THROW_HR_IF_NULL(E_UNEXPECTED, _dragBarWindow); } // Function Description: // - The window procedure for the drag bar forwards clicks on its client area to its parent as non-client clicks. -LRESULT __stdcall NonClientIslandWindow::_DragWindowWndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept +LRESULT __stdcall NonClientIslandWindow::_InputSinkMessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept { std::optional nonClientMessage{ std::nullopt }; @@ -98,13 +118,12 @@ LRESULT __stdcall NonClientIslandWindow::_DragWindowWndProc(HWND const window, U if (nonClientMessage.has_value()) { - POINT clientPt = { GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam) }; + POINT clientPt{ GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam) }; POINT screenPt = clientPt; - if (ClientToScreen(window, &screenPt)) + if (ClientToScreen(_dragBarWindow.get(), &screenPt)) { - const auto parentWindow{ GetAncestor(window, GA_PARENT) }; - THROW_HR_IF_NULL(E_UNEXPECTED, parentWindow); + auto parentWindow{ _window.get() }; // Hit test the parent window at the screen coordinates the user clicked in the drag input sink window, // then pass that click through as an NC click in that location. @@ -115,7 +134,7 @@ LRESULT __stdcall NonClientIslandWindow::_DragWindowWndProc(HWND const window, U } } - return DefWindowProc(window, message, wparam, lparam); + return DefWindowProc(_dragBarWindow.get(), message, wparam, lparam); } // Method Description: diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h index 4fa733d9384..f14b4ab38c2 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.h +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.h @@ -62,7 +62,8 @@ class NonClientIslandWindow : public IslandWindow bool _isMaximized; - [[nodiscard]] static LRESULT __stdcall _DragWindowWndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; + [[nodiscard]] static LRESULT __stdcall _StaticInputSinkWndProc(HWND const window, UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; + [[nodiscard]] LRESULT _InputSinkMessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept; void _ResizeDragBarWindow() noexcept; From 9b40895ca53af597d16bda344e9509de2bf36305 Mon Sep 17 00:00:00 2001 From: Dustin Howett Date: Fri, 24 Apr 2020 10:15:24 -0700 Subject: [PATCH 17/17] fix CR --- .../WindowsTerminal/NonClientIslandWindow.cpp | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp index 18a5e347112..40212e0926d 100644 --- a/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp +++ b/src/cascadia/WindowsTerminal/NonClientIslandWindow.cpp @@ -118,16 +118,15 @@ LRESULT __stdcall NonClientIslandWindow::_InputSinkMessageHandler(UINT const mes if (nonClientMessage.has_value()) { - POINT clientPt{ GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam) }; - - POINT screenPt = clientPt; + const POINT clientPt{ GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam) }; + POINT screenPt{ clientPt }; if (ClientToScreen(_dragBarWindow.get(), &screenPt)) { - auto parentWindow{ _window.get() }; + auto parentWindow{ GetWindowHandle() }; // Hit test the parent window at the screen coordinates the user clicked in the drag input sink window, // then pass that click through as an NC click in that location. - LRESULT hitTest{ SendMessage(parentWindow, WM_NCHITTEST, 0, MAKELPARAM(screenPt.x, screenPt.y)) }; + const LRESULT hitTest{ SendMessage(parentWindow, WM_NCHITTEST, 0, MAKELPARAM(screenPt.x, screenPt.y)) }; SendMessage(parentWindow, nonClientMessage.value(), hitTest, 0); return 0; @@ -564,10 +563,8 @@ int NonClientIslandWindow::_GetResizeHandleHeight() const noexcept // we still want to get the cursor position that was associated // with that message at the time it was sent to handle the message // correctly. - const auto screenPtDword = GetMessagePos(); - POINT screenPt = { GET_X_LPARAM(screenPtDword), GET_Y_LPARAM(screenPtDword) }; - - LRESULT hitTest = SendMessage(GetWindowHandle(), WM_NCHITTEST, 0, MAKELPARAM(screenPt.x, screenPt.y)); + const auto screenPtLparam{ GetMessagePos() }; + const LRESULT hitTest{ SendMessage(GetWindowHandle(), WM_NCHITTEST, 0, screenPtLparam) }; if (hitTest == HTTOP) { // We have to set the vertical resize cursor manually on