11680 SNAP LAYOUT

This commit is contained in:
Dustin Howett 2021-11-17 17:54:50 -06:00
commit 52d1e788f7
11 changed files with 460 additions and 59 deletions

View File

@ -43,12 +43,14 @@ fullkbd
futex
GETDESKWALLPAPER
GETHIGHCONTRAST
GETMOUSEHOVERTIME
Hashtable
HIGHCONTRASTON
HIGHCONTRASTW
hotkeys
href
hrgn
HTCLOSE
IActivation
IApp
IAppearance
@ -93,11 +95,14 @@ MENUINFO
MENUITEMINFOW
memicmp
mptt
MOUSELEAVE
mov
msappx
MULTIPLEUSE
NCHITTEST
NCLBUTTONDBLCLK
NCMOUSELEAVE
NCMOUSEMOVE
NCRBUTTONDBLCLK
NIF
NIN
@ -163,9 +168,11 @@ TASKBARCREATED
TBPF
THEMECHANGED
tlg
TME
tmp
tolower
toupper
TRACKMOUSEEVENT
TTask
TVal
UChar

View File

@ -1,25 +1,67 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
// MinMaxCloseControl.xaml.cpp
// Implementation of the MinMaxCloseControl class
//
#include "pch.h"
#include "MinMaxCloseControl.h"
#include "MinMaxCloseControl.g.cpp"
#include <LibraryResources.h>
using namespace winrt::Windows::UI::Xaml;
namespace winrt::TerminalApp::implementation
{
static void closeToolTipForButton(const Controls::Button& button)
{
if (auto tt{ Controls::ToolTipService::GetToolTip(button) })
{
if (auto tooltip{ tt.try_as<Controls::ToolTip>() })
{
tooltip.IsOpen(false);
}
}
}
MinMaxCloseControl::MinMaxCloseControl()
{
// Get our dispatcher. This will get us the same dispatcher as
// Dispatcher(), but it's a DispatcherQueue, so we can use it with
// ThrottledFunc
auto dispatcher = winrt::Windows::System::DispatcherQueue::GetForCurrentThread();
InitializeComponent();
// Get the tooltip hover time from the system, or default to 400ms
// (which should be the default, see:
// https://docs.microsoft.com/en-us/windows/win32/api/winuser/ns-winuser-trackmouseevent#remarks)
unsigned int hoverTimeoutMillis{ 400 };
LOG_IF_WIN32_BOOL_FALSE(SystemParametersInfoW(SPI_GETMOUSEHOVERTIME, 0, &hoverTimeoutMillis, 0));
const auto toolTipInterval = std::chrono::milliseconds(hoverTimeoutMillis);
// Create a ThrottledFunc for opening the tooltip after the hover
// timeout. If we hover another button, we should make sure to call
// Run() with the new button. Calling `_displayToolTip.Run(nullptr)`,
// which will cause us to not display a tooltip, which is used when we
// leave the control entirely.
_displayToolTip = std::make_shared<ThrottledFuncTrailing<Controls::Button>>(
dispatcher,
toolTipInterval,
[weakThis = get_weak()](Controls::Button button) {
// If we provide a button, then open the tooltip on that button.
// We can "dismiss" this throttled func by calling it with null,
// which will cause us to do nothing at the end of the timeout
// instead.
if (button)
{
if (auto tt{ Controls::ToolTipService::GetToolTip(button) })
{
if (auto tooltip{ tt.try_as<Controls::ToolTip>() })
{
tooltip.IsOpen(true);
}
}
}
});
}
// These event handlers simply forward each buttons click events up to the
@ -95,4 +137,104 @@ namespace winrt::TerminalApp::implementation
break;
}
}
// Method Description:
// - Called when the mouse hovers a button.
// - Transition that button to `PointerOver`
// - run the throttled func with this button, to display the tooltip after
// a timeout
// - dismiss any open tooltips on other buttons.
// Arguments:
// - button: the button that was hovered
void MinMaxCloseControl::HoverButton(CaptionButton button)
{
// Keep track of the button that's been pressed. we get a mouse move
// message when we open the tooltip. If we move the mouse on top of this
// button, that we've already pressed, then no need to move to the
// "hovered" state, we should stay in the pressed state.
if (_lastPressedButton && _lastPressedButton.value() == button)
{
return;
}
switch (button)
{
// Make sure to use true for the useTransitions parameter, to
// animate the fade in/out transition between colors.
case CaptionButton::Minimize:
VisualStateManager::GoToState(MinimizeButton(), L"PointerOver", true);
VisualStateManager::GoToState(MaximizeButton(), L"Normal", true);
VisualStateManager::GoToState(CloseButton(), L"Normal", true);
_displayToolTip->Run(MinimizeButton());
closeToolTipForButton(MaximizeButton());
closeToolTipForButton(CloseButton());
break;
case CaptionButton::Maximize:
VisualStateManager::GoToState(MinimizeButton(), L"Normal", true);
VisualStateManager::GoToState(MaximizeButton(), L"PointerOver", true);
VisualStateManager::GoToState(CloseButton(), L"Normal", true);
closeToolTipForButton(MinimizeButton());
_displayToolTip->Run(MaximizeButton());
closeToolTipForButton(CloseButton());
break;
case CaptionButton::Close:
VisualStateManager::GoToState(MinimizeButton(), L"Normal", true);
VisualStateManager::GoToState(MaximizeButton(), L"Normal", true);
VisualStateManager::GoToState(CloseButton(), L"PointerOver", true);
closeToolTipForButton(MinimizeButton());
closeToolTipForButton(MaximizeButton());
_displayToolTip->Run(CloseButton());
break;
}
}
// Method Description:
// - Called when the mouse presses down on a button. NOT when it is
// released. That's handled one level above, in
// TitleBarControl::ReleaseButtons
// - Transition that button to `Pressed`
// Arguments:
// - button: the button that was pressed
void MinMaxCloseControl::PressButton(CaptionButton button)
{
switch (button)
{
case CaptionButton::Minimize:
VisualStateManager::GoToState(MinimizeButton(), L"Pressed", true);
VisualStateManager::GoToState(MaximizeButton(), L"Normal", true);
VisualStateManager::GoToState(CloseButton(), L"Normal", true);
break;
case CaptionButton::Maximize:
VisualStateManager::GoToState(MinimizeButton(), L"Normal", true);
VisualStateManager::GoToState(MaximizeButton(), L"Pressed", true);
VisualStateManager::GoToState(CloseButton(), L"Normal", true);
break;
case CaptionButton::Close:
VisualStateManager::GoToState(MinimizeButton(), L"Normal", true);
VisualStateManager::GoToState(MaximizeButton(), L"Normal", true);
VisualStateManager::GoToState(CloseButton(), L"Pressed", true);
break;
}
_lastPressedButton = button;
}
// Method Description:
// - Called when buttons are no longer hovered or pressed. Return them all
// to the normal state, and dismiss the tooltips.
void MinMaxCloseControl::ReleaseButtons()
{
_displayToolTip->Run(nullptr);
VisualStateManager::GoToState(MinimizeButton(), L"Normal", true);
VisualStateManager::GoToState(MaximizeButton(), L"Normal", true);
VisualStateManager::GoToState(CloseButton(), L"Normal", true);
closeToolTipForButton(MinimizeButton());
closeToolTipForButton(MaximizeButton());
closeToolTipForButton(CloseButton());
_lastPressedButton = std::nullopt;
}
}

