Compare commits
28 commits
main
...
dev/migrie
Author | SHA1 | Date | |
---|---|---|---|
6939ff77cf | |||
174e413f8e | |||
e24f4d5638 | |||
fd2f584019 | |||
15ab87f5d4 | |||
aa9577bfc7 | |||
37c4cbee74 | |||
77975e88f0 | |||
5acbad512e | |||
eebd1a078a | |||
6e2150ba5b | |||
1167e20b0f | |||
01aa26c522 | |||
21ba58ca29 | |||
0fa10cd3ef | |||
953620669d | |||
a00e9edecc | |||
94d6bfbf2d | |||
1915a06f6e | |||
0ef867319d | |||
deb494f6ef | |||
d32fac4d3f | |||
939177bdab | |||
be38330ddf | |||
487edb9071 | |||
6c9b399048 | |||
5fdef429a8 | |||
04145d4fe0 |
7
.github/actions/spelling/allow/apis.txt
vendored
7
.github/actions/spelling/allow/apis.txt
vendored
|
@ -43,12 +43,14 @@ fullkbd
|
||||||
futex
|
futex
|
||||||
GETDESKWALLPAPER
|
GETDESKWALLPAPER
|
||||||
GETHIGHCONTRAST
|
GETHIGHCONTRAST
|
||||||
|
GETMOUSEHOVERTIME
|
||||||
Hashtable
|
Hashtable
|
||||||
HIGHCONTRASTON
|
HIGHCONTRASTON
|
||||||
HIGHCONTRASTW
|
HIGHCONTRASTW
|
||||||
hotkeys
|
hotkeys
|
||||||
href
|
href
|
||||||
hrgn
|
hrgn
|
||||||
|
HTCLOSE
|
||||||
IActivation
|
IActivation
|
||||||
IApp
|
IApp
|
||||||
IAppearance
|
IAppearance
|
||||||
|
@ -93,11 +95,14 @@ MENUINFO
|
||||||
MENUITEMINFOW
|
MENUITEMINFOW
|
||||||
memicmp
|
memicmp
|
||||||
mptt
|
mptt
|
||||||
|
MOUSELEAVE
|
||||||
mov
|
mov
|
||||||
msappx
|
msappx
|
||||||
MULTIPLEUSE
|
MULTIPLEUSE
|
||||||
NCHITTEST
|
NCHITTEST
|
||||||
NCLBUTTONDBLCLK
|
NCLBUTTONDBLCLK
|
||||||
|
NCMOUSELEAVE
|
||||||
|
NCMOUSEMOVE
|
||||||
NCRBUTTONDBLCLK
|
NCRBUTTONDBLCLK
|
||||||
NIF
|
NIF
|
||||||
NIN
|
NIN
|
||||||
|
@ -163,9 +168,11 @@ TASKBARCREATED
|
||||||
TBPF
|
TBPF
|
||||||
THEMECHANGED
|
THEMECHANGED
|
||||||
tlg
|
tlg
|
||||||
|
TME
|
||||||
tmp
|
tmp
|
||||||
tolower
|
tolower
|
||||||
toupper
|
toupper
|
||||||
|
TRACKMOUSEEVENT
|
||||||
TTask
|
TTask
|
||||||
TVal
|
TVal
|
||||||
UChar
|
UChar
|
||||||
|
|
|
@ -1,25 +1,67 @@
|
||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
//
|
|
||||||
// MinMaxCloseControl.xaml.cpp
|
|
||||||
// Implementation of the MinMaxCloseControl class
|
|
||||||
//
|
|
||||||
|
|
||||||
#include "pch.h"
|
#include "pch.h"
|
||||||
|
|
||||||
#include "MinMaxCloseControl.h"
|
#include "MinMaxCloseControl.h"
|
||||||
|
|
||||||
#include "MinMaxCloseControl.g.cpp"
|
#include "MinMaxCloseControl.g.cpp"
|
||||||
|
|
||||||
#include <LibraryResources.h>
|
#include <LibraryResources.h>
|
||||||
|
|
||||||
using namespace winrt::Windows::UI::Xaml;
|
using namespace winrt::Windows::UI::Xaml;
|
||||||
|
|
||||||
namespace winrt::TerminalApp::implementation
|
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()
|
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();
|
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
|
// These event handlers simply forward each buttons click events up to the
|
||||||
|
@ -95,4 +137,104 @@ namespace winrt::TerminalApp::implementation
|
||||||
break;
|
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
|
#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 "MinMaxCloseControl.g.h"
|
||||||
#include "../../cascadia/inc/cppwinrt_utils.h"
|
#include "../../cascadia/inc/cppwinrt_utils.h"
|
||||||
|
#include <ThrottledFunc.h>
|
||||||
|
|
||||||
namespace winrt::TerminalApp::implementation
|
namespace winrt::TerminalApp::implementation
|
||||||
{
|
{
|
||||||
|
@ -20,6 +18,10 @@ namespace winrt::TerminalApp::implementation
|
||||||
|
|
||||||
void SetWindowVisualState(WindowVisualState visualState);
|
void SetWindowVisualState(WindowVisualState visualState);
|
||||||
|
|
||||||
|
void HoverButton(CaptionButton button);
|
||||||
|
void PressButton(CaptionButton button);
|
||||||
|
void ReleaseButtons();
|
||||||
|
|
||||||
void _MinimizeClick(winrt::Windows::Foundation::IInspectable const& sender,
|
void _MinimizeClick(winrt::Windows::Foundation::IInspectable const& sender,
|
||||||
winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
|
winrt::Windows::UI::Xaml::RoutedEventArgs const& e);
|
||||||
void _MaximizeClick(winrt::Windows::Foundation::IInspectable const& sender,
|
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(MinimizeClick, TerminalApp::MinMaxCloseControl, winrt::Windows::UI::Xaml::RoutedEventArgs);
|
||||||
TYPED_EVENT(MaximizeClick, 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);
|
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 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> MinimizeClick;
|
||||||
event Windows.Foundation.TypedEventHandler<MinMaxCloseControl, Windows.UI.Xaml.RoutedEventArgs> MaximizeClick;
|
event Windows.Foundation.TypedEventHandler<MinMaxCloseControl, Windows.UI.Xaml.RoutedEventArgs> MaximizeClick;
|
||||||
event Windows.Foundation.TypedEventHandler<MinMaxCloseControl, Windows.UI.Xaml.RoutedEventArgs> CloseClick;
|
event Windows.Foundation.TypedEventHandler<MinMaxCloseControl, Windows.UI.Xaml.RoutedEventArgs> CloseClick;
|
||||||
|
|
|
@ -220,7 +220,7 @@
|
||||||
</StackPanel.Resources>
|
</StackPanel.Resources>
|
||||||
|
|
||||||
<Button x:Name="MinimizeButton"
|
<Button x:Name="MinimizeButton"
|
||||||
x:Uid="WindowMinimizeButton"
|
x:Uid="MinimizeButton"
|
||||||
Width="46.0"
|
Width="46.0"
|
||||||
Height="{StaticResource CaptionButtonHeightWindowed}"
|
Height="{StaticResource CaptionButtonHeightWindowed}"
|
||||||
MinWidth="46.0"
|
MinWidth="46.0"
|
||||||
|
@ -232,9 +232,14 @@
|
||||||
<x:String x:Key="CaptionButtonPath">M 0 0 H 10</x:String>
|
<x:String x:Key="CaptionButtonPath">M 0 0 H 10</x:String>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</Button.Resources>
|
</Button.Resources>
|
||||||
|
<ToolTipService.ToolTip>
|
||||||
|
<ToolTip>
|
||||||
|
<TextBlock x:Uid="WindowMinimizeButtonToolTip" />
|
||||||
|
</ToolTip>
|
||||||
|
</ToolTipService.ToolTip>
|
||||||
</Button>
|
</Button>
|
||||||
<Button x:Name="MaximizeButton"
|
<Button x:Name="MaximizeButton"
|
||||||
x:Uid="WindowMaximizeButton"
|
x:Uid="MaximizeButton"
|
||||||
Width="46.0"
|
Width="46.0"
|
||||||
Height="{StaticResource CaptionButtonHeightWindowed}"
|
Height="{StaticResource CaptionButtonHeightWindowed}"
|
||||||
MinWidth="46.0"
|
MinWidth="46.0"
|
||||||
|
@ -256,7 +261,7 @@
|
||||||
</ToolTipService.ToolTip>
|
</ToolTipService.ToolTip>
|
||||||
</Button>
|
</Button>
|
||||||
<Button x:Name="CloseButton"
|
<Button x:Name="CloseButton"
|
||||||
x:Uid="WindowCloseButton"
|
x:Uid="CloseButton"
|
||||||
Width="46.0"
|
Width="46.0"
|
||||||
Height="{StaticResource CaptionButtonHeightWindowed}"
|
Height="{StaticResource CaptionButtonHeightWindowed}"
|
||||||
MinWidth="46.0"
|
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>
|
<x:String x:Key="CaptionButtonPath">M 0 0 L 10 10 M 10 0 L 0 10</x:String>
|
||||||
</ResourceDictionary>
|
</ResourceDictionary>
|
||||||
</Button.Resources>
|
</Button.Resources>
|
||||||
|
<ToolTipService.ToolTip>
|
||||||
|
<ToolTip>
|
||||||
|
<TextBlock x:Uid="WindowCloseButtonToolTip" />
|
||||||
|
</ToolTip>
|
||||||
|
</ToolTipService.ToolTip>
|
||||||
</Button>
|
</Button>
|
||||||
</StackPanel>
|
</StackPanel>
|
||||||
|
|
|
@ -411,6 +411,9 @@
|
||||||
<data name="WindowCloseButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
<data name="WindowCloseButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||||
<value>Close</value>
|
<value>Close</value>
|
||||||
</data>
|
</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">
|
<data name="WindowMaximizeButton.[using:Windows.UI.Xaml.Automation]AutomationProperties.Name" xml:space="preserve">
|
||||||
<value>Maximize</value>
|
<value>Maximize</value>
|
||||||
</data>
|
</data>
|
||||||
|
@ -420,6 +423,9 @@
|
||||||
<data name="WindowMinimizeButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
<data name="WindowMinimizeButton.[using:Windows.UI.Xaml.Controls]ToolTipService.ToolTip" xml:space="preserve">
|
||||||
<value>Minimize</value>
|
<value>Minimize</value>
|
||||||
</data>
|
</data>
|
||||||
|
<data name="WindowMinimizeButtonToolTip.Text" xml:space="preserve">
|
||||||
|
<value>Minimize</value>
|
||||||
|
</data>
|
||||||
<data name="AboutDialog.Title" xml:space="preserve">
|
<data name="AboutDialog.Title" xml:space="preserve">
|
||||||
<value>About</value>
|
<value>About</value>
|
||||||
</data>
|
</data>
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
// Copyright (c) Microsoft Corporation.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
//
|
//
|
||||||
// TitlebarControl.xaml.cpp
|
|
||||||
// Implementation of the TitlebarControl class
|
// Implementation of the TitlebarControl class
|
||||||
//
|
//
|
||||||
|
|
||||||
|
@ -24,6 +23,14 @@ namespace winrt::TerminalApp::implementation
|
||||||
MinMaxCloseControl().CloseClick({ this, &TitlebarControl::Close_Click });
|
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()
|
IInspectable TitlebarControl::Content()
|
||||||
{
|
{
|
||||||
return ContentRoot().Content();
|
return ContentRoot().Content();
|
||||||
|
@ -93,4 +100,48 @@ namespace winrt::TerminalApp::implementation
|
||||||
{
|
{
|
||||||
MinMaxCloseControl().SetWindowVisualState(visualState);
|
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.
|
// Copyright (c) Microsoft Corporation.
|
||||||
// Licensed under the MIT license.
|
// Licensed under the MIT license.
|
||||||
//
|
|
||||||
// Declaration of the MainUserControl class.
|
|
||||||
//
|
|
||||||
|
|
||||||
#pragma once
|
#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"
|
#include "TitlebarControl.g.h"
|
||||||
|
|
||||||
namespace winrt::TerminalApp::implementation
|
namespace winrt::TerminalApp::implementation
|
||||||
|
@ -17,6 +11,12 @@ namespace winrt::TerminalApp::implementation
|
||||||
{
|
{
|
||||||
TitlebarControl(uint64_t handle);
|
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();
|
IInspectable Content();
|
||||||
void Content(IInspectable content);
|
void Content(IInspectable content);
|
||||||
|
|
||||||
|
|
|
@ -10,11 +10,25 @@ namespace TerminalApp
|
||||||
WindowVisualStateIconified
|
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
|
[default_interface] runtimeclass TitlebarControl : Windows.UI.Xaml.Controls.Grid
|
||||||
{
|
{
|
||||||
TitlebarControl(UInt64 parentWindowHandle);
|
TitlebarControl(UInt64 parentWindowHandle);
|
||||||
void SetWindowVisualState(WindowVisualState visualState);
|
void SetWindowVisualState(WindowVisualState visualState);
|
||||||
|
|
||||||
|
void HoverButton(CaptionButton button);
|
||||||
|
void PressButton(CaptionButton button);
|
||||||
|
void ClickButton(CaptionButton button);
|
||||||
|
void ReleaseButtons();
|
||||||
|
Double CaptionButtonWidth { get; };
|
||||||
|
|
||||||
IInspectable Content;
|
IInspectable Content;
|
||||||
Windows.UI.Xaml.Controls.Border DragBar { get; };
|
Windows.UI.Xaml.Controls.Border DragBar { get; };
|
||||||
}
|
}
|
||||||
|
|
|
@ -87,51 +87,203 @@ void NonClientIslandWindow::MakeWindow() noexcept
|
||||||
THROW_HR_IF_NULL(E_UNEXPECTED, _dragBarWindow);
|
THROW_HR_IF_NULL(E_UNEXPECTED, _dragBarWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Function Description:
|
LRESULT NonClientIslandWindow::_dragBarNcHitTest(const til::point& pointer)
|
||||||
// - 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
|
|
||||||
{
|
{
|
||||||
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)
|
switch (message)
|
||||||
{
|
{
|
||||||
case WM_LBUTTONDOWN:
|
case WM_NCHITTEST:
|
||||||
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())
|
|
||||||
{
|
{
|
||||||
const POINT clientPt{ GET_X_LPARAM(lparam), GET_Y_LPARAM(lparam) };
|
// Try to determine what part of the window is being hovered here. This
|
||||||
POINT screenPt{ clientPt };
|
// is absolutely critical to making sure Snap Layouts (GH#9443) works!
|
||||||
if (ClientToScreen(_dragBarWindow.get(), &screenPt))
|
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() };
|
auto parentWindow{ GetHandle() };
|
||||||
|
return SendMessage(parentWindow, message, wparam, lparam);
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
|
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);
|
return DefWindowProc(_dragBarWindow.get(), message, wparam, lparam);
|
||||||
|
@ -279,10 +431,17 @@ RECT NonClientIslandWindow::_GetDragAreaRect() const noexcept
|
||||||
{
|
{
|
||||||
const auto scale = GetCurrentDpiScale();
|
const auto scale = GetCurrentDpiScale();
|
||||||
const auto transform = _dragBar.TransformToVisual(_rootGrid);
|
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{
|
const auto logicalDragBarRect = winrt::Windows::Foundation::Rect{
|
||||||
0.0f,
|
0.0f,
|
||||||
0.0f,
|
0.0f,
|
||||||
static_cast<float>(_dragBar.ActualWidth()),
|
static_cast<float>(_rootGrid.ActualWidth()),
|
||||||
static_cast<float>(_dragBar.ActualHeight())
|
static_cast<float>(_dragBar.ActualHeight())
|
||||||
};
|
};
|
||||||
const auto clientDragBarRect = transform.TransformBounds(logicalDragBarRect);
|
const auto clientDragBarRect = transform.TransformBounds(logicalDragBarRect);
|
||||||
|
@ -550,6 +709,7 @@ int NonClientIslandWindow::_GetResizeHandleHeight() const noexcept
|
||||||
// we didn't change them.
|
// we didn't change them.
|
||||||
LPARAM lParam = MAKELONG(ptMouse.x, ptMouse.y);
|
LPARAM lParam = MAKELONG(ptMouse.x, ptMouse.y);
|
||||||
const auto originalRet = DefWindowProc(_window.get(), WM_NCHITTEST, 0, lParam);
|
const auto originalRet = DefWindowProc(_window.get(), WM_NCHITTEST, 0, lParam);
|
||||||
|
|
||||||
if (originalRet != HTCLIENT)
|
if (originalRet != HTCLIENT)
|
||||||
{
|
{
|
||||||
// If we're the quake window, suppress resizing on any side except the
|
// If we're the quake window, suppress resizing on any side except the
|
||||||
|
|
|
@ -62,6 +62,7 @@ private:
|
||||||
winrt::Windows::UI::Xaml::ElementTheme _theme;
|
winrt::Windows::UI::Xaml::ElementTheme _theme;
|
||||||
|
|
||||||
bool _isMaximized;
|
bool _isMaximized;
|
||||||
|
bool _trackingMouse{ false };
|
||||||
|
|
||||||
[[nodiscard]] static LRESULT __stdcall _StaticInputSinkWndProc(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;
|
[[nodiscard]] LRESULT _InputSinkMessageHandler(UINT const message, WPARAM const wparam, LPARAM const lparam) noexcept;
|
||||||
|
@ -71,6 +72,7 @@ private:
|
||||||
int _GetResizeHandleHeight() const noexcept;
|
int _GetResizeHandleHeight() const noexcept;
|
||||||
RECT _GetDragAreaRect() const noexcept;
|
RECT _GetDragAreaRect() const noexcept;
|
||||||
int _GetTopBorderHeight() const noexcept;
|
int _GetTopBorderHeight() const noexcept;
|
||||||
|
LRESULT _dragBarNcHitTest(const til::point& pointer);
|
||||||
|
|
||||||
[[nodiscard]] LRESULT _OnNcCreate(WPARAM wParam, LPARAM lParam) noexcept override;
|
[[nodiscard]] LRESULT _OnNcCreate(WPARAM wParam, LPARAM lParam) noexcept override;
|
||||||
[[nodiscard]] LRESULT _OnNcCalcSize(const WPARAM wParam, const LPARAM lParam) noexcept;
|
[[nodiscard]] LRESULT _OnNcCalcSize(const WPARAM wParam, const LPARAM lParam) noexcept;
|
||||||
|
|
Loading…
Reference in a new issue