Skip to content

Commit

Permalink
wpf: port selection changes from TermControl, add multi-click selecti…
Browse files Browse the repository at this point in the history
…on (#5374)

## Summary of the Pull Request

This pull request ports #5096 to WpfTerminalControl, bringing it in line with the selection mechanics in Terminal. It also introduces double- and triple-click selection and makes sure we clear the selection when we resize.

Please read #5096 for more details.

## Detailed Description of the Pull Request / Additional comments

This code is, largely, copy-and-pasted from TermControl with some updates to use `std::chrono` and `til::point`. I love `til::point`. A lot.

## Validation Steps Performed
Lots of manual selection.
  • Loading branch information
DHowett authored Apr 17, 2020
1 parent 7f88662 commit a6b2e7f
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 18 deletions.
88 changes: 71 additions & 17 deletions src/cascadia/PublicTerminalCore/HwndTerminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,9 @@ LRESULT CALLBACK HwndTerminal::HwndTerminalWndProc(
case WM_LBUTTONDOWN:
LOG_IF_FAILED(terminal->_StartSelection(lParam));
return 0;
case WM_LBUTTONUP:
terminal->_singleClickTouchdownPos = std::nullopt;
break;
case WM_MOUSEMOVE:
if (WI_IsFlagSet(wParam, MK_LBUTTON))
{
Expand Down Expand Up @@ -94,7 +97,8 @@ HwndTerminal::HwndTerminal(HWND parentHwnd) :
_uiaProvider{ nullptr },
_uiaProviderInitialized{ false },
_currentDpi{ USER_DEFAULT_SCREEN_DPI },
_pfnWriteCallback{ nullptr }
_pfnWriteCallback{ nullptr },
_multiClickTime{ 500 } // this will be overwritten by the windows system double-click time
{
HINSTANCE hInstance = wil::GetModuleInstanceHandle();

Expand Down Expand Up @@ -160,6 +164,8 @@ HRESULT HwndTerminal::Initialize()
_terminal->SetWriteInputCallback([=](std::wstring & input) noexcept { _WriteTextToConnection(input); });
localPointerToThread->EnablePainting();

_multiClickTime = std::chrono::milliseconds{ GetDoubleClickTime() };

return S_OK;
}

Expand Down Expand Up @@ -241,6 +247,8 @@ HRESULT HwndTerminal::Refresh(const SIZE windowSize, _Out_ COORD* dimensions)

auto lock = _terminal->LockForWriting();

_terminal->ClearSelection();

RETURN_IF_FAILED(_renderEngine->SetWindowSize(windowSize));

// Invalidate everything
Expand Down Expand Up @@ -346,26 +354,59 @@ void _stdcall TerminalUserScroll(void* terminal, int viewTop)
publicTerminal->_terminal->UserScrollViewport(viewTop);
}

const unsigned int HwndTerminal::_NumberOfClicks(til::point point, std::chrono::steady_clock::time_point timestamp) noexcept
{
// if click occurred at a different location or past the multiClickTimer...
const auto delta{ timestamp - _lastMouseClickTimestamp };
if (point != _lastMouseClickPos || delta > _multiClickTime)
{
// exit early. This is a single click.
_multiClickCounter = 1;
}
else
{
_multiClickCounter++;
}
return _multiClickCounter;
}

HRESULT HwndTerminal::_StartSelection(LPARAM lParam) noexcept
try
{
const bool altPressed = GetKeyState(VK_MENU) < 0;
COORD cursorPosition{
const til::point cursorPosition{
GET_X_LPARAM(lParam),
GET_Y_LPARAM(lParam),
};

const auto fontSize = this->_actualFont.GetSize();
auto lock = _terminal->LockForWriting();
const bool altPressed = GetKeyState(VK_MENU) < 0;
const til::size fontSize{ this->_actualFont.GetSize() };

RETURN_HR_IF(E_NOT_VALID_STATE, fontSize.X == 0);
RETURN_HR_IF(E_NOT_VALID_STATE, fontSize.Y == 0);
this->_terminal->SetBlockSelection(altPressed);

cursorPosition.X /= fontSize.X;
cursorPosition.Y /= fontSize.Y;
const auto clickCount{ _NumberOfClicks(cursorPosition, std::chrono::steady_clock::now()) };

this->_terminal->SetSelectionAnchor(cursorPosition);
this->_terminal->SetBlockSelection(altPressed);
// This formula enables the number of clicks to cycle properly between single-, double-, and triple-click.
// To increase the number of acceptable click states, simply increment MAX_CLICK_COUNT and add another if-statement
const unsigned int MAX_CLICK_COUNT = 3;
const auto multiClickMapper = clickCount > MAX_CLICK_COUNT ? ((clickCount + MAX_CLICK_COUNT - 1) % MAX_CLICK_COUNT) + 1 : clickCount;

if (multiClickMapper == 3)
{
_terminal->MultiClickSelection(cursorPosition / fontSize, ::Terminal::SelectionExpansionMode::Line);
}
else if (multiClickMapper == 2)
{
_terminal->MultiClickSelection(cursorPosition / fontSize, ::Terminal::SelectionExpansionMode::Word);
}
else
{
this->_terminal->ClearSelection();
_singleClickTouchdownPos = cursorPosition;

_lastMouseClickTimestamp = std::chrono::steady_clock::now();
_lastMouseClickPos = cursorPosition;
}
this->_renderer->TriggerSelection();

return S_OK;
Expand All @@ -375,20 +416,29 @@ CATCH_RETURN();
HRESULT HwndTerminal::_MoveSelection(LPARAM lParam) noexcept
try
{
COORD cursorPosition{
const til::point cursorPosition{
GET_X_LPARAM(lParam),
GET_Y_LPARAM(lParam),
};

const auto fontSize = this->_actualFont.GetSize();
auto lock = _terminal->LockForWriting();
const til::size fontSize{ this->_actualFont.GetSize() };

RETURN_HR_IF(E_NOT_VALID_STATE, fontSize.X == 0);
RETURN_HR_IF(E_NOT_VALID_STATE, fontSize.Y == 0);
RETURN_HR_IF(E_NOT_VALID_STATE, fontSize.area() == 0); // either dimension = 0, area == 0

cursorPosition.X /= fontSize.X;
cursorPosition.Y /= fontSize.Y;
if (this->_singleClickTouchdownPos)
{
const auto& touchdownPoint{ *this->_singleClickTouchdownPos };
const auto distance{ std::sqrtf(std::powf(cursorPosition.x<float>() - touchdownPoint.x<float>(), 2) + std::powf(cursorPosition.y<float>() - touchdownPoint.y<float>(), 2)) };
if (distance >= (std::min(fontSize.width(), fontSize.height()) / 4.f))
{
_terminal->SetSelectionAnchor(touchdownPoint / fontSize);
// stop tracking the touchdown point
_singleClickTouchdownPos = std::nullopt;
}
}

this->_terminal->SetSelectionEnd(cursorPosition);
this->_terminal->SetSelectionEnd(cursorPosition / fontSize);
this->_renderer->TriggerSelection();

return S_OK;
Expand Down Expand Up @@ -522,6 +572,10 @@ HRESULT _stdcall TerminalResize(void* terminal, COORD dimensions)
{
const auto publicTerminal = static_cast<const HwndTerminal*>(terminal);

auto lock = publicTerminal->_terminal->LockForWriting();
publicTerminal->_terminal->ClearSelection();
publicTerminal->_renderer->TriggerRedrawAll();

return publicTerminal->_terminal->UserResize(dimensions);
}

Expand Down
7 changes: 7 additions & 0 deletions src/cascadia/PublicTerminalCore/HwndTerminal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,12 @@ struct HwndTerminal : ::Microsoft::Console::Types::IControlAccessibilityInfo
std::unique_ptr<::Microsoft::Console::Render::Renderer> _renderer;
std::unique_ptr<::Microsoft::Console::Render::DxEngine> _renderEngine;

std::chrono::milliseconds _multiClickTime;
unsigned int _multiClickCounter{};
std::chrono::steady_clock::time_point _lastMouseClickTimestamp{};
std::optional<til::point> _lastMouseClickPos;
std::optional<til::point> _singleClickTouchdownPos;

friend HRESULT _stdcall CreateTerminal(HWND parentHwnd, _Out_ void** hwnd, _Out_ void** terminal);
friend HRESULT _stdcall TerminalResize(void* terminal, COORD dimensions);
friend void _stdcall TerminalDpiChanged(void* terminal, int newDpi);
Expand All @@ -95,6 +101,7 @@ struct HwndTerminal : ::Microsoft::Console::Types::IControlAccessibilityInfo
void _PasteTextFromClipboard() noexcept;
void _StringPaste(const wchar_t* const pData) noexcept;

const unsigned int _NumberOfClicks(til::point clickPos, std::chrono::steady_clock::time_point clickTime) noexcept;
HRESULT _StartSelection(LPARAM lParam) noexcept;
HRESULT _MoveSelection(LPARAM lParam) noexcept;
IRawElementProviderSimple* _GetUiaProvider() noexcept;
Expand Down
6 changes: 5 additions & 1 deletion src/cascadia/TerminalCore/Terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -839,7 +839,11 @@ void Terminal::_NotifyTerminalCursorPositionChanged() noexcept
{
if (_pfnCursorPositionChanged)
{
_pfnCursorPositionChanged();
try
{
_pfnCursorPositionChanged();
}
CATCH_LOG();
}
}

Expand Down

0 comments on commit a6b2e7f

Please sign in to comment.