terminal/src/host/scrolling.cpp

334 lines
10 KiB
C++

// Copyright (c) Microsoft Corporation.
// Licensed under the MIT license.
#include "precomp.h"
#include "scrolling.hpp"
#include "selection.hpp"
#include "../interactivity/inc/ServiceLocator.hpp"
using Microsoft::Console::VirtualTerminal::StateMachine;
using namespace Microsoft::Console::Interactivity;
using namespace Microsoft::Console::Types;
ULONG Scrolling::s_ucWheelScrollLines = 0;
ULONG Scrolling::s_ucWheelScrollChars = 0;
void Scrolling::s_UpdateSystemMetrics()
{
s_ucWheelScrollLines = ServiceLocator::LocateSystemConfigurationProvider()->GetNumberOfWheelScrollLines();
s_ucWheelScrollChars = ServiceLocator::LocateSystemConfigurationProvider()->GetNumberOfWheelScrollCharacters();
}
bool Scrolling::s_IsInScrollMode()
{
const CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
return WI_IsFlagSet(gci.Flags, CONSOLE_SCROLLING);
}
void Scrolling::s_DoScroll()
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
IConsoleWindow* const pWindow = ServiceLocator::LocateConsoleWindow();
if (!s_IsInScrollMode())
{
// clear any selection we may have -- can't scroll and select at the same time
Selection::Instance().ClearSelection();
WI_SetFlag(gci.Flags, CONSOLE_SCROLLING);
if (pWindow != nullptr)
{
pWindow->UpdateWindowText();
}
}
}
void Scrolling::s_ClearScroll()
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
IConsoleWindow* const pWindow = ServiceLocator::LocateConsoleWindow();
WI_ClearFlag(gci.Flags, CONSOLE_SCROLLING);
if (pWindow != nullptr)
{
pWindow->UpdateWindowText();
}
}
void Scrolling::s_ScrollIfNecessary(const SCREEN_INFORMATION& ScreenInfo)
{
IConsoleWindow* pWindow = ServiceLocator::LocateConsoleWindow();
FAIL_FAST_IF_NULL(pWindow);
Selection* const pSelection = &Selection::Instance();
if (pSelection->IsInSelectingState() && pSelection->IsMouseButtonDown())
{
POINT CursorPos;
if (!pWindow->GetCursorPosition(&CursorPos))
{
return;
}
RECT ClientRect;
if (!pWindow->GetClientRectangle(&ClientRect))
{
return;
}
pWindow->MapPoints((LPPOINT)&ClientRect, 2);
if (!(s_IsPointInRectangle(&ClientRect, CursorPos)))
{
pWindow->ConvertScreenToClient(&CursorPos);
COORD MousePosition;
MousePosition.X = (SHORT)CursorPos.x;
MousePosition.Y = (SHORT)CursorPos.y;
COORD coordFontSize = ScreenInfo.GetScreenFontSize();
MousePosition.X /= coordFontSize.X;
MousePosition.Y /= coordFontSize.Y;
MousePosition.X += ScreenInfo.GetViewport().Left();
MousePosition.Y += ScreenInfo.GetViewport().Top();
pSelection->ExtendSelection(MousePosition);
}
}
}
void Scrolling::s_HandleMouseWheel(_In_ bool isMouseWheel,
_In_ bool isMouseHWheel,
_In_ short wheelDelta,
_In_ bool hasShift,
SCREEN_INFORMATION& ScreenInfo)
{
COORD NewOrigin = ScreenInfo.GetViewport().Origin();
// s_ucWheelScrollLines == 0 means that it is turned off.
if (isMouseWheel && s_ucWheelScrollLines > 0)
{
// Rounding could cause this to be zero if gucWSL is bigger than 240 or so.
ULONG const ulActualDelta = std::max(WHEEL_DELTA / s_ucWheelScrollLines, 1ul);
// If we change direction we need to throw away any remainder we may have in the other direction.
if ((ScreenInfo.WheelDelta > 0) == (wheelDelta > 0))
{
ScreenInfo.WheelDelta += wheelDelta;
}
else
{
ScreenInfo.WheelDelta = wheelDelta;
}
if ((ULONG)abs(ScreenInfo.WheelDelta) >= ulActualDelta)
{
/*
* By default, SHIFT + WM_MOUSEWHEEL will scroll 1/2 the
* screen size. A ScrollScale of 1 indicates 1/2 the screen
* size. This value can be modified in the registry.
*/
SHORT delta = 1;
if (hasShift)
{
delta = gsl::narrow<SHORT>(std::max((ScreenInfo.GetViewport().Height() * ScreenInfo.ScrollScale) / 2, 1u));
// Account for scroll direction changes by adjusting delta if there was a direction change.
delta *= (ScreenInfo.WheelDelta < 0 ? -1 : 1);
ScreenInfo.WheelDelta %= delta;
}
else
{
delta *= (ScreenInfo.WheelDelta / (short)ulActualDelta);
ScreenInfo.WheelDelta %= ulActualDelta;
}
NewOrigin.Y -= delta;
const COORD coordBufferSize = ScreenInfo.GetBufferSize().Dimensions();
if (NewOrigin.Y < 0)
{
NewOrigin.Y = 0;
}
else if (NewOrigin.Y + ScreenInfo.GetViewport().Height() > coordBufferSize.Y)
{
NewOrigin.Y = coordBufferSize.Y - ScreenInfo.GetViewport().Height();
}
LOG_IF_FAILED(ScreenInfo.SetViewportOrigin(true, NewOrigin, false));
}
}
else if (isMouseHWheel && s_ucWheelScrollChars > 0)
{
ULONG const ulActualDelta = std::max(WHEEL_DELTA / s_ucWheelScrollChars, 1ul);
if ((ScreenInfo.HWheelDelta > 0) == (wheelDelta > 0))
{
ScreenInfo.HWheelDelta += wheelDelta;
}
else
{
ScreenInfo.HWheelDelta = wheelDelta;
}
if ((ULONG)abs(ScreenInfo.HWheelDelta) >= ulActualDelta)
{
SHORT delta = 1;
if (hasShift)
{
delta = std::max(ScreenInfo.GetViewport().RightInclusive(), 1i16);
}
delta *= (ScreenInfo.HWheelDelta / (short)ulActualDelta);
ScreenInfo.HWheelDelta %= ulActualDelta;
NewOrigin.X += delta;
const COORD coordBufferSize = ScreenInfo.GetBufferSize().Dimensions();
if (NewOrigin.X < 0)
{
NewOrigin.X = 0;
}
else if (NewOrigin.X + ScreenInfo.GetViewport().Width() > coordBufferSize.X)
{
NewOrigin.X = coordBufferSize.X - ScreenInfo.GetViewport().Width();
}
LOG_IF_FAILED(ScreenInfo.SetViewportOrigin(true, NewOrigin, false));
}
}
}
bool Scrolling::s_HandleKeyScrollingEvent(const INPUT_KEY_INFO* const pKeyInfo)
{
CONSOLE_INFORMATION& gci = ServiceLocator::LocateGlobals().getConsoleInformation();
IConsoleWindow* pWindow = ServiceLocator::LocateConsoleWindow();
FAIL_FAST_IF_NULL(pWindow);
const WORD VirtualKeyCode = pKeyInfo->GetVirtualKey();
const bool fIsCtrlPressed = pKeyInfo->IsCtrlPressed();
const bool fIsEditLineEmpty = CommandLine::Instance().IsEditLineEmpty();
// If escape, enter or ctrl-c, cancel scroll.
if (VirtualKeyCode == VK_ESCAPE ||
VirtualKeyCode == VK_RETURN ||
(VirtualKeyCode == 'C' && fIsCtrlPressed))
{
Scrolling::s_ClearScroll();
}
else
{
WORD ScrollCommand;
BOOL Horizontal = FALSE;
switch (VirtualKeyCode)
{
case VK_UP:
{
ScrollCommand = SB_LINEUP;
break;
}
case VK_DOWN:
{
ScrollCommand = SB_LINEDOWN;
break;
}
case VK_LEFT:
{
ScrollCommand = SB_LINEUP;
Horizontal = TRUE;
break;
}
case VK_RIGHT:
{
ScrollCommand = SB_LINEDOWN;
Horizontal = TRUE;
break;
}
case VK_NEXT:
{
ScrollCommand = SB_PAGEDOWN;
break;
}
case VK_PRIOR:
{
ScrollCommand = SB_PAGEUP;
break;
}
case VK_END:
{
if (fIsCtrlPressed)
{
if (fIsEditLineEmpty)
{
// Ctrl-End when edit line is empty will scroll to last line in the buffer.
gci.GetActiveOutputBuffer().MakeCurrentCursorVisible();
return true;
}
else
{
// If edit line is non-empty, we won't handle this so it can modify the edit line (trim characters to end of line from cursor pos).
return false;
}
}
else
{
ScrollCommand = SB_PAGEDOWN;
Horizontal = TRUE;
}
break;
}
case VK_HOME:
{
if (fIsCtrlPressed)
{
if (fIsEditLineEmpty)
{
// Ctrl-Home when edit line is empty will scroll to top of buffer.
ScrollCommand = SB_TOP;
}
else
{
// If edit line is non-empty, we won't handle this so it can modify the edit line (trim characters to beginning of line from cursor pos).
return false;
}
}
else
{
ScrollCommand = SB_PAGEUP;
Horizontal = TRUE;
}
break;
}
case VK_SHIFT:
case VK_CONTROL:
case VK_MENU:
{
return true;
}
default:
{
pWindow->SendNotifyBeep();
return true;
}
}
if (Horizontal)
{
pWindow->HorizontalScroll(ScrollCommand, 0);
}
else
{
pWindow->VerticalScroll(ScrollCommand, 0);
}
}
return true;
}
BOOL Scrolling::s_IsPointInRectangle(const RECT* const prc, const POINT pt)
{
return ((pt.x >= prc->left) && (pt.x < prc->right) &&
(pt.y >= prc->top) && (pt.y < prc->bottom));
}