Skip to content

Commit

Permalink
Move autoscrolling down to Interactivity
Browse files Browse the repository at this point in the history
  • Loading branch information
DHowett committed Sep 14, 2023
1 parent 7f16cf9 commit 0bff156
Show file tree
Hide file tree
Showing 4 changed files with 171 additions and 165 deletions.
158 changes: 156 additions & 2 deletions src/cascadia/TerminalControl/ControlInteractivity.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ namespace winrt::Microsoft::Terminal::Control::implementation
self->_AttachedHandlers(*self, nullptr);
}
});

_createInteractivityTimers();
}

uint64_t ControlInteractivity::Id()
Expand All @@ -82,12 +84,14 @@ namespace winrt::Microsoft::Terminal::Control::implementation
LOG_IF_FAILED(_uiaEngine->Disable());
_core->DetachUiaEngine(_uiaEngine.get());
}
_destroyInteractivityTimers();
_core->Detach();
}

void ControlInteractivity::AttachToNewControl(const Microsoft::Terminal::Control::IKeyBindings& keyBindings)
{
_core->AttachToNewControl(keyBindings);
_createInteractivityTimers();
}

// Method Description:
Expand Down Expand Up @@ -119,12 +123,30 @@ namespace winrt::Microsoft::Terminal::Control::implementation
void ControlInteractivity::Close()
{
_ClosedHandlers(*this, nullptr);
_destroyInteractivityTimers();
if (_core)
{
_core->Close();
}
}

void ControlInteractivity::_createInteractivityTimers()
{
_autoScrollTimer = _core->Dispatcher().CreateTimer();
static constexpr auto AutoScrollUpdateInterval = std::chrono::microseconds(static_cast<int>(1.0 / 30.0 * 1000000));
_autoScrollTimer.Interval(AutoScrollUpdateInterval);
_autoScrollTimer.Tick({ get_weak(), &ControlInteractivity::_updateAutoScroll });
}

void ControlInteractivity::_destroyInteractivityTimers()
{
if (_autoScrollTimer)
{
_autoScrollTimer.Stop();
_autoScrollTimer = nullptr;
}
}

// Method Description:
// - Returns the number of clicks that occurred (double and triple click support).
// Every call to this function registers a click.
Expand Down Expand Up @@ -347,7 +369,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
_touchAnchor = contactPoint;
}

void ControlInteractivity::PointerMoved(const uint32_t /*pointerId*/,
void ControlInteractivity::PointerMoved(const uint32_t pointerId,
Control::MouseButtonState buttonState,
const unsigned int pointerUpdateKind,
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
Expand Down Expand Up @@ -392,6 +414,40 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}

SetEndSelectionPoint(pixelPosition);

// 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.
{
// 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 auto height = _core->ViewHeight() * _core->FontSize().Height;
const auto cursorBelowBottomDist = pixelPosition.Y - height;
const auto cursorAboveTopDist = -1 * pixelPosition.Y;

constexpr auto MinAutoScrollDist = 2.0; // Arbitrary value
auto newAutoScrollVelocity = 0.0;
if (cursorBelowBottomDist > MinAutoScrollDist)
{
newAutoScrollVelocity = _getAutoScrollSpeed(cursorBelowBottomDist);
}
else if (cursorAboveTopDist > MinAutoScrollDist)
{
newAutoScrollVelocity = -1.0 * _getAutoScrollSpeed(cursorAboveTopDist);
}

if (newAutoScrollVelocity != 0)
{
_tryStartAutoScroll(pointerId, pixelPosition, newAutoScrollVelocity);
}
else
{
_tryStopAutoScroll(pointerId);
}
}
}

_core->SetHoveredCell(terminalPosition.to_core_point());
Expand Down Expand Up @@ -433,7 +489,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}
}

void ControlInteractivity::PointerReleased(const uint32_t /*pointerId*/,
void ControlInteractivity::PointerReleased(const uint32_t pointerId,
Control::MouseButtonState buttonState,
const unsigned int pointerUpdateKind,
const ::Microsoft::Terminal::Core::ControlKeyStates modifiers,
Expand Down Expand Up @@ -464,6 +520,7 @@ namespace winrt::Microsoft::Terminal::Control::implementation
}

_singleClickTouchdownPos = std::nullopt;
_tryStopAutoScroll(pointerId);
}

void ControlInteractivity::TouchReleased()
Expand Down Expand Up @@ -755,4 +812,101 @@ namespace winrt::Microsoft::Terminal::Control::implementation
{
return _core->Settings().ProfileSource() == L"Windows.Terminal.Wsl";
}

