Skip to content

Commit 5da2ab1

Browse files
mcpiromanminiksa
authored andcommitted
Scroll from selection dragging out of window (#1247) (#1523)
* Scroll from selection dragging out of window * Review changes, dynamic dt measurement, function separation
1 parent e662277 commit 5da2ab1

File tree

2 files changed

+176
-9
lines changed

2 files changed

+176
-9
lines changed

src/cascadia/TerminalControl/TermControl.cpp

Lines changed: 163 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
3535
_settings{ settings },
3636
_closing{ false },
3737
_lastScrollOffset{ std::nullopt },
38+
_autoScrollVelocity{ 0 },
39+
_autoScrollingPointerPoint{ std::nullopt },
40+
_autoScrollTimer{},
41+
_lastAutoScrollUpdateTime{ std::nullopt },
3842
_desiredFont{ DEFAULT_FONT_FACE.c_str(), 0, 10, { 0, DEFAULT_FONT_SIZE }, CP_UTF8 },
3943
_actualFont{ DEFAULT_FONT_FACE.c_str(), 0, 10, { 0, DEFAULT_FONT_SIZE }, CP_UTF8, false },
4044
_touchAnchor{ std::nullopt },
@@ -500,6 +504,10 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
500504
auto pfnScrollPositionChanged = std::bind(&TermControl::_TerminalScrollPositionChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
501505
_terminal->SetScrollPositionChangedCallback(pfnScrollPositionChanged);
502506

507+
static constexpr auto AutoScrollUpdateInterval = std::chrono::microseconds(static_cast<int>(1.0 / 30.0 * 1000000));
508+
_autoScrollTimer.Interval(AutoScrollUpdateInterval);
509+
_autoScrollTimer.Tick({ this, &TermControl::_UpdateAutoScroll });
510+
503511
// Set up blinking cursor
504512
int blinkTime = GetCaretBlinkTime();
505513
if (blinkTime != INFINITE)
@@ -743,14 +751,33 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
743751

744752
if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Mouse)
745753
{
746-
if (point.Properties().IsLeftButtonPressed())
754+
if (_terminal->IsSelectionActive() && point.Properties().IsLeftButtonPressed())
747755
{
748756
const auto cursorPosition = point.Position();
749-
const auto terminalPosition = _GetTerminalPosition(cursorPosition);
757+
_SetEndSelectionPointAtCursor(cursorPosition);
758+
759+
const double cursorBelowBottomDist = cursorPosition.Y - _swapChainPanel.Margin().Top - _swapChainPanel.ActualHeight();
760+
const double cursorAboveTopDist = -1 * cursorPosition.Y + _swapChainPanel.Margin().Top;
761+
762+
constexpr double MinAutoScrollDist = 2.0; // Arbitrary value
763+
double newAutoScrollVelocity = 0.0;
764+
if (cursorBelowBottomDist > MinAutoScrollDist)
765+
{
766+
newAutoScrollVelocity = _GetAutoScrollSpeed(cursorBelowBottomDist);
767+
}
768+
else if (cursorAboveTopDist > MinAutoScrollDist)
769+
{
770+
newAutoScrollVelocity = -1.0 * _GetAutoScrollSpeed(cursorAboveTopDist);
771+
}
750772

751-
// save location (for rendering) + render
752-
_terminal->SetEndSelectionPosition(terminalPosition);
753-
_renderer->TriggerSelection();
773+
if (newAutoScrollVelocity != 0)
774+
{
775+
_TryStartAutoScroll(point, newAutoScrollVelocity);
776+
}
777+
else
778+
{
779+
_TryStopAutoScroll(ptr.PointerId());
780+
}
754781
}
755782
}
756783
else if (ptr.PointerDeviceType() == Windows::Devices::Input::PointerDeviceType::Touch && _touchAnchor)
@@ -806,6 +833,8 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
806833
_touchAnchor = std::nullopt;
807834
}
808835

836+
_TryStopAutoScroll(ptr.PointerId());
837+
809838
args.Handled(true);
810839
}
811840