View File

@ -6,11 +6,9 @@
#pragma once
#include "winrt/Windows.UI.Xaml.h"
#include "winrt/Windows.UI.Xaml.Markup.h"
#include "winrt/Windows.UI.Xaml.Interop.h"
#include "MinMaxCloseControl.g.h"
#include "../../cascadia/inc/cppwinrt_utils.h"
#include <ThrottledFunc.h>
namespace winrt::TerminalApp::implementation
{
@ -20,6 +18,10 @@ namespace winrt::TerminalApp::implementation
void SetWindowVisualState(WindowVisualState visualState);
void HoverButton(CaptionButton button);
void PressButton(CaptionButton button);
void ReleaseButtons();
void _MinimizeClick(winrt::Windows::Foundation::IInspectable const& sender,
winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
void _MaximizeClick(winrt::Windows::Foundation::IInspectable const& sender,
@ -30,6 +32,9 @@ namespace winrt::TerminalApp::implementation
TYPED_EVENT(MinimizeClick, TerminalApp::MinMaxCloseControl, winrt::Windows::UI::Xaml::RoutedEventArgs);
TYPED_EVENT(MaximizeClick, TerminalApp::MinMaxCloseControl, winrt::Windows::UI::Xaml::RoutedEventArgs);
TYPED_EVENT(CloseClick, TerminalApp::MinMaxCloseControl, winrt::Windows::UI::Xaml::RoutedEventArgs);
std::shared_ptr<ThrottledFuncTrailing<winrt::Windows::UI::Xaml::Controls::Button>> _displayToolTip{ nullptr };
std::optional<CaptionButton> _lastPressedButton{ std::nullopt };
};
}

View File

@ -11,6 +11,10 @@ namespace TerminalApp
void SetWindowVisualState(WindowVisualState visualState);
void HoverButton(CaptionButton button);
void PressButton(CaptionButton button);
void ReleaseButtons();
event Windows.Foundation.TypedEventHandler<MinMaxCloseControl, Windows.UI.Xaml.RoutedEventArgs> MinimizeClick;
event Windows.Foundation.TypedEventHandler<MinMaxCloseControl, Windows.UI.Xaml.RoutedEventArgs> MaximizeClick;
event Windows.Foundation.TypedEventHandler<MinMaxCloseControl, Windows.UI.Xaml.RoutedEventArgs> CloseClick;

View File

@ -220,7 +220,7 @@
</StackPanel.Resources>
<Button x:Name="MinimizeButton"
x:Uid="WindowMinimizeButton"
x:Uid="MinimizeButton"
Width="46.0"
Height="{StaticResource CaptionButtonHeightWindowed}"
MinWidth="46.0"
@ -232,9 +232,14 @@
<x:String x:Key="CaptionButtonPath">M 0 0 H 10</x:String>
</ResourceDictionary>
</Button.Resources>
<ToolTipService.ToolTip>
<ToolTip>
<TextBlock x:Uid="WindowMinimizeButtonToolTip" />
</ToolTip>
</ToolTipService.ToolTip>
</Button>
<Button x:Name="MaximizeButton"
x:Uid="WindowMaximizeButton"
x:Uid="MaximizeButton"
Width="46.0"
Height="{StaticResource CaptionButtonHeightWindowed}"
MinWidth="46.0"
@ -256,7 +261,7 @@
</ToolTipService.ToolTip>
</Button>
<Button x:Name="CloseButton"
x:Uid="WindowCloseButton"
x:Uid="CloseButton"
Width="46.0"
Height="{StaticResource CaptionButtonHeightWindowed}"
MinWidth="46.0"
@ -309,5 +314,10 @@
<x:String x:Key="CaptionButtonPath">M 0 0 L 10 10 M 10 0 L 0 10</x:String>
</ResourceDictionary>
</Button.Resources>
<ToolTipService.ToolTip>
<ToolTip>
<TextBlock x:Uid="WindowCloseButtonToolTip" />
</ToolTip>
</ToolTipService.ToolTip>
</Button>
</StackPanel>

View File

@ -411,6 +411,9 @@
<data name="WindowCloseButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>Close</value>
</data>
<data name="WindowCloseButtonToolTip.Text" xml:space="preserve">
<value>Close</value>
</data>
<data name="WindowMaximizeButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
<value>Maximize</value>
</data>
@ -420,6 +423,9 @@
<data name="WindowMinimizeButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
<value>Minimize</value>
</data>
<data name="WindowMinimizeButtonToolTip.Text" xml:space="preserve">
<value>Minimize</value>
</data>
<data name="AboutDialog.Title" xml:space="preserve">
<value>About</value>
</data>

View File

@ -1,7 +1,6 @@
// Copyright (c) Microsoft Corporation.
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
// TitlebarControl.xaml.cpp
// Implementation of the TitlebarControl class
//
@ -24,6 +23,14 @@ namespace winrt::TerminalApp::implementation
MinMaxCloseControl().CloseClick({ this, &TitlebarControl::Close_Click });
}
double TitlebarControl::CaptionButtonWidth()
{
// Divide by three, since we know there are only three buttons. When
// Windows 12 comes along and adds another, we can update this /s
static double width{ MinMaxCloseControl().ActualWidth() / 3.0 };
return width;
}
IInspectable TitlebarControl::Content()
{
return ContentRoot().Content();
@ -93,4 +100,48 @@ namespace winrt::TerminalApp::implementation
{
MinMaxCloseControl().SetWindowVisualState(visualState);
}
// GH#9443: HoverButton, PressButton, ClickButton and ReleaseButtons are all
// used to manually interact with the buttons, in the same way that XAML
// would normally send events.
void TitlebarControl::HoverButton(CaptionButton button)
{
MinMaxCloseControl().HoverButton(button);
}
void TitlebarControl::PressButton(CaptionButton button)
{
MinMaxCloseControl().PressButton(button);
}
winrt::fire_and_forget TitlebarControl::ClickButton(CaptionButton button)
{
// GH#8587: Handle this on the _next_ pass of the UI thread. If we
// handle this immediately, then we'll accidentally leave the button in
// the "Hovered" state when we minimize. This will leave the button
// visibly hovered in the taskbar preview for our window.
auto weakThis{ get_weak() };
co_await MinMaxCloseControl().Dispatcher();
if (auto self{ weakThis.get() })
{
// Just handle this in the same way we would if the button were
// clicked normally.
switch (button)
{
case CaptionButton::Minimize:
Minimize_Click(nullptr, nullptr);
break;
case CaptionButton::Maximize:
Maximize_Click(nullptr, nullptr);
break;
case CaptionButton::Close:
Close_Click(nullptr, nullptr);
break;
}
}
}
void TitlebarControl::ReleaseButtons()
{
MinMaxCloseControl().ReleaseButtons();
}
}

