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

Display Emojis, Kaomojis, and symbols while in IME mode #4688

Merged
9 commits merged into from
Feb 26, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
97 changes: 62 additions & 35 deletions src/cascadia/TerminalControl/TSFInputControl.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,8 @@ using namespace winrt::Windows::UI::Xaml;
namespace winrt::Microsoft::Terminal::TerminalControl::implementation
{
TSFInputControl::TSFInputControl() :
_editContext{ nullptr }
_editContext{ nullptr },
_inComposition{ false }
{
_Create();
}
Expand Down Expand Up @@ -138,32 +139,31 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// Get the cursor position in text buffer position
auto cursorArgs = winrt::make_self<CursorPositionEventArgs>();
_CurrentCursorPositionHandlers(*this, *cursorArgs);
const COORD cursorPos = { gsl::narrow_cast<SHORT>(cursorArgs->CurrentPosition().X), gsl::narrow_cast<SHORT>(cursorArgs->CurrentPosition().Y) };
const COORD cursorPos = { ::base::ClampedNumeric<short>(cursorArgs->CurrentPosition().X), ::base::ClampedNumeric<short>(cursorArgs->CurrentPosition().Y) };

// 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);

const float fontWidth = fontArgs->FontSize().Width;
const float fontHeight = fontArgs->FontSize().Height;
const auto fontWidth = fontArgs->FontSize().Width;
const auto fontHeight = fontArgs->FontSize().Height;

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

// Convert from client coordinate to screen coordinate by adding window position
THROW_IF_FAILED(ShortAdd(clientCursorPos.X, gsl::narrow_cast<SHORT>(windowBounds.X), &screenCursorPos.X));
THROW_IF_FAILED(ShortAdd(clientCursorPos.Y, gsl::narrow_cast<SHORT>(windowBounds.Y), &screenCursorPos.Y));
COORD screenCursorPos;
screenCursorPos.X = ::base::ClampAdd(clientCursorPos.X, ::base::ClampedNumeric<short>(windowBounds.X));
screenCursorPos.Y = ::base::ClampAdd(clientCursorPos.Y, ::base::ClampedNumeric<short>(windowBounds.Y));

// get any offset (margin + tabs, etc..) of the control within the window
const auto offsetPoint = this->TransformToVisual(nullptr).TransformPoint(winrt::Windows::Foundation::Point(0, 0));

// add the margin offsets if any
const auto currentMargin = this->Margin();
THROW_IF_FAILED(ShortAdd(screenCursorPos.X, gsl::narrow_cast<SHORT>(offsetPoint.X), &screenCursorPos.X));
THROW_IF_FAILED(ShortAdd(screenCursorPos.Y, gsl::narrow_cast<SHORT>(offsetPoint.Y), &screenCursorPos.Y));
screenCursorPos.X = ::base::ClampAdd(screenCursorPos.X, ::base::ClampedNumeric<short>(offsetPoint.X));
screenCursorPos.Y = ::base::ClampAdd(screenCursorPos.Y, ::base::ClampedNumeric<short>(offsetPoint.Y));

// Get scale factor for view
const double scaleFactor = DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();
Expand All @@ -178,12 +178,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation

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

// width is cursor to end of canvas
_textBlock.Width(200); // TODO GitHub #3640: Determine proper Width
Copy link
Member

Choose a reason for hiding this comment

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

Since this got removed, what happens if your composition text is larger than the width of the buffer? Do we just start writing off the end of the window?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup, it'll start writing off the end of the window, which actually is the current behavior. Until text wrap-around is implemented, the composition text will always write off the end of the window. Removing this just allows the user to be able to see as much composition text as the window is wide (or until the user resizes the window to make it wider).

_textBlock.Height(fontHeight);

// calculate FontSize in pixels from DIPs
const double fontSizePx = (fontHeight * 72) / USER_DEFAULT_SCREEN_DPI;
_textBlock.FontSize(fontSizePx);
Expand All @@ -201,8 +198,7 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// - <none>
void TSFInputControl::_compositionStartedHandler(CoreTextEditContext sender, CoreTextCompositionStartedEventArgs const& /*args*/)
{
_canvas.Visibility(Visibility::Visible);
_textBlock.Visibility(Visibility::Visible);
_inComposition = true;
}

// Method Description:
Expand All @@ -215,30 +211,19 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
// - <none>
void TSFInputControl::_compositionCompletedHandler(CoreTextEditContext sender, CoreTextCompositionCompletedEventArgs const& /*args*/)
{
_inComposition = false;

// only need to do work if the current buffer has text
if (!_inputBuffer.empty())
{
// call event handler with data handled by parent
_compositionCompletedHandlers(_inputBuffer);

// clear the buffer for next round
const auto bufferLength = gsl::narrow_cast<int32_t>(_inputBuffer.length());
_inputBuffer.clear();
_textBlock.Text(L"");

// indicate text is now 0
_editContext.NotifyTextChanged({ 0, bufferLength }, 0, { 0, 0 });

// hide the controls until composition starts again
_canvas.Visibility(Visibility::Collapsed);
_textBlock.Visibility(Visibility::Collapsed);
_SendAndClearText();
}
}

// Method Description:
// - Handler for FocusRemoved event by CoreEditContext responsible
// for removing focus for the TSFInputControl control accordingly
// when focus was forcibly removed from text input control. (TODO GitHub #3644)
// when focus was forcibly removed from text input control.
// NOTE: Documentation says application should handle this event
// Arguments:
// - sender: CoreTextEditContext sending the request. Not used in method.
Expand All @@ -265,7 +250,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation

try
{
const auto textRequested = _inputBuffer.substr(range.StartCaretPosition, static_cast<size_t>(range.EndCaretPosition) - static_cast<size_t>(range.StartCaretPosition));
const auto textEnd = ::base::ClampMin<size_t>(range.EndCaretPosition, _inputBuffer.length());
const auto length = ::base::ClampSub<size_t>(textEnd, range.StartCaretPosition);
const auto textRequested = _inputBuffer.substr(range.StartCaretPosition, length);

args.Request().Text(textRequested);
}
Expand Down Expand Up @@ -315,13 +302,24 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation

try
{
_canvas.Visibility(Visibility::Visible);
_textBlock.Visibility(Visibility::Visible);

const auto length = ::base::ClampSub<size_t>(range.EndCaretPosition, range.StartCaretPosition);
_inputBuffer = _inputBuffer.replace(
range.StartCaretPosition,
static_cast<size_t>(range.EndCaretPosition) - static_cast<size_t>(range.StartCaretPosition),
length,
text);

_textBlock.Text(_inputBuffer);

// If we receive tabbed IME input like emoji, kaomojis, and symbols, send it to the terminal immediately.
// They aren't composition, so we don't want to wait for the user to start and finish a composition to send the text.
if (!_inComposition)
{
_SendAndClearText();
}

// Notify the TSF that the update succeeded
args.Result(CoreTextTextUpdatingResult::Succeeded);
}
Expand All @@ -334,6 +332,35 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
}
}

