Add snap-layouts support to the Terminal (#11680)
Adds snap layout support to the Terminal's maximize button. This PR is full of BODGY, so brace yourselves. Big thanks to Chris Swan in #11134 for building the prototype. I don't believe this solves #8795, because XAML islands can't get nchittest messages - 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, 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. - We also need to manually display the button tooltips now, because XAML doesn't know when they've been hovered for long enough. Hence, the `_displayToolTip` `ThrottledFuncTrailing` ## Validation Minimized, maximized, restored down, hovered the buttons slowly, moved the mouse over them quickly, they feel the same as before. But now with snap layouts appearing. ## TODO! * [x] I'm working on getting the ToolTips on the caption buttons back. Alas, I needed a demo of this _today_, so I'll fix that tomorrow morning. * [x] mild concern: I should probably test Win 10 to make sure there wasn't weird changes to the message loop in win11 that means this is broken on win10. * [x] I think I used the wrong issue number for tons of my comments throughout this PR. Double check that. Should be #9443, not #9447. Closes #9443 I thought this took care of #8587 ~as a bonus, because I was here, and the fix is _now_ trivial~, but looking at the latest commit that regressed. Co-authored-by: Chris Swan <chswan@microsoft.com>
This commit is contained in:
parent
a9c2db4770
commit
f2ebb21bd1
|
@ -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
|
||||
|
|
|
@ -1,25 +1,67 @@
|
|||
// 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;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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 };
|
||||
};
|
||||
}
|
||||
|
||||
|
|
|
@ -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;
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
// 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();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -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);
|
||||
|
||||
|
|
|
@ -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; };
|
||||
}
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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;
|
||||
|
|
Loading…
Reference in New Issue