wpf: port selection changes from TermControl, add multi-click selection (#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.
This commit is contained in:
parent
7f886620b3
commit
a6b2e7f612
|
@ -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))
|
||||
{
|
||||
|
@ -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();
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
cursorPosition.X /= fontSize.X;
|
||||
cursorPosition.Y /= fontSize.Y;
|
||||
|
||||
this->_terminal->SetSelectionAnchor(cursorPosition);
|
||||
this->_terminal->SetBlockSelection(altPressed);
|
||||
|
||||
const auto clickCount{ _NumberOfClicks(cursorPosition, std::chrono::steady_clock::now()) };
|
||||
|
||||
// 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;
|
||||
|
@ -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;
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
|
|
@ -75,6 +75,12 @@ private:
|
|||
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);
|
||||
|
@ -95,6 +101,7 @@ private:
|
|||
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;
|
||||
|
|
|
@ -839,7 +839,11 @@ void Terminal::_NotifyTerminalCursorPositionChanged() noexcept
|
|||
{
|
||||
if (_pfnCursorPositionChanged)
|
||||
{
|
||||
_pfnCursorPositionChanged();
|
||||
try
|
||||
{
|
||||
_pfnCursorPositionChanged();
|
||||
}
|
||||
CATCH_LOG();
|
||||
}
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue