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

#459 IME UI does not follow the cursor in Windows Terminal #1919

Merged
merged 33 commits into from
Nov 22, 2019
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
33 commits
Select commit Hold shift + click to select a range
b57310d
Add TSF3.0 IME UserControl
Jun 20, 2019
17b624d
Trying to figure out why Surface Pro 6 isn't working right
Jul 8, 2019
1a69d5c
Add documentation, getting ready for review
Jul 9, 2019
77c6197
Minor code formatting updates
Jul 9, 2019
466fd96
More changes to get ready for draft PR
Jul 9, 2019
078c69f
Add Exception handling around std::wstring operations
Jul 9, 2019
3a097ca
Add TSF3.0 IME UserControl
Jun 20, 2019
578578c
Trying to figure out why Surface Pro 6 isn't working right
Jul 8, 2019
882e97f
Add documentation, getting ready for review
Jul 9, 2019
28e99d0
Minor code formatting updates
Jul 9, 2019
b890187
More changes to get ready for draft PR
Jul 9, 2019
4cec73a
Add Exception handling around std::wstring operations
Jul 9, 2019
8309559
Merge branch 'dev/philnach/459-TSF30-IME_2' of https://github.com/phi…
Jul 10, 2019
f309dfe
Removing Outputdebugstrings
Jul 10, 2019
2afe5f3
Apply Formatting fixes that caused code formatting failures
Jul 11, 2019
ebf3d8c
The FontWidth and Height properties weren't used on the TSFInputControl
Jul 11, 2019
41a373b
applying const to where appropriate
philnach Jul 25, 2019
c3c42e9
Merging with upstream
Oct 23, 2019
35c456a
Fix incorrect merge
Oct 23, 2019
f9e3ab9
More const changes
Nov 19, 2019
714d00a
Merge branch 'master' into dev/philnach/459-TSF30-IME_2
Nov 19, 2019
39aea27
Code Analysis Errors
Nov 19, 2019
cec486a
Remove TAB OFFSET Hack
Nov 20, 2019
e781a85
Add GitHub issues for all TODOs in TSFInputControl
Nov 20, 2019
a1b5c3a
Add more TODO github linked issues
Nov 20, 2019
8354fc1
Fix Code Formatting errors
Nov 20, 2019
30bfc3e
Update src/cascadia/TerminalControl/TSFInputControl.cpp
philnach Nov 21, 2019
04d1a63
Move Color conversion to WinRT Utils library
Nov 21, 2019
facb878
Add missing file header
Nov 21, 2019
d2a0ae8
Address PR feedback, switched to TYPED_EVENT, newer Exception Macro, …
Nov 21, 2019
4bafb32
Switch to GETSET_PROPERTY macro
Nov 21, 2019
9108b41
Adjust fontsize as supposed to fixed size
Nov 21, 2019
12c1ac6
LogicalDPI was not the right call it's always 96
Nov 21, 2019
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
379 changes: 379 additions & 0 deletions src/cascadia/TerminalControl/TSFInputControl.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,379 @@
#include "pch.h"
philnach marked this conversation as resolved.
Show resolved Hide resolved
#include "TSFInputControl.h"
#include "TSFInputControl.g.cpp"

using namespace winrt::Windows::Foundation;
using namespace winrt::Windows::Graphics::Display;
using namespace winrt::Windows::UI::Core;
using namespace winrt::Windows::UI::Text;
using namespace winrt::Windows::UI::Text::Core;
using namespace winrt::Windows::UI::Xaml;

// hack to account for offset caused by tabs
philnach marked this conversation as resolved.
Show resolved Hide resolved
#define TABS_OFFSET 38

namespace winrt::Microsoft::Terminal::TerminalControl::implementation
{
TSFInputControl::TSFInputControl() :
_editContext{ nullptr }
{
_Create();
}

// Method Description:
// - Creates XAML controls for displaying user input and hooks up CoreTextEditContext handlers
// for handling text input from the Text Services Framework.
// Arguments:
// - <none>
// Return Value:
// - <none>
void TSFInputControl::_Create()
{
// TextBlock for user input form TSF
_textBlock = Controls::TextBlock();
_textBlock.Visibility(Visibility::Collapsed);
_textBlock.IsTextSelectionEnabled(false);
_textBlock.TextDecorations(TextDecorations::Underline);

// Canvas for controlling exact position of the TextBlock
_canvas = Windows::UI::Xaml::Controls::Canvas();
_canvas.Visibility(Visibility::Collapsed);

// add the Textblock to the Canvas
_canvas.Children().Append(_textBlock);

// set the content of this control to be the Canvas
this->Content(_canvas);

// Create a CoreTextEditingContext for since we are acting like a custom edit control
auto manager = Core::CoreTextServicesManager::GetForCurrentView();
_editContext = manager.CreateEditContext();

// sets the Input Pane display policy to Manual for now so that it can manually show the
// software keyboard when the control gains focus and dismiss it when the control loses focus.
// TODO investigate if we can set this to Automatic
philnach marked this conversation as resolved.
Show resolved Hide resolved
_editContext.InputPaneDisplayPolicy(Core::CoreTextInputPaneDisplayPolicy::Manual);

// set the input scope to Text because this control is for any text.
_editContext.InputScope(Core::CoreTextInputScope::Text);

_editContext.TextRequested({ this, &TSFInputControl::_textRequestedHandler });

_editContext.SelectionRequested({ this, &TSFInputControl::_selectionRequestedHandler });

_editContext.FocusRemoved({ this, &TSFInputControl::_focusRemovedHandler });

_editContext.TextUpdating({ this, &TSFInputControl::_textUpdatingHandler });

_editContext.SelectionUpdating({ this, &TSFInputControl::_selectionUpdatingHandler });

_editContext.FormatUpdating({ this, &TSFInputControl::_formatUpdatingHandler });

_editContext.LayoutRequested({ this, &TSFInputControl::_layoutRequestedHandler });

_editContext.CompositionStarted({ this, &TSFInputControl::_compositionStartedHandler });

_editContext.CompositionCompleted({ this, &TSFInputControl::_compositionCompletedHandler });
}

// Method Description:
// - NotifyFocusEnter handler for notifying CoreEditTextContext of focus enter
// when TerminalControl receives focus.
// Arguments:
// - <none>
// Return Value:
// - <none>
void TSFInputControl::NotifyFocusEnter()
{
if (_editContext != nullptr)
{
_editContext.NotifyFocusEnter();
}
}

// Method Description:
// - NotifyFocusEnter handler for notifying CoreEditTextContext of focus leaving.
// when TerminalControl no longer has focus.
// Arguments:
// - <none>
// Return Value:
// - <none>
void TSFInputControl::NotifyFocusLeave()
{
if (_editContext != nullptr)
{
//_editContext.NotifyFocusLeave();
philnach marked this conversation as resolved.
Show resolved Hide resolved
}
}

// Method Description:
// - Scales a Rect based on a scale factor
// Arguments:
// - rect: Rect to scale by scale
// - scale: amount to scale rect by
// Return Value:
// - Rect scaled by scale
inline Rect ScaleRect(Rect rect, double scale)
philnach marked this conversation as resolved.
Show resolved Hide resolved
{
const float scaleLocal = gsl::narrow<float>(scale);
rect.X *= scaleLocal;
rect.Y *= scaleLocal;
rect.Width *= scaleLocal;
rect.Height *= scaleLocal;
return rect;
}

// Method Description:
// - Converts a COLORREF to Color
// Arguments:
// - colorref: COLORREF to convert to Color
// Return Value:
// - Color containing the RGB values from colorref
inline winrt::Windows::UI::Color ColorRefToColor(const COLORREF& colorref)
philnach marked this conversation as resolved.
Show resolved Hide resolved
{
winrt::Windows::UI::Color color;
color.R = GetRValue(colorref);
color.G = GetGValue(colorref);
color.B = GetBValue(colorref);
return color;
}

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

// Get window in screen coordinates, this is the entire window including tabs
auto windowBounds = Window::Current().CoreWindow().Bounds();
philnach marked this conversation as resolved.
Show resolved Hide resolved

// Get the cursor position in text buffer position
auto cursorArgs = winrt::make_self<CursorPositionEventArgs>();
_currentCursorPositionHandlers(*this, *cursorArgs);
COORD cursorPos = { gsl::narrow_cast<SHORT>(cursorArgs->CurrentPosition().X), gsl::narrow_cast<SHORT>(cursorArgs->CurrentPosition().Y) }; //_terminal->GetCursorPosition();
philnach marked this conversation as resolved.
Show resolved Hide resolved

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

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

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

// TODO: add tabs offset, currently a hack, since we can't determine the actual screen position of the control
THROW_IF_FAILED(ShortAdd(screenCursorPos.Y, TABS_OFFSET, &screenCursorPos.Y));

// Get scale factor for view
double scaleFactor = DisplayInformation::GetForCurrentView().RawPixelsPerViewPixel();

// TODO set real layout bounds
Rect selectionRect = Rect(screenCursorPos.X, screenCursorPos.Y, 0, fontHeight);
request.LayoutBounds().TextBounds(ScaleRect(selectionRect, scaleFactor));

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

// position textblock to cursor position
_canvas.SetLeft(_textBlock, clientCursorPos.X);
_canvas.SetTop(_textBlock, static_cast<double>(clientCursorPos.Y) + 2); // TODO figure out how to align

// width is cursor to end of canvas
_textBlock.Width(200); // TODO figure out proper width
_textBlock.Height(fontHeight);

//SHORT foo = _actualFont.GetUnscaledSize().Y;
// TODO: font claims to be 12, but on screen 14 look more correct
_textBlock.FontSize(14);
philnach marked this conversation as resolved.
Show resolved Hide resolved

_textBlock.FontFamily(Media::FontFamily(fontArgs->FontFace()));
}

