11680 SNAP LAYOUT
This commit is contained in:
commit
52d1e788f7
7
.github/actions/spelling/allow/apis.txt
vendored
7
.github/actions/spelling/allow/apis.txt
vendored
|
@ -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.
|
||||
// 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.
|
||||
// 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;
|
||||
case WM_NCHITTEST:
|
||||
{
|
||||
// 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;
|
||||
|
||||
if (nonClientMessage.has_value())
|
||||
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)
|
||||
{
|
||||
const POINT clientPt{ GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam) };
|
||||
POINT screenPt{ clientPt };
|
||||
if (ClientToScreen(_dragBarWindow.get(), &screenPt))
|
||||
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 a new issue