mirror of
https://github.com/microsoft/terminal.git
synced 2025-12-12 08:40:37 -06:00
Fix a pair of TermControl dragging bugs (#10650)
## Summary of the Pull Request This fixes two bugs related to dragging into the bounds of the `TermControl`. Although the fixes are fairly small, I'm batching them up, because I don't want to stack 2 more PRs on top of #10051. * #9109 - This is fixed by only starting an autoscroll if the click&drag actually started within the bounds of the control. * #4603 - Building on the above change, only modify the selection when the drag started in the control. ## References * srsly go read #10051. ## PR Checklist * [x] Closes #9109 * [x] Closes #4603 * [x] I work here * [x] Test added * [n/a] Requires documentation to be updated ## Detailed Description of the Pull Request / Additional comments This is kind of annoying that the auto-scrolling is handled by the TermControl, but it uses a timer that's still a WinUI construct. We only want to start the auto-scrolling behavior when the drag started _inside_ the control. Otherwise, in the tab drag scenario, dragging into the bounds of the TermControl will trick it into thinking it should start a scroll.
This commit is contained in:
parent
f058b08fde
commit
4b45bb8df1
@ -279,7 +279,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||||||
const unsigned int pointerUpdateKind,
|
const unsigned int pointerUpdateKind,
|
||||||
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
|
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
|
||||||
const bool focused,
|
const bool focused,
|
||||||
const til::point pixelPosition)
|
const til::point pixelPosition,
|
||||||
|
const bool pointerPressedInBounds)
|
||||||
{
|
{
|
||||||
const til::point terminalPosition = _getTerminalPosition(pixelPosition);
|
const til::point terminalPosition = _getTerminalPosition(pixelPosition);
|
||||||
|
|
||||||
@ -288,7 +289,11 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||||||
{
|
{
|
||||||
_core->SendMouseEvent(terminalPosition, pointerUpdateKind, modifiers, 0, toInternalMouseState(buttonState));
|
_core->SendMouseEvent(terminalPosition, pointerUpdateKind, modifiers, 0, toInternalMouseState(buttonState));
|
||||||
}
|
}
|
||||||
else if (focused && WI_IsFlagSet(buttonState, MouseButtonState::IsLeftButtonDown))
|
// GH#4603 - don't modify the selection if the pointer press didn't
|
||||||
|
// actually start _in_ the control bounds. Case in point - someone drags
|
||||||
|
// a file into the bounds of the control. That shouldn't send the
|
||||||
|
// selection into space.
|
||||||
|
else if (focused && pointerPressedInBounds && WI_IsFlagSet(buttonState, MouseButtonState::IsLeftButtonDown))
|
||||||
{
|
{
|
||||||
if (_singleClickTouchdownPos)
|
if (_singleClickTouchdownPos)
|
||||||
{
|
{
|
||||||
|
|||||||
@ -58,7 +58,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||||||
const unsigned int pointerUpdateKind,
|
const unsigned int pointerUpdateKind,
|
||||||
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
|
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
|
||||||
const bool focused,
|
const bool focused,
|
||||||
const til::point pixelPosition);
|
const til::point pixelPosition,
|
||||||
|
const bool pointerPressedInBounds);
|
||||||
void TouchMoved(const til::point newTouchPoint,
|
void TouchMoved(const til::point newTouchPoint,
|
||||||
const bool focused);
|
const bool focused);
|
||||||
|
|
||||||
|
|||||||
@ -39,7 +39,9 @@ namespace Microsoft.Terminal.Control
|
|||||||
UInt32 pointerUpdateKind,
|
UInt32 pointerUpdateKind,
|
||||||
Microsoft.Terminal.Core.ControlKeyStates modifiers,
|
Microsoft.Terminal.Core.ControlKeyStates modifiers,
|
||||||
Boolean focused,
|
Boolean focused,
|
||||||
Microsoft.Terminal.Core.Point pixelPosition);
|
Microsoft.Terminal.Core.Point pixelPosition,
|
||||||
|
Boolean pointerPressedInBounds);
|
||||||
|
|
||||||
void TouchMoved(Microsoft.Terminal.Core.Point newTouchPoint,
|
void TouchMoved(Microsoft.Terminal.Core.Point newTouchPoint,
|
||||||
Boolean focused);
|
Boolean focused);
|
||||||
|
|
||||||
|
|||||||
@ -1052,6 +1052,10 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||||||
Focus(FocusState::Pointer);
|
Focus(FocusState::Pointer);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Mark that this pointer event actually started within our bounds.
|
||||||
|
// We'll need this later, for PointerMoved events.
|
||||||
|
_pointerPressedInBounds = true;
|
||||||
|
|
||||||
if (type == Windows::Devices::Input::PointerDeviceType::Touch)
|
if (type == Windows::Devices::Input::PointerDeviceType::Touch)
|
||||||
{
|
{
|
||||||
const auto contactRect = point.Properties().ContactRect();
|
const auto contactRect = point.Properties().ContactRect();
|
||||||
@ -1104,10 +1108,19 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||||||
TermControl::GetPointerUpdateKind(point),
|
TermControl::GetPointerUpdateKind(point),
|
||||||
ControlKeyStates(args.KeyModifiers()),
|
ControlKeyStates(args.KeyModifiers()),
|
||||||
_focused,
|
_focused,
|
||||||
pixelPosition);
|
pixelPosition,
|
||||||
|
_pointerPressedInBounds);
|
||||||
|
|
||||||
if (_focused && point.Properties().IsLeftButtonPressed())
|
// GH#9109 - Only start an auto-scroll when the drag actually
|
||||||
|
// started within our bounds. Otherwise, someone could start a drag
|
||||||
|
// outside the terminal control, drag into the padding, and trick us
|
||||||
|
// into starting to scroll.
|
||||||
|
if (_focused && _pointerPressedInBounds && point.Properties().IsLeftButtonPressed())
|
||||||
{
|
{
|
||||||
|
// We want to find the distance relative to the bounds of the
|
||||||
|
// SwapChainPanel, not the entire control. If they drag out of
|
||||||
|
// the bounds of the text, into the padding, we still what that
|
||||||
|
// to auto-scroll
|
||||||
const double cursorBelowBottomDist = cursorPosition.Y - SwapChainPanel().Margin().Top - SwapChainPanel().ActualHeight();
|
const double cursorBelowBottomDist = cursorPosition.Y - SwapChainPanel().Margin().Top - SwapChainPanel().ActualHeight();
|
||||||
const double cursorAboveTopDist = -1 * cursorPosition.Y + SwapChainPanel().Margin().Top;
|
const double cursorAboveTopDist = -1 * cursorPosition.Y + SwapChainPanel().Margin().Top;
|
||||||
|
|
||||||
@ -1157,6 +1170,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
_pointerPressedInBounds = false;
|
||||||
|
|
||||||
const auto ptr = args.Pointer();
|
const auto ptr = args.Pointer();
|
||||||
const auto point = args.GetCurrentPoint(*this);
|
const auto point = args.GetCurrentPoint(*this);
|
||||||
const auto cursorPosition = point.Position();
|
const auto cursorPosition = point.Position();
|
||||||
|
|||||||
@ -167,11 +167,13 @@ namespace winrt::Microsoft::Terminal::Control::implementation
|
|||||||
std::shared_ptr<ThrottledFuncTrailing<ScrollBarUpdate>> _updateScrollBar;
|
std::shared_ptr<ThrottledFuncTrailing<ScrollBarUpdate>> _updateScrollBar;
|
||||||
bool _isInternalScrollBarUpdate;
|
bool _isInternalScrollBarUpdate;
|
||||||
|
|
||||||
// Auto scroll occurs when user, while selecting, drags cursor outside viewport. View is then scrolled to 'follow' the cursor.
|
// Auto scroll occurs when user, while selecting, drags cursor outside
|
||||||
|
// viewport. View is then scrolled to 'follow' the cursor.
|
||||||
double _autoScrollVelocity;
|
double _autoScrollVelocity;
|
||||||
std::optional<Windows::UI::Input::PointerPoint> _autoScrollingPointerPoint;
|
std::optional<Windows::UI::Input::PointerPoint> _autoScrollingPointerPoint;
|
||||||
Windows::UI::Xaml::DispatcherTimer _autoScrollTimer;
|
Windows::UI::Xaml::DispatcherTimer _autoScrollTimer;
|
||||||
std::optional<std::chrono::high_resolution_clock::time_point> _lastAutoScrollUpdateTime;
|
std::optional<std::chrono::high_resolution_clock::time_point> _lastAutoScrollUpdateTime;
|
||||||
|
bool _pointerPressedInBounds{ false };
|
||||||
|
|
||||||
winrt::Windows::UI::Composition::ScalarKeyFrameAnimation _bellLightAnimation{ nullptr };
|
winrt::Windows::UI::Composition::ScalarKeyFrameAnimation _bellLightAnimation{ nullptr };
|
||||||
Windows::UI::Xaml::DispatcherTimer _bellLightTimer{ nullptr };
|
Windows::UI::Xaml::DispatcherTimer _bellLightTimer{ nullptr };
|
||||||
|
|||||||
@ -32,6 +32,9 @@ namespace ControlUnitTests
|
|||||||
TEST_METHOD(ScrollWithSelection);
|
TEST_METHOD(ScrollWithSelection);
|
||||||
TEST_METHOD(TestScrollWithTrackpad);
|
TEST_METHOD(TestScrollWithTrackpad);
|
||||||
TEST_METHOD(TestQuickDragOnSelect);
|
TEST_METHOD(TestQuickDragOnSelect);
|
||||||
|
|
||||||
|
TEST_METHOD(TestDragSelectOutsideBounds);
|
||||||
|
|
||||||
TEST_METHOD(PointerClickOutsideActiveRegion);
|
TEST_METHOD(PointerClickOutsideActiveRegion);
|
||||||
|
|
||||||
TEST_CLASS_SETUP(ClassSetup)
|
TEST_CLASS_SETUP(ClassSetup)
|
||||||
@ -288,7 +291,8 @@ namespace ControlUnitTests
|
|||||||
WM_LBUTTONDOWN, //pointerUpdateKind
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
||||||
modifiers,
|
modifiers,
|
||||||
true, // focused,
|
true, // focused,
|
||||||
cursorPosition1);
|
cursorPosition1,
|
||||||
|
true);
|
||||||
Log::Comment(L"Verify that there's one selection");
|
Log::Comment(L"Verify that there's one selection");
|
||||||
VERIFY_IS_TRUE(core->HasSelection());
|
VERIFY_IS_TRUE(core->HasSelection());
|
||||||
VERIFY_ARE_EQUAL(1u, core->_terminal->GetSelectionRects().size());
|
VERIFY_ARE_EQUAL(1u, core->_terminal->GetSelectionRects().size());
|
||||||
@ -300,7 +304,8 @@ namespace ControlUnitTests
|
|||||||
WM_LBUTTONDOWN, //pointerUpdateKind
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
||||||
modifiers,
|
modifiers,
|
||||||
true, // focused,
|
true, // focused,
|
||||||
cursorPosition2);
|
cursorPosition2,
|
||||||
|
true);
|
||||||
Log::Comment(L"Verify that there's now two selections (one on each row)");
|
Log::Comment(L"Verify that there's now two selections (one on each row)");
|
||||||
VERIFY_IS_TRUE(core->HasSelection());
|
VERIFY_IS_TRUE(core->HasSelection());
|
||||||
VERIFY_ARE_EQUAL(2u, core->_terminal->GetSelectionRects().size());
|
VERIFY_ARE_EQUAL(2u, core->_terminal->GetSelectionRects().size());
|
||||||
@ -333,7 +338,8 @@ namespace ControlUnitTests
|
|||||||
WM_LBUTTONDOWN, //pointerUpdateKind
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
||||||
modifiers,
|
modifiers,
|
||||||
true, // focused,
|
true, // focused,
|
||||||
cursorPosition4);
|
cursorPosition4,
|
||||||
|
true);
|
||||||
Log::Comment(L"Verify that there's now one selection");
|
Log::Comment(L"Verify that there's now one selection");
|
||||||
VERIFY_IS_TRUE(core->HasSelection());
|
VERIFY_IS_TRUE(core->HasSelection());
|
||||||
VERIFY_ARE_EQUAL(1u, core->_terminal->GetSelectionRects().size());
|
VERIFY_ARE_EQUAL(1u, core->_terminal->GetSelectionRects().size());
|
||||||
@ -388,7 +394,8 @@ namespace ControlUnitTests
|
|||||||
WM_LBUTTONDOWN, //pointerUpdateKind
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
||||||
modifiers,
|
modifiers,
|
||||||
true, // focused,
|
true, // focused,
|
||||||
cursorPosition1);
|
cursorPosition1,
|
||||||
|
true);
|
||||||
Log::Comment(L"Verify that there's one selection");
|
Log::Comment(L"Verify that there's one selection");
|
||||||
VERIFY_IS_TRUE(core->HasSelection());
|
VERIFY_IS_TRUE(core->HasSelection());
|
||||||
VERIFY_ARE_EQUAL(1u, core->_terminal->GetSelectionRects().size());
|
VERIFY_ARE_EQUAL(1u, core->_terminal->GetSelectionRects().size());
|
||||||
@ -536,7 +543,8 @@ namespace ControlUnitTests
|
|||||||
WM_LBUTTONDOWN, //pointerUpdateKind
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
||||||
modifiers,
|
modifiers,
|
||||||
true, // focused,
|
true, // focused,
|
||||||
cursorPosition1);
|
cursorPosition1,
|
||||||
|
true);
|
||||||
Log::Comment(L"Verify that there's one selection");
|
Log::Comment(L"Verify that there's one selection");
|
||||||
VERIFY_IS_TRUE(core->HasSelection());
|
VERIFY_IS_TRUE(core->HasSelection());
|
||||||
VERIFY_ARE_EQUAL(1u, core->_terminal->GetSelectionRects().size());
|
VERIFY_ARE_EQUAL(1u, core->_terminal->GetSelectionRects().size());
|
||||||
@ -546,6 +554,74 @@ namespace ControlUnitTests
|
|||||||
VERIFY_ARE_EQUAL(expectedAnchor, core->_terminal->GetSelectionAnchor());
|
VERIFY_ARE_EQUAL(expectedAnchor, core->_terminal->GetSelectionAnchor());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void ControlInteractivityTests::TestDragSelectOutsideBounds()
|
||||||
|
{
|
||||||
|
// This is a test for GH#4603
|
||||||
|
|
||||||
|
auto [settings, conn] = _createSettingsAndConnection();
|
||||||
|
auto [core, interactivity] = _createCoreAndInteractivity(*settings, *conn);
|
||||||
|
_standardInit(core, interactivity);
|
||||||
|
|
||||||
|
// For this test, don't use any modifiers
|
||||||
|
const auto modifiers = ControlKeyStates();
|
||||||
|
const Control::MouseButtonState leftMouseDown{ Control::MouseButtonState::IsLeftButtonDown };
|
||||||
|
const Control::MouseButtonState noMouseDown{};
|
||||||
|
|
||||||
|
const til::size fontSize{ 9, 21 };
|
||||||
|
Log::Comment(L"Click on the terminal");
|
||||||
|
const til::point cursorPosition0{ 6, 0 };
|
||||||
|
interactivity->PointerPressed(leftMouseDown,
|
||||||
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
||||||
|
0, // timestamp
|
||||||
|
modifiers,
|
||||||
|
cursorPosition0);
|
||||||
|
|
||||||
|
Log::Comment(L"Verify that there's not yet a selection");
|
||||||
|
VERIFY_IS_FALSE(core->HasSelection());
|
||||||
|
|
||||||
|
VERIFY_IS_TRUE(interactivity->_singleClickTouchdownPos.has_value());
|
||||||
|
VERIFY_ARE_EQUAL(cursorPosition0, interactivity->_singleClickTouchdownPos.value());
|
||||||
|
|
||||||
|
Log::Comment(L"Drag the mouse a lot. This simulates dragging the mouse real fast.");
|
||||||
|
const til::point cursorPosition1{ 6 + fontSize.width<int>() * 2, 0 };
|
||||||
|
interactivity->PointerMoved(leftMouseDown,
|
||||||
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
||||||
|
modifiers,
|
||||||
|
true, // focused,
|
||||||
|
cursorPosition1,
|
||||||
|
true);
|
||||||
|
Log::Comment(L"Verify that there's one selection");
|
||||||
|
VERIFY_IS_TRUE(core->HasSelection());
|
||||||
|
VERIFY_ARE_EQUAL(1u, core->_terminal->GetSelectionRects().size());
|
||||||
|
|
||||||
|
Log::Comment(L"Verify that it started on the first cell we clicked on, not the one we dragged to");
|
||||||
|
COORD expectedAnchor{ 0, 0 };
|
||||||
|
VERIFY_ARE_EQUAL(expectedAnchor, core->_terminal->GetSelectionAnchor());
|
||||||
|
COORD expectedEnd{ 2, 0 };
|
||||||
|
VERIFY_ARE_EQUAL(expectedEnd, core->_terminal->GetSelectionEnd());
|
||||||
|
|
||||||
|
interactivity->PointerReleased(noMouseDown,
|
||||||
|
WM_LBUTTONUP,
|
||||||
|
modifiers,
|
||||||
|
cursorPosition1);
|
||||||
|
|
||||||
|
VERIFY_ARE_EQUAL(expectedAnchor, core->_terminal->GetSelectionAnchor());
|
||||||
|
VERIFY_ARE_EQUAL(expectedEnd, core->_terminal->GetSelectionEnd());
|
||||||
|
|
||||||
|
Log::Comment(L"Simulate dragging the mouse into the control, without first clicking into the control");
|
||||||
|
const til::point cursorPosition2{ fontSize.width<int>() * 10, 0 };
|
||||||
|
interactivity->PointerMoved(leftMouseDown,
|
||||||
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
||||||
|
modifiers,
|
||||||
|
true, // focused,
|
||||||
|
cursorPosition2,
|
||||||
|
false);
|
||||||
|
|
||||||
|
Log::Comment(L"The selection should be unchanged.");
|
||||||
|
VERIFY_ARE_EQUAL(expectedAnchor, core->_terminal->GetSelectionAnchor());
|
||||||
|
VERIFY_ARE_EQUAL(expectedEnd, core->_terminal->GetSelectionEnd());
|
||||||
|
}
|
||||||
|
|
||||||
void ControlInteractivityTests::PointerClickOutsideActiveRegion()
|
void ControlInteractivityTests::PointerClickOutsideActiveRegion()
|
||||||
{
|
{
|
||||||
// This is a test for GH#10642
|
// This is a test for GH#10642
|
||||||
@ -561,7 +637,6 @@ namespace ControlUnitTests
|
|||||||
const Control::MouseButtonState noMouseDown{};
|
const Control::MouseButtonState noMouseDown{};
|
||||||
|
|
||||||
const til::size fontSize{ 9, 21 };
|
const til::size fontSize{ 9, 21 };
|
||||||
|
|
||||||
interactivity->_rowsToScroll = 1;
|
interactivity->_rowsToScroll = 1;
|
||||||
int expectedTop = 0;
|
int expectedTop = 0;
|
||||||
int expectedViewHeight = 20;
|
int expectedViewHeight = 20;
|
||||||
@ -630,7 +705,8 @@ namespace ControlUnitTests
|
|||||||
WM_LBUTTONDOWN, //pointerUpdateKind
|
WM_LBUTTONDOWN, //pointerUpdateKind
|
||||||
modifiers,
|
modifiers,
|
||||||
true, // focused,
|
true, // focused,
|
||||||
cursorPosition1);
|
cursorPosition1,
|
||||||
|
true);
|
||||||
Log::Comment(L"Verify that there's still no selection");
|
Log::Comment(L"Verify that there's still no selection");
|
||||||
VERIFY_IS_FALSE(core->HasSelection());
|
VERIFY_IS_FALSE(core->HasSelection());
|
||||||
}
|
}
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user