Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Redraw TSFInputControl when Terminal cursor updates #5135

Merged
10 commits merged into from
Mar 30, 2020
127 changes: 92 additions & 35 deletions src/cascadia/TerminalControl/TSFInputControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,13 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
TSFInputControl::TSFInputControl() :
_editContext{ nullptr },
_inComposition{ false },
_activeTextStart{ 0 }
_activeTextStart{ 0 },
_focused{ false },
_currentTerminalCursorPos{ 0, 0 },
_currentCanvasWidth{ 0.0 },
_currentTextBlockHeight{ 0.0 },
_currentTextBounds{ 0, 0, 0, 0 },
_currentControlBounds{ 0, 0, 0, 0 }
{
InitializeComponent();

Expand Down Expand Up @@ -58,7 +64,11 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
{
// Explicitly disconnect the LayoutRequested handler -- it can cause problems during application teardown.
// See GH#4159 for more info.
// Also disconnect compositionCompleted and textUpdating explicitly. It seems to occasionally cause problems if
// a composition is active during application teardown.
_layoutRequestedRevoker.revoke();
_compositionCompletedRevoker.revoke();
_textUpdatingRevoker.revoke();
}

// Method Description:
Expand All @@ -73,6 +83,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
if (_editContext != nullptr)
{
_editContext.NotifyFocusEnter();
_focused = true;
}
}

Expand All @@ -88,6 +99,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
if (_editContext != nullptr)
{
_editContext.NotifyFocusLeave();
_focused = false;
}
}

Expand All @@ -114,28 +126,52 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
}

// Method Description:
// - Handler for LayoutRequested event by CoreEditContext responsible
// for returning the current position the IME should be placed
// in screen coordinates on the screen. TSFInputControls internal
// XAML controls (TextBlock/Canvas) are also positioned and updated.
// NOTE: documentation says application should handle this event
// - Redraw the canvas if certain dimensions have changed since the last
// redraw. This includes the Terminal cursor position, the Canvas width, and the TextBlock height.
// Arguments:
// - sender: CoreTextEditContext sending the request.
// - args: CoreTextLayoutRequestedEventArgs to be updated with position information.
// - <none>
// Return Value:
// - <none>
void TSFInputControl::_layoutRequestedHandler(CoreTextEditContext sender, CoreTextLayoutRequestedEventArgs const& args)
void TSFInputControl::TryRedrawCanvas()
{
auto request = args.Request();

// Get window in screen coordinates, this is the entire window including tabs
const auto windowBounds = CoreWindow::GetForCurrentThread().Bounds();
if (!_focused)
{
return;
}

// Get the cursor position in text buffer position
auto cursorArgs = winrt::make_self<CursorPositionEventArgs>();
_CurrentCursorPositionHandlers(*this, *cursorArgs);
const COORD cursorPos = { ::base::ClampedNumeric<short>(cursorArgs->CurrentPosition().X), ::base::ClampedNumeric<short>(cursorArgs->CurrentPosition().Y) };

const double actualCanvasWidth = Canvas().ActualWidth();

const double actualTextBlockHeight = TextBlock().ActualHeight();

if (_currentTerminalCursorPos.X == cursorPos.X &&
_currentTerminalCursorPos.Y == cursorPos.Y &&
_currentCanvasWidth == actualCanvasWidth &&
_currentTextBlockHeight == actualTextBlockHeight)
{
return;
}

_currentTerminalCursorPos = cursorPos;
_currentCanvasWidth = actualCanvasWidth;
_currentTextBlockHeight = actualTextBlockHeight;

_RedrawCanvas();
}