View File

@ -1,14 +1,8 @@
// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
//
// Declaration of the MainUserControl class.
//
#pragma once
#include "winrt/Windows.UI.Xaml.h"
#include "winrt/Windows.UI.Xaml.Markup.h"
#include "winrt/Windows.UI.Xaml.Interop.h"
#include "TitlebarControl.g.h"
namespace winrt::TerminalApp::implementation
@ -17,6 +11,12 @@ namespace winrt::TerminalApp::implementation
{
TitlebarControl(uint64_t handle);
void HoverButton(CaptionButton button);
void PressButton(CaptionButton button);
winrt::fire_and_forget ClickButton(CaptionButton button);
void ReleaseButtons();
double CaptionButtonWidth();
IInspectable Content();
void Content(IInspectable content);

View File

@ -10,11 +10,25 @@ namespace TerminalApp
WindowVisualStateIconified
};
// For simplicity, make sure that these are the same values as the ones used
// by messages like WM_NCHITTEST
enum CaptionButton {
Minimize = 8, // HTMINBUTTON
Maximize = 9, // HTMAXBUTTON
Close = 20 // HTCLOSE
};
[default_interface] runtimeclass TitlebarControl : Windows.UI.Xaml.Controls.Grid
{
TitlebarControl(UInt64 parentWindowHandle);
void SetWindowVisualState(WindowVisualState visualState);
void HoverButton(CaptionButton button);
void PressButton(CaptionButton button);
void ClickButton(CaptionButton button);
void ReleaseButtons();
Double CaptionButtonWidth { get; };
IInspectable Content;
Windows.UI.Xaml.Controls.Border DragBar { get; };
}