@@ -818,7 +847,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
818847
void TermControl::_MouseWheelHandler(Windows::Foundation::IInspectable const& /*sender*/,
819848
Input::PointerRoutedEventArgs const& args)
820849
{
821-
auto delta = args.GetCurrentPoint(_root).Properties().MouseWheelDelta();
850+
const auto point = args.GetCurrentPoint(_root);
851+
const auto delta = point.Properties().MouseWheelDelta();
852+
822853
// Get the state of the Ctrl & Shift keys
823854
// static_cast to a uint32_t because we can't use the WI_IsFlagSet macro
824855
// directly with a VirtualKeyModifiers
@@ -836,7 +867,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
836867
}
837868
else
838869
{
839-
_MouseScrollHandler(delta);
870+
_MouseScrollHandler(delta, point);
840871
}
841872
}
842873

@@ -893,7 +924,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
893924
// - Scroll the visible viewport in response to a mouse wheel event.
894925
// Arguments:
895926
// - mouseDelta: the mouse wheel delta that triggered this event.
896-
void TermControl::_MouseScrollHandler(const double mouseDelta)
927+
void TermControl::_MouseScrollHandler(const double mouseDelta, Windows::UI::Input::PointerPoint const& pointerPoint)
897928
{
898929
const auto currentOffset = this->GetScrollOffset();
899930

@@ -915,6 +946,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
915946
// The scroll bar's ValueChanged handler will actually move the viewport
916947
// for us.
917948
_scrollBar.Value(static_cast<int>(newValue));
949+
950+
if (_terminal->IsSelectionActive() && pointerPoint.Properties().IsLeftButtonPressed())
951+
{
952+
// If user is mouse selecting and scrolls, they then point at new character.
953+
// Make sure selection reflects that immediately.
954+
_SetEndSelectionPointAtCursor(pointerPoint.Position());
955+
}
918956
}
919957

920958
void TermControl::_ScrollbarChangeHandler(Windows::Foundation::IInspectable const& sender,
@@ -982,6 +1020,84 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
9821020
return false;
9831021
}
9841022