// Method Description:
// - Redraw the Canvas and update the current Text Bounds and Control Bounds for
// the CoreTextEditContext.
// Arguments:
// - <none>
// Return Value:
// - <none>
void TSFInputControl::_RedrawCanvas()
{
// Get Font Info as we use this is the pixel size for characters in the display
auto fontArgs = winrt::make_self<FontInfoEventArgs>();
_CurrentFontInfoHandlers(*this, *fontArgs);
Expand All @@ -145,8 +181,27 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation

// Convert text buffer cursor position to client coordinate position within the window
COORD clientCursorPos;
clientCursorPos.X = ::base::ClampMul(cursorPos.X, ::base::ClampedNumeric<short>(fontWidth));
clientCursorPos.Y = ::base::ClampMul(cursorPos.Y, ::base::ClampedNumeric<short>(fontHeight));
clientCursorPos.X = ::base::ClampMul(_currentTerminalCursorPos.X, ::base::ClampedNumeric<short>(fontWidth));
clientCursorPos.Y = ::base::ClampMul(_currentTerminalCursorPos.Y, ::base::ClampedNumeric<short>(fontHeight));

// position textblock to cursor position
Canvas().SetLeft(TextBlock(), clientCursorPos.X);
Canvas().SetTop(TextBlock(), ::base::ClampedNumeric<double>(clientCursorPos.Y));

// calculate FontSize in pixels from DPIs
const double fontSizePx = (fontHeight * 72) / USER_DEFAULT_SCREEN_DPI;
TextBlock().FontSize(fontSizePx);
TextBlock().FontFamily(Media::FontFamily(fontArgs->FontFace()));

const auto widthToTerminalEnd = _currentCanvasWidth - ::base::ClampedNumeric<double>(clientCursorPos.X);
// Make sure that we're setting the MaxWidth to a positive number - a
// negative number here will crash us in mysterious ways with a useless
// stack trace
const auto newMaxWidth = std::max<double>(0.0, widthToTerminalEnd);
TextBlock().MaxWidth(newMaxWidth);

// Get window in screen coordinates, this is the entire window including tabs
const auto windowBounds = CoreWindow::GetForCurrentThread().Bounds();

// Convert from client coordinate to screen coordinate by adding window position
COORD screenCursorPos;
Expand All @@ -162,33 +217,35 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation

// Get scale factor for view
const double scaleFactor = DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();
const auto yOffset = ::base::ClampedNumeric<float>(_currentTextBlockHeight) - fontHeight;
const auto textBottom = ::base::ClampedNumeric<float>(screenCursorPos.Y) + yOffset;

// position textblock to cursor position
Canvas().SetLeft(TextBlock(), clientCursorPos.X);
Canvas().SetTop(TextBlock(), ::base::ClampedNumeric<double>(clientCursorPos.Y));
_currentTextBounds = ScaleRect(Rect(screenCursorPos.X, textBottom, 0, fontHeight), scaleFactor);
_currentControlBounds = ScaleRect(Rect(screenCursorPos.X, screenCursorPos.Y, 0, fontHeight), scaleFactor);
}

// calculate FontSize in pixels from DIPs
const double fontSizePx = (fontHeight * 72) / USER_DEFAULT_SCREEN_DPI;
TextBlock().FontSize(fontSizePx);
TextBlock().FontFamily(Media::FontFamily(fontArgs->FontFace()));
// Method Description:
// - Handler for LayoutRequested event by CoreEditContext responsible
// for returning the current position the IME should be placed
// in screen coordinates on the screen. TSFInputControls internal
// XAML controls (TextBlock/Canvas) are also positioned and updated.
// NOTE: documentation says application should handle this event
// Arguments:
// - sender: CoreTextEditContext sending the request.
// - args: CoreTextLayoutRequestedEventArgs to be updated with position information.
// Return Value:
// - <none>
void TSFInputControl::_layoutRequestedHandler(CoreTextEditContext sender, CoreTextLayoutRequestedEventArgs const& args)
{
auto request = args.Request();

const auto canvasActualWidth = Canvas().ActualWidth();
const auto widthToTerminalEnd = canvasActualWidth - ::base::ClampedNumeric<double>(clientCursorPos.X);
// Make sure that we're setting the MaxWidth to a positive number - a
// negative number here will crash us in mysterious ways with a useless
// stack trace
const auto newMaxWidth = std::max<double>(0.0, widthToTerminalEnd);
TextBlock().MaxWidth(newMaxWidth);
TryRedrawCanvas();

// Set the text block bounds
const auto yOffset = ::base::ClampedNumeric<float>(TextBlock().ActualHeight()) - fontHeight;
const auto textBottom = ::base::ClampedNumeric<float>(screenCursorPos.Y) + yOffset;
Rect selectionRect = Rect(screenCursorPos.X, textBottom, 0, fontHeight);
request.LayoutBounds().TextBounds(ScaleRect(selectionRect, scaleFactor));
request.LayoutBounds().TextBounds(_currentTextBounds);

// Set the control bounds of the whole control
Rect controlRect = Rect(screenCursorPos.X, screenCursorPos.Y, 0, fontHeight);
request.LayoutBounds().ControlBounds(ScaleRect(controlRect, scaleFactor));
request.LayoutBounds().ControlBounds(_currentControlBounds);
}

// Method Description:
Expand Down
9 changes: 9 additions & 0 deletions src/cascadia/TerminalControl/TSFInputControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void NotifyFocusEnter();
void NotifyFocusLeave();
void ClearBuffer();
void TryRedrawCanvas();

void Close();

Expand Down Expand Up @@ -73,6 +74,14 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
bool _inComposition;
size_t _activeTextStart;
void _SendAndClearText();
void _RedrawCanvas();
bool _focused;

COORD _currentTerminalCursorPos;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

should probably be a til::point nowadays

double _currentCanvasWidth;
double _currentTextBlockHeight;
winrt::Windows::Foundation::Rect _currentControlBounds;
winrt::Windows::Foundation::Rect _currentTextBounds;
};
}
namespace winrt::Microsoft::Terminal::TerminalControl::factory_implementation
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalControl/TSFInputControl.idl
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ namespace Microsoft.Terminal.TerminalControl
void NotifyFocusEnter();
void NotifyFocusLeave();
void ClearBuffer();
void TryRedrawCanvas();

void Close();
}
Expand Down
28 changes: 28 additions & 0 deletions src/cascadia/TerminalControl/TermControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -611,6 +611,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
auto pfnScrollPositionChanged = std::bind(&TermControl::_TerminalScrollPositionChanged, this, std::placeholders::_1, std::placeholders::_2, std::placeholders::_3);
_terminal->SetScrollPositionChangedCallback(pfnScrollPositionChanged);