// Method Description:
// - Calculates speed of single axis of auto scrolling. It has to allow for both
// fast and precise selection.
// Arguments:
// - cursorDistanceFromBorder: distance from viewport border to cursor, in pixels. Must be non-negative.
// Return Value:
// - positive speed in characters / sec
double ControlInteractivity::_getAutoScrollSpeed(double cursorDistanceFromBorder) const
{
// The numbers below just feel well, feel free to change.
// TODO: Maybe account for space beyond border that user has available
return std::pow(cursorDistanceFromBorder, 2.0) / 25.0 + 2.0;
}

// Method Description:
// - Starts new pointer related auto scroll behavior, or continues existing one.
// Does nothing when there is already auto scroll associated with another pointer.
// Arguments:
// - pointerId, point: info about pointer that causes auto scroll. Pointer's position
// is later used to update selection.
// - scrollVelocity: target velocity of scrolling in characters / sec
void ControlInteractivity::_tryStartAutoScroll(const uint32_t pointerId, const Core::Point& point, const double scrollVelocity)
{
// Allow only one pointer at the time
if (!_autoScrollingPointerId ||
_autoScrollingPointerId == pointerId)
{
_autoScrollingPointerId = pointerId;
_autoScrollingPointerPoint = point;
_autoScrollVelocity = scrollVelocity;

// If this is first time the auto scroll update is about to be called,
// kick-start it by initializing its time delta as if it started now
if (!_lastAutoScrollUpdateTime)
{
_lastAutoScrollUpdateTime = std::chrono::high_resolution_clock::now();
}

// Apparently this check is not necessary but greatly improves performance
if (!_autoScrollTimer.IsRunning())
{
_autoScrollTimer.Start();
}
}
}

// Method Description:
// - Stops auto scroll if it's active and is associated with supplied pointer id.
// Arguments:
// - pointerId: id of pointer for which to stop auto scroll
void ControlInteractivity::_tryStopAutoScroll(const uint32_t pointerId)
{
if (_autoScrollingPointerId &&
pointerId == _autoScrollingPointerId)
{
_autoScrollingPointerId = std::nullopt;
_autoScrollingPointerPoint = std::nullopt;
_autoScrollVelocity = 0;
_lastAutoScrollUpdateTime = std::nullopt;

// Apparently this check is not necessary but greatly improves performance
if (_autoScrollTimer.IsRunning())
{
_autoScrollTimer.Stop();
}
}
}

// Method Description:
// - Called continuously to gradually scroll viewport when user is mouse
// selecting outside it (to 'follow' the cursor).
// Arguments:
// - none
void ControlInteractivity::_updateAutoScroll(const Windows::Foundation::IInspectable& /* sender */,
const Windows::Foundation::IInspectable& /* e */)
{
if (_autoScrollVelocity != 0)
{
const auto timeNow = std::chrono::high_resolution_clock::now();

if (_lastAutoScrollUpdateTime)
{
static constexpr auto microSecPerSec = 1000000.0;
const auto deltaTime = std::chrono::duration_cast<std::chrono::microseconds>(timeNow - *_lastAutoScrollUpdateTime).count() / microSecPerSec;
UpdateScrollbar(_core->ScrollOffset() + _autoScrollVelocity * deltaTime);

if (_autoScrollingPointerPoint)
{
SetEndSelectionPoint(*_autoScrollingPointerPoint);
}
}

_lastAutoScrollUpdateTime = timeNow;
}
}

}
15 changes: 15 additions & 0 deletions src/cascadia/TerminalControl/ControlInteractivity.h
Original file line number Diff line number Diff line change
Expand Up @@ -144,8 +144,23 @@ namespace winrt::Microsoft::Terminal::Control::implementation

bool _focused{ false };

// Auto scroll occurs when user, while selecting, drags cursor outside
// viewport. View is then scrolled to 'follow' the cursor.
double _autoScrollVelocity;
std::optional<uint32_t> _autoScrollingPointerId;
std::optional<Core::Point> _autoScrollingPointerPoint;
Windows::System::DispatcherQueueTimer _autoScrollTimer{ nullptr };
std::optional<std::chrono::high_resolution_clock::time_point> _lastAutoScrollUpdateTime;
bool _pointerPressedInBounds{ false };

void _tryStartAutoScroll(const uint32_t id, const Core::Point& point, const double scrollVelocity);
void _tryStopAutoScroll(const uint32_t pointerId);
void _updateAutoScroll(const Windows::Foundation::IInspectable& sender, const Windows::Foundation::IInspectable& e);
double _getAutoScrollSpeed(double cursorDistanceFromBorder) const;

void _createInteractivityTimers();
void _destroyInteractivityTimers();

unsigned int _numberOfClicks(Core::Point clickPos, Timestamp clickTime);
void _updateSystemParameterSettings() noexcept;

Expand Down
Loading

0 comments on commit 0bff156

Please sign in to comment.