@@ -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.
0 commit comments