1023+
// Method Description:
1024+
// - Starts new pointer related auto scroll behavior, or continues existing one.
1025+
// Does nothing when there is already auto scroll associated with another pointer.
1026+
// Arguments:
1027+
// - pointerPoint: info about pointer that causes auto scroll. Pointer's position
1028+
// is later used to update selection.
1029+
// - scrollVelocity: target velocity of scrolling in characters / sec
1030+
void TermControl::_TryStartAutoScroll(Windows::UI::Input::PointerPoint const& pointerPoint, const double scrollVelocity)
1031+
{
1032+
// Allow only one pointer at the time
1033+
if (!_autoScrollingPointerPoint.has_value() || _autoScrollingPointerPoint.value().PointerId() == pointerPoint.PointerId())
1034+
{
1035+
_autoScrollingPointerPoint = pointerPoint;
1036+
_autoScrollVelocity = scrollVelocity;
1037+
1038+
// If this is first time the auto scroll update is about to be called,
1039+
// kick-start it by initializing its time delta as if it started now
1040+
if (!_lastAutoScrollUpdateTime.has_value())
1041+
{
1042+
_lastAutoScrollUpdateTime = std::chrono::high_resolution_clock::now();
1043+
}
1044+
1045+
// Apparently this check is not necessary but greatly improves performance
1046+
if (!_autoScrollTimer.IsEnabled())
1047+
{
1048+
_autoScrollTimer.Start();
1049+
}
1050+
}
1051+
}
1052+
1053+
// Method Description:
1054+
// - Stops auto scroll if it's active and is associated with supplied pointer id.
1055+
// Arguments:
1056+
// - pointerId: id of pointer for which to stop auto scroll
1057+
void TermControl::_TryStopAutoScroll(const uint32_t pointerId)
1058+
{
1059+
if (_autoScrollingPointerPoint.has_value() && pointerId == _autoScrollingPointerPoint.value().PointerId())
1060+
{
1061+
_autoScrollingPointerPoint = std::nullopt;
1062+
_autoScrollVelocity = 0;
1063+
_lastAutoScrollUpdateTime = std::nullopt;
1064+
1065+
// Apparently this check is not necessary but greatly improves performance
1066+
if (_autoScrollTimer.IsEnabled())
1067+
{
1068+
_autoScrollTimer.Stop();
1069+
}
1070+
}
1071+
}
1072+
1073+
// Method Description:
1074+
// - Called continuously to gradually scroll viewport when user is
1075+
// mouse selecting outside it (to 'follow' the cursor).
1076+
// Arguments:
1077+
// - none
1078+
void TermControl::_UpdateAutoScroll(Windows::Foundation::IInspectable const& /* sender */,
1079+
Windows::Foundation::IInspectable const& /* e */)
1080+
{
1081+
if (_autoScrollVelocity != 0)
1082+
{
1083+
const auto timeNow = std::chrono::high_resolution_clock::now();
1084+
1085+
if (_lastAutoScrollUpdateTime.has_value())
1086+
{
1087+
static constexpr double microSecPerSec = 1000000.0;
1088+
const double deltaTime = std::chrono::duration_cast<std::chrono::microseconds>(timeNow - _lastAutoScrollUpdateTime.value()).count() / microSecPerSec;
1089+
_scrollBar.Value(_scrollBar.Value() + _autoScrollVelocity * deltaTime);
1090+
1091+
if (_autoScrollingPointerPoint.has_value())
1092+
{
1093+
_SetEndSelectionPointAtCursor(_autoScrollingPointerPoint.value().Position());
1094+
}
1095+
}
1096+
1097+
_lastAutoScrollUpdateTime = timeNow;
1098+
}
1099+
}
1100+
9851101
// Method Description:
9861102
// - Event handler for the GotFocus event. This is used to start
9871103
// blinking the cursor when the window is focused.
@@ -1087,6 +1203,25 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
10871203
_terminal->SetCursorVisible(!_terminal->IsCursorVisible());
10881204
}
10891205

1206+
// Method Description:
1207+
// - Sets selection's end position to match supplied cursor position, e.g. while mouse dragging.
1208+
// Arguments:
1209+
// - cursorPosition: in pixels, relative to the origin of the control
1210+
void TermControl::_SetEndSelectionPointAtCursor(Windows::Foundation::Point const& cursorPosition)
1211+
{
1212+
auto terminalPosition = _GetTerminalPosition(cursorPosition);
1213+
1214+
const short lastVisibleRow = std::max(_terminal->GetViewport().Height() - 1, 0);
1215+
const short lastVisibleCol = std::max(_terminal->GetViewport().Width() - 1, 0);
1216+
1217+
terminalPosition.Y = std::clamp(terminalPosition.Y, short{ 0 }, lastVisibleRow);
1218+
terminalPosition.X = std::clamp(terminalPosition.X, short{ 0 }, lastVisibleCol);
1219+
1220+
// save location (for rendering) + render
1221+
_terminal->SetEndSelectionPosition(terminalPosition);
1222+
_renderer->TriggerSelection();
1223+
}
1224+
10901225
// Method Description:
10911226
// - Process a resize event that was initiated by the user. This can either
10921227
// be due to the user resizing the window (causing the swapchain to
@@ -1237,6 +1372,12 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
12371372
// cursorTimer timer, now stopped, is destroyed.
12381373
}
12391374

1375+
if (auto localAutoScrollTimer{ std::exchange(_autoScrollTimer, nullptr) })
1376+
{
1377+
localAutoScrollTimer.Stop();
1378+
// _autoScrollTimer timer, now stopped, is destroyed.
1379+
}
1380+
12401381
if (auto localConnection{ std::exchange(_connection, nullptr) })
12411382
{
12421383
localConnection.Close();
@@ -1580,6 +1721,20 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
15801721
return _multiClickCounter;
15811722
}
15821723