View File

@ -87,51 +87,203 @@ void NonClientIslandWindow::MakeWindow() noexcept
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 NonClientIslandWindow::_InputSinkMessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept
LRESULT NonClientIslandWindow::_dragBarNcHitTest(const til::point& pointer)
{
std::optional<UINT> nonClientMessage{ std::nullopt };
RECT rcParent = GetWindowRect();
// The size of the buttons doesn't change over the life of the application.
const auto buttonWidthInDips{ _titlebar.CaptionButtonWidth() };
// translate WM_ messages on the window to WM_NC* on the top level window
// However, the DPI scaling might, so get the updated size of the buttons in pixels
const auto buttonWidthInPixels{ buttonWidthInDips * GetCurrentDpiScale() };
// make sure to account for the width of the window frame!
const til::rectangle nonClientFrame{ GetNonClientFrame(_currentDpi) };
const auto rightBorder{ rcParent.right - nonClientFrame.right<int>() };
// From the right to the left,
// * are we in the close button?
// * the maximize button?
// * the minimize button?
// If we're not, then we're in either the top resize border, or just
// generally in the titlebar.
if ((rightBorder - pointer.x()) < (buttonWidthInPixels))
{
return HTCLOSE;
}
else if ((rightBorder - pointer.x()) < (buttonWidthInPixels * 2))
{
return HTMAXBUTTON;
}
else if ((rightBorder - pointer.x()) < (buttonWidthInPixels * 3))
{
return HTMINBUTTON;
}
else
{
// If we're not on a caption button, then check if we're on the top
// border. If we're not on the top border, then we're just generally in
// the caption area.
const auto resizeBorderHeight = _GetResizeHandleHeight();
const auto isOnResizeBorder = pointer.y() < rcParent.top + resizeBorderHeight;
return isOnResizeBorder ? HTTOP : HTCAPTION;
}
}
// Function Description:
// - The window procedure for the drag bar forwards clicks on its client area to
// its parent as non-client clicks.
// - BODGY: It also _manually_ handles the caption buttons. They exist in the
// titlebar, and work reasonably well with just XAML, if the drag bar isn't
// covering them.
// - However, to get snap layout support (GH#9443), we need to actually return
// HTMAXBUTTON where the maximize button is. If the drag bar doesn't cover the
// caption buttons, then the core input site (which takes up the entirety of
// the XAML island) will steal the WM_NCHITTEST before we get a chance to
// handle it.
// - So, the drag bar covers the caption buttons, and manually handles hovering
// and pressing them when needed. This gives the impression that they're
// getting input as they normally would, even if they're not _really_ getting
// input via XAML.
LRESULT NonClientIslandWindow::_InputSinkMessageHandler(UINT const message,
WPARAM const wparam,
LPARAM const lparam) noexcept
{
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())
case WM_NCHITTEST:
{
const POINT clientPt{ GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam) };
POINT screenPt{ clientPt };
if (ClientToScreen(_dragBarWindow.get(), &screenPt))
// Try to determine what part of the window is being hovered here. This
// is absolutely critical to making sure Snap Layouts (GH#9443) works!
return _dragBarNcHitTest(til::point{ GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam) });
}
break;
case WM_NCMOUSEMOVE:
// When we get this message, it's because the mouse moved when it was
// over somewhere we said was the non-client area.
//
// We'll use this to communicate state to the title bar control, so that
// it can update its visuals.
// - If we're over a button, hover it.
// - If we're over _anything else_, stop hovering the buttons.
switch (wparam)
{
case HTTOP:
case HTCAPTION:
{
_titlebar.ReleaseButtons();
// Pass caption-related nonclient messages to the parent window.
// Make sure to do this for the HTTOP, which is the top resize
// border, so we can resize the window on the top.
auto parentWindow{ GetHandle() };
const LPARAM newLparam = MAKELPARAM(screenPt.x, screenPt.y);
// 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.
const LRESULT hitTest{ SendMessage(parentWindow, WM_NCHITTEST, 0, newLparam) };
SendMessage(parentWindow, nonClientMessage.value(), hitTest, newLparam);
return 0;
return SendMessage(parentWindow, message, wparam, lparam);
}
case HTMINBUTTON:
case HTMAXBUTTON:
case HTCLOSE:
_titlebar.HoverButton(static_cast<winrt::TerminalApp::CaptionButton>(wparam));
break;
default:
_titlebar.ReleaseButtons();
}
// If we haven't previously asked for mouse tracking, request mouse
// tracking. We need to do this so we can get the WM_NCMOUSELEAVE
// message when the mouse leave the titlebar. Otherwise, we won't always
// get that message (especially if the user moves the mouse _real
// fast_).
if (!_trackingMouse &&
(wparam == HTMINBUTTON || wparam == HTMAXBUTTON || wparam == HTCLOSE))
{
TRACKMOUSEEVENT ev{};
ev.cbSize = sizeof(TRACKMOUSEEVENT);
// TME_NONCLIENT is absolutely critical here. In my experimentation,
// we'd get WM_MOUSELEAVE messages after just a HOVER_DEFAULT
// timeout even though we're not requesting TME_HOVER, which kinda
// ruined the whole point of this.
ev.dwFlags = TME_LEAVE | TME_NONCLIENT;
ev.hwndTrack = _dragBarWindow.get();
ev.dwHoverTime = HOVER_DEFAULT; // we don't _really_ care about this.
LOG_IF_WIN32_BOOL_FALSE(TrackMouseEvent(&ev));
_trackingMouse = true;
}
break;
case WM_NCMOUSELEAVE:
case WM_MOUSELEAVE:
// When the mouse leaves the drag rect, make sure to dismiss any hover.
_titlebar.ReleaseButtons();
_trackingMouse = false;
break;
// NB: *Shouldn't be forwarding these* when they're not over the caption
// because they can inadvertently take action using the system's default
// metrics instead of our own.
case WM_NCLBUTTONDOWN:
case WM_NCLBUTTONDBLCLK:
// Manual handling for mouse clicks in the drag bar. If it's in a
// caption button, then tell the titlebar to "press" the button, which
// should change its visual state.
//
// If it's not in a caption button, then just forward the message along
// to the root HWND. Make sure to do this for the HTTOP, which is the
// top resize border.
switch (wparam)
{
case HTTOP:
case HTCAPTION:
{
// Pass caption-related nonclient messages to the parent window.
auto parentWindow{ GetHandle() };
return SendMessage(parentWindow, message, wparam, lparam);
}
// The buttons won't work as you'd expect; we need to handle those
// ourselves.
case HTMINBUTTON:
case HTMAXBUTTON:
case HTCLOSE:
_titlebar.PressButton(static_cast<winrt::TerminalApp::CaptionButton>(wparam));
break;
}
return 0;
case WM_NCLBUTTONUP:
// Manual handling for mouse RELEASES in the drag bar. If it's in a
// caption button, then manually handle what we'd expect for that button.
//
// If it's not in a caption button, then just forward the message along
// to the root HWND.
switch (wparam)
{
case HTTOP:
case HTCAPTION:
{
// Pass caption-related nonclient messages to the parent window.
// The buttons won't work as you'd expect; we need to handle those ourselves.
auto parentWindow{ GetHandle() };
return SendMessage(parentWindow, message, wparam, lparam);
}
break;
// If we do find a button, then tell the titlebar to raise the same
// event that would be raised if it were "tapped"
case HTMINBUTTON:
case HTMAXBUTTON:
case HTCLOSE:
_titlebar.ReleaseButtons();
_titlebar.ClickButton(static_cast<winrt::TerminalApp::CaptionButton>(wparam));
break;
}
return 0;
// Make sure to pass along right-clicks in this region to our parent window
// - we don't need to handle these.
case WM_NCRBUTTONDOWN:
case WM_NCRBUTTONDBLCLK:
case WM_NCRBUTTONUP:
auto parentWindow{ GetHandle() };
return SendMessage(parentWindow, message, wparam, lparam);
}
return DefWindowProc(_dragBarWindow.get(), message, wparam, lparam);
@ -279,10 +431,17 @@ RECT NonClientIslandWindow::_GetDragAreaRect() const noexcept
{
const auto scale = GetCurrentDpiScale();
const auto transform = _dragBar.TransformToVisual(_rootGrid);
// GH#9443: Previously, we'd only extend the drag bar from the left of
// the tabs to the right of the caption buttons. Now, we're extending it
// all the way to the right side of the window, covering the caption
// buttons. We'll manually handle input to those buttons, to make it
// seem like they're still getting XAML input. We do this so we can get
// snap layout support for the maximize button.
const auto logicalDragBarRect = winrt::Windows::Foundation::Rect{
0.0f,
0.0f,
static_cast<float>(_dragBar.ActualWidth()),
static_cast<float>(_rootGrid.ActualWidth()),
static_cast<float>(_dragBar.ActualHeight())
};
const auto clientDragBarRect = transform.TransformBounds(logicalDragBarRect);
@ -550,6 +709,7 @@ int NonClientIslandWindow::_GetResizeHandleHeight() const noexcept
// we didn't change them.
LPARAM lParam = MAKELONG(ptMouse.x, ptMouse.y);
const auto originalRet = DefWindowProc(_window.get(), WM_NCHITTEST, 0, lParam);
if (originalRet != HTCLIENT)
{
// If we're the quake window, suppress resizing on any side except the

View File

@ -62,6 +62,7 @@ private:
winrt::Windows::UI::Xaml::ElementTheme _theme;
bool _isMaximized;
bool _trackingMouse{ false };
[[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;
@ -71,6 +72,7 @@ private:
int _GetResizeHandleHeight() const noexcept;
RECT _GetDragAreaRect() const noexcept;
int _GetTopBorderHeight() const noexcept;
LRESULT _dragBarNcHitTest(const til::point& pointer);
[[nodiscard]] LRESULT _OnNcCreate(WPARAM wParam, LPARAM lParam) noexcept override;
[[nodiscard]] LRESULT _OnNcCalcSize(const WPARAM wParam, const LPARAM lParam) noexcept;