// Method Description:
// - Handler for CompositionStarted event by CoreEditContext responsible
// for making internal TSFInputControl controls visisble.
// Arguments:
// - sender: CoreTextEditContext sending the request. Not used in method.
// - args: CoreTextCompositionStartedEventArgs. Not used in method.
// Return Value:
// - <none>
void TSFInputControl::_compositionStartedHandler(CoreTextEditContext sender, CoreTextCompositionStartedEventArgs const& args)
{
_canvas.Visibility(Visibility::Visible);
_textBlock.Visibility(Visibility::Visible);
}

// Method Description:
// - Handler for CompositionCompleted event by CoreEditContext responsible
// for making internal TSFInputControl controls visisble.
// Arguments:
// - sender: CoreTextEditContext sending the request. Not used in method.
// - args: CoreTextCompositionCompletedEventArgs. Not used in method.
// Return Value:
// - <none>
void TSFInputControl::_compositionCompletedHandler(CoreTextEditContext sender, CoreTextCompositionCompletedEventArgs const& args)
{
// only need to do work if the current buffer has text
if (!_inputBuffer.empty())
{
auto hstr = to_hstring(_inputBuffer.c_str());
philnach marked this conversation as resolved.
Show resolved Hide resolved

// call event handler with data handled by parent
_compositionCompletedHandlers(hstr);

// clear the buffer for next round
_inputBuffer.clear();
_textBlock.Text(L"");

// tell the input server that we've cleared the buffer
CoreTextRange emptyTextRange;
emptyTextRange.StartCaretPosition = 0;
emptyTextRange.EndCaretPosition = 0;

// indicate text is now 0
_editContext.NotifyTextChanged(emptyTextRange, 0, emptyTextRange);
_editContext.NotifySelectionChanged(emptyTextRange);

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

// Method Description:
// - Handler for FocusRemoved event by CoreEditContext responsible
// for removing focus for the TSFInputControl control accordingly
// when focus was forecibly removed from text input control. (TODO)
// NOTE: Documentation says application should handle this event
// Arguments:
// - sender: CoreTextEditContext sending the request. Not used in method.
// - object: CoreTextCompositionStartedEventArgs. Not used in method.
// Return Value:
// - <none>
void TSFInputControl::_focusRemovedHandler(CoreTextEditContext sender, winrt::Windows::Foundation::IInspectable const& object)
{
}

// Method Description:
// - Handler for TextRequested event by CoreEditContext responsible
// for returning the range of text requeted.
philnach marked this conversation as resolved.
Show resolved Hide resolved
// NOTE: Documentation says application should handle this event
// Arguments:
// - sender: CoreTextEditContext sending the request. Not used in method.
// - args: CoreTextTextRequestedEventArgs to be updated with requested range text.
// Return Value:
// - <none>
void TSFInputControl::_textRequestedHandler(CoreTextEditContext sender, CoreTextTextRequestedEventArgs const& args)
{
// the range the TSF wants to know about
auto range = args.Request().Range();
philnach marked this conversation as resolved.
Show resolved Hide resolved

try
{
auto textRequested = _inputBuffer.substr(range.StartCaretPosition, static_cast<size_t>(range.EndCaretPosition) - static_cast<size_t>(range.StartCaretPosition));

args.Request().Text(winrt::to_hstring(textRequested.c_str()));
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();
}
philnach marked this conversation as resolved.
Show resolved Hide resolved
}

// Method Description:
// - Handler for SelectionRequested event by CoreEditContext responsible
// for returning the currently selected text.
// TSFInputControl currently doesn't allow selection, so nothing happens.
// NOTE: Documentation says application should handle this event
// Arguments:
// - sender: CoreTextEditContext sending the request. Not used in method.
// - args: CoreTextSelectionRequestedEventArgs for providing data for the SelectionRequested event. Not used in method.
// Return Value:
// - <none>
void TSFInputControl::_selectionRequestedHandler(CoreTextEditContext sender, CoreTextSelectionRequestedEventArgs const& args)
{
}

// Method Description:
// - Handler for SelectionUpdating event by CoreEditContext responsible
// for handling modifications to the range of text currently selected.
// TSFInputControl doesn't currently allow selection, so nothing happens.
// NOTE: Documentation says application should set its selection range accordingly
// Arguments:
// - sender: CoreTextEditContext sending the request. Not used in method.
// - args: CoreTextSelectionUpdatingEventArgs for providing data for the SelectionUpdating event. Not used in method.
// Return Value:
// - <none>
void TSFInputControl::_selectionUpdatingHandler(CoreTextEditContext sender, CoreTextSelectionUpdatingEventArgs const& args)
{
}

// Method Description:
// - Handler for TextUpdating event by CoreEditContext responsible
// for handling text updates.
// Arguments:
// - sender: CoreTextEditContext sending the request. Not used in method.
// - args: CoreTextTextUpdatingEventArgs contains new text to update buffer with.
// Return Value:
// - <none>
void TSFInputControl::_textUpdatingHandler(CoreTextEditContext sender, CoreTextTextUpdatingEventArgs const& args)
{
auto text = args.Text();
auto range = args.Range();

try
{
_inputBuffer = _inputBuffer.replace(
range.StartCaretPosition,
static_cast<size_t>(range.EndCaretPosition) - static_cast<size_t>(range.StartCaretPosition),
text.c_str());

_textBlock.Text(_inputBuffer);

// Notify the TSF that the update succeeded
args.Result(CoreTextTextUpdatingResult::Succeeded);
}
catch (...)
{
LOG_CAUGHT_EXCEPTION();

// indicate
args.Result(CoreTextTextUpdatingResult::Failed);
}
}

// Method Description:
// - Handler for FormatUpdating event by CoreEditContext responsible
// for handling different format updates for a particular range of text.
// TSFInputControl doesn't do anything with this event.
// Arguments:
// - sender: CoreTextEditContext sending the request. Not used in method.
// - args: CoreTextFormatUpdatingEventArgs Provides data for the FormatUpdating event. Not used in method.
// Return Value:
// - <none>
void TSFInputControl::_formatUpdatingHandler(CoreTextEditContext sender, CoreTextFormatUpdatingEventArgs const& args)
{
}

DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TSFInputControl, CurrentCursorPosition, _currentCursorPositionHandlers, TerminalControl::TSFInputControl, TerminalControl::CursorPositionEventArgs);
philnach marked this conversation as resolved.
Show resolved Hide resolved
DEFINE_EVENT_WITH_TYPED_EVENT_HANDLER(TSFInputControl, CurrentFontInfo, _currentFontInfoHandlers, TerminalControl::TSFInputControl, TerminalControl::FontInfoEventArgs);
DEFINE_EVENT(TSFInputControl, CompositionCompleted, _compositionCompletedHandlers, TerminalControl::CompositionCompletedEventArgs);
}
Loading