auto pfnTerminalCursorPositionChanged = std::bind(&TermControl::_TerminalCursorPositionChanged, this);
_terminal->SetCursorPositionChangedCallback(pfnTerminalCursorPositionChanged);

static constexpr auto AutoScrollUpdateInterval = std::chrono::microseconds(static_cast<int>(1.0 / 30.0 * 1000000));
_autoScrollTimer.Interval(AutoScrollUpdateInterval);
_autoScrollTimer.Tick({ get_weak(), &TermControl::_UpdateAutoScroll });
Expand Down Expand Up @@ -1774,6 +1777,31 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
}
}

// Method Description:
// - Tells TSFInputControl to redraw the Canvas/TextBlock so it'll update
// to be where the current cursor position is.
// Arguments:
// - N/A
winrt::fire_and_forget TermControl::_TerminalCursorPositionChanged()
{
if (_closing.load())
{
return;
}

auto weakThis{ get_weak() };

co_await winrt::resume_foreground(Dispatcher());

if (auto control{ weakThis.get() })
carlos-zamora marked this conversation as resolved.
Show resolved Hide resolved
{
if (!_closing.load())
{
TSFInputControl().TryRedrawCanvas();
}
}
}

hstring TermControl::Title()
{
if (!_initializedTerminal)
Expand Down
1 change: 1 addition & 0 deletions src/cascadia/TerminalControl/TermControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
void _DoResize(const double newWidth, const double newHeight);
void _TerminalTitleChanged(const std::wstring_view& wstr);
winrt::fire_and_forget _TerminalScrollPositionChanged(const int viewTop, const int viewHeight, const int bufferSize);
winrt::fire_and_forget _TerminalCursorPositionChanged();

void _MouseScrollHandler(const double delta, Windows::UI::Input::PointerPoint const& pointerPoint);
void _MouseZoomHandler(const double delta);
Expand Down
15 changes: 15 additions & 0 deletions src/cascadia/TerminalCore/Terminal.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -704,6 +704,8 @@ void Terminal::_AdjustCursorPosition(const COORD proposedPosition)
_buffer->GetRenderTarget().TriggerRedrawAll();
_NotifyScrollEvent();
}

_NotifyTerminalCursorPositionChanged();
}

void Terminal::UserScrollViewport(const int viewTop)
Expand Down Expand Up @@ -736,6 +738,14 @@ try
}
CATCH_LOG()

void Terminal::_NotifyTerminalCursorPositionChanged() noexcept
{
if (_pfnCursorPositionChanged)
{
_pfnCursorPositionChanged();
}
}

void Terminal::SetWriteInputCallback(std::function<void(std::wstring&)> pfn) noexcept
{
_pfnWriteInput.swap(pfn);
Expand All @@ -751,6 +761,11 @@ void Terminal::SetScrollPositionChangedCallback(std::function<void(const int, co
_pfnScrollPositionChanged.swap(pfn);
}

void Terminal::SetCursorPositionChangedCallback(std::function<void()> pfn) noexcept
{
_pfnCursorPositionChanged.swap(pfn);
}

// Method Description:
// - Allows setting a callback for when the background color is changed
// Arguments:
Expand Down
4 changes: 4 additions & 0 deletions src/cascadia/TerminalCore/Terminal.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -168,6 +168,7 @@ class Microsoft::Terminal::Core::Terminal final :
void SetWriteInputCallback(std::function<void(std::wstring&)> pfn) noexcept;
void SetTitleChangedCallback(std::function<void(const std::wstring_view&)> pfn) noexcept;
void SetScrollPositionChangedCallback(std::function<void(const int, const int, const int)> pfn) noexcept;
void SetCursorPositionChangedCallback(std::function<void()> pfn) noexcept;
void SetBackgroundCallback(std::function<void(const uint32_t)> pfn) noexcept;

void SetCursorOn(const bool isOn) noexcept;
Expand All @@ -194,6 +195,7 @@ class Microsoft::Terminal::Core::Terminal final :
std::function<void(const std::wstring_view&)> _pfnTitleChanged;
std::function<void(const int, const int, const int)> _pfnScrollPositionChanged;
std::function<void(const uint32_t)> _pfnBackgroundColorChanged;
std::function<void()> _pfnCursorPositionChanged;

std::unique_ptr<::Microsoft::Console::VirtualTerminal::StateMachine> _stateMachine;
std::unique_ptr<::Microsoft::Console::VirtualTerminal::TerminalInput> _terminalInput;
Expand Down Expand Up @@ -266,6 +268,8 @@ class Microsoft::Terminal::Core::Terminal final :

void _NotifyScrollEvent() noexcept;

void _NotifyTerminalCursorPositionChanged() noexcept;

#pragma region TextSelection
// These methods are defined in TerminalSelection.cpp
std::vector<SMALL_RECT> _GetSelectionRects() const noexcept;
Expand Down