1724+
// Method Description:
1725+
// - Calculates speed of single axis of auto scrolling. It has to allow for both
1726+
// fast and precise selection.
1727+
// Arguments:
1728+
// - cursorDistanceFromBorder: distance from viewport border to cursor, in pixels. Must be non-negative.
1729+
// Return Value:
1730+
// - positive speed in characters / sec
1731+
double TermControl::_GetAutoScrollSpeed(double cursorDistanceFromBorder) const
1732+
{
1733+
// The numbers below just feel well, feel free to change.
1734+
// TODO: Maybe account for space beyond border that user has available
1735+
return std::pow(cursorDistanceFromBorder, 2.0) / 25.0 + 2.0;
1736+
}
1737+
15831738
// -------------------------------- WinRT Events ---------------------------------
15841739
// Winrt events need a method for adding a callback to the event and removing the callback.
15851740
// These macros will define them both for you.

src/cascadia/TerminalControl/TermControl.h

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,12 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
9292

9393
std::optional<int> _lastScrollOffset;
9494

95+
// Auto scroll occurs when user, while selecting, drags cursor outside viewport. View is then scrolled to 'follow' the cursor.
96+
double _autoScrollVelocity;
97+
std::optional<Windows::UI::Input::PointerPoint> _autoScrollingPointerPoint;
98+
Windows::UI::Xaml::DispatcherTimer _autoScrollTimer;
99+
std::optional<std::chrono::high_resolution_clock::time_point> _lastAutoScrollUpdateTime;
100+
95101
// storage location for the leading surrogate of a utf-16 surrogate pair
96102
std::optional<wchar_t> _leadingSurrogate;
97103

@@ -135,27 +141,33 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
135141
void _LostFocusHandler(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::RoutedEventArgs const& e);
136142

137143
void _BlinkCursor(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e);
144+
void _SetEndSelectionPointAtCursor(Windows::Foundation::Point const& cursorPosition);
138145
void _SendInputToConnection(const std::wstring& wstr);
139146
void _SwapChainSizeChanged(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::SizeChangedEventArgs const& e);
140147
void _SwapChainScaleChanged(Windows::UI::Xaml::Controls::SwapChainPanel const& sender, Windows::Foundation::IInspectable const& args);
141148
void _DoResize(const double newWidth, const double newHeight);
142149
void _TerminalTitleChanged(const std::wstring_view& wstr);
143150
void _TerminalScrollPositionChanged(const int viewTop, const int viewHeight, const int bufferSize);
144151

145-
void _MouseScrollHandler(const double delta);
152+
void _MouseScrollHandler(const double delta, Windows::UI::Input::PointerPoint const& pointerPoint);
146153
void _MouseZoomHandler(const double delta);
147154
void _MouseTransparencyHandler(const double delta);
148155

149156
bool _CapturePointer(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
150157
bool _ReleasePointerCapture(Windows::Foundation::IInspectable const& sender, Windows::UI::Xaml::Input::PointerRoutedEventArgs const& e);
151158

159+
void _TryStartAutoScroll(Windows::UI::Input::PointerPoint const& pointerPoint, const double scrollVelocity);
160+
void _TryStopAutoScroll(const uint32_t pointerId);
161+
void _UpdateAutoScroll(Windows::Foundation::IInspectable const& sender, Windows::Foundation::IInspectable const& e);
162+
152163
void _ScrollbarUpdater(Windows::UI::Xaml::Controls::Primitives::ScrollBar scrollbar, const int viewTop, const int viewHeight, const int bufferSize);
153164
static Windows::UI::Xaml::Thickness _ParseThicknessFromPadding(const hstring padding);
154165

155166
::Microsoft::Terminal::Core::ControlKeyStates _GetPressedModifierKeys() const;
156167

157168
const COORD _GetTerminalPosition(winrt::Windows::Foundation::Point cursorPosition);
158169
const unsigned int _NumberOfClicks(winrt::Windows::Foundation::Point clickPos, Timestamp clickTime);
170+
double _GetAutoScrollSpeed(double cursorDistanceFromBorder) const;
159171
};
160172
}
161173

0 commit comments

Comments
 (0)