// Method Description:
// - Sends the currently held text in the input buffer to the parent and
// clears the input buffer and text block for the next round of input.
// Then hides the text block control until the next time text received.
// Arguments:
// - <none>
// Return Value:
// - <none>
void TSFInputControl::_SendAndClearText()
{
// call event handler with data handled by parent
_compositionCompletedHandlers(_inputBuffer);
Copy link
Contributor

Choose a reason for hiding this comment

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

Dumb q: the textblock is relaid-out between these events, right? When composition "completes" without starting, that is?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Yup, the textblock actually seems to get relaid-out constantly between just about all events, such as TextUpdate, CompositionStarted, or CompositionCompleted events.


// clear the buffer for next round
const auto bufferLength = ::base::ClampedNumeric<int32_t>(_inputBuffer.length());
_inputBuffer.clear();
_textBlock.Text(L"");

// Leaving focus before NotifyTextChanged seems to guarantee that the next
// composition will send us a CompositionStarted event.
_editContext.NotifyFocusLeave();
_editContext.NotifyTextChanged({ 0, bufferLength }, 0, { 0, 0 });
_editContext.NotifyFocusEnter();

// hide the controls until text input starts again
_canvas.Visibility(Visibility::Collapsed);
_textBlock.Visibility(Visibility::Collapsed);
}

// Method Description:
// - Handler for FormatUpdating event by CoreEditContext responsible
// for handling different format updates for a particular range of text.
Expand Down
3 changes: 3 additions & 0 deletions src/cascadia/TerminalControl/TSFInputControl.h
Original file line number Diff line number Diff line change
Expand Up @@ -75,6 +75,9 @@ namespace winrt::Microsoft::Terminal::TerminalControl::implementation
std::wstring _inputBuffer;

void _Create();

bool _inComposition;
void _SendAndClearText();
};
}
namespace winrt::Microsoft::Terminal::TerminalControl::factory_implementation
Expand Down
10 changes: 5 additions & 5 deletions src/cascadia/WinRTUtils/inc/Utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,10 +28,10 @@ inline winrt::Windows::UI::Color ColorRefToColor(const COLORREF& colorref)
// - Rect scaled by scale
inline winrt::Windows::Foundation::Rect ScaleRect(winrt::Windows::Foundation::Rect rect, double scale)
{
const float scaleLocal = gsl::narrow_cast<float>(scale);
rect.X *= scaleLocal;
rect.Y *= scaleLocal;
rect.Width *= scaleLocal;
rect.Height *= scaleLocal;
const auto scaleLocal = base::ClampedNumeric<float>(scale);
rect.X = base::ClampMul(rect.X, scaleLocal);
rect.Y = base::ClampMul(rect.Y, scaleLocal);
rect.Width = base::ClampMul(rect.Width, scaleLocal);
rect.Height = base::ClampMul(rect.Height, scaleLocal);
return rect;
}