334 lines
10 KiB
C++
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));
|
|
}
|