Coherent UI  2.5.3
A modern user interface library for games
 All Classes Namespaces Functions Variables Enumerations Enumerator Pages
Sample - IME

This sample demonstrates the IME support available in Cohernet UI. It allows the user to input East Asian text on all supported platforms. It also shows how to draw an IME candidate list with a secondary Coherent UI View on Windows. A great overview of how IME works in a generic game can be seen at: http://msdn.microsoft.com/en-us/library/windows/desktop/ee419002%28v=vs.85%29.aspx

Note
Supplied code is not representative for a well-designed, high-performance solution. It's written for simplicity and its purpose is to demonstrate a feature of Coherent UI.

Building the sample

The sample solution is already configured and you only have to compile and run it.

It uses a sample mini game framework that provides very basic functionality.

The output will be in the Coherent/Samples/UI/bin directory.

Key points

  1. You should activate IME on the View you want to support it.
  2. You should signal the OS when you accept IME input by monitoring what the user has focused inside the view (ViewListener::OnTextInputTypeChanged)
  3. You should notify the View of the progress of the current IME composition with the appropriate methods: View::IMESetComposition, View::IMEConfirmComposition, View::IMEConfirmComposition.
  4. On Windows you can request the data for the candidate list and draw it in a secondary View. On other OSes we just show using their default windows.

Sample walkthrough

Coherent UI's IME support is OS agnostic. It will notify the user of all events needed to enable/disable IME input and where to draw the eventual candidate list. Views need to be notified of all progress of the IME composition to draw it correctly and give the user a familiar feel.

The sample application framework that all Coherent UI samples are built upon has been written so that it can handle and interact with the OS IME-related functionality. Please refer to the PlatformWindow class that has the following IME-related methods:

void UpdateOSIMEState(bool activate);
void CancelIMEComposition();
IMECandidateList GetIMECandidateList();
void IMECandidateSelected(int id);
bool IsIMEActive() const;
void SetCaretRect(unsigned x, unsigned y, unsigned w, unsigned h);

These methods are not part of Coherent UI itself, but offer a sample how to plumb the OS events to Views on all platforms.

To enable the IME-related functionality in the framework set "SupportCustomIME" to true in the Style of the created window. To enable IME events for a View call IMEActivate with true. On Windows we create a second Coherent UI View encapsulated in the IMEView class. It will be used to show the candidate list in the application.

First we monitor what the client has focused in a page:

// This means that the user has focused a new element in the View.
// We want to accept IME input only in text boxes.
virtual void OnTextInputTypeChanged(Coherent::UI::TextInputControlType type, bool canCompositeInline)
{
const bool activate = (type != Coherent::UI::TICT_None && type != Coherent::UI::TICT_Password);
if(m_MainWindow)
{
m_MainWindow->UpdateOSIMEState(activate);
}
}

We tell to OS to accept or not IME events based on the currently focused element.

When a composition is in-progress the Application framework will notify us and we need to pass the composition to the View so that it can be shown in it:

virtual void OnIMESetComposition(const wchar_t* composition, unsigned cursorPos, unsigned targetStart, unsigned targetEnd) COHERENT_OVERRIDE
{
if(m_ViewListener->GetView())
{
m_ViewListener->GetView()->IMESetComposition(composition, cursorPos, targetStart, targetEnd);
}
}
virtual void OnIMEConfirmComposition(const wchar_t* composition) COHERENT_OVERRIDE
{
if(m_ViewListener->GetView())
{
m_ViewListener->GetView()->IMEConfirmComposition(composition);
}
}
virtual void OnIMEEndComposition() COHERENT_OVERRIDE
{
if(m_ViewListener->GetView())
{
m_ViewListener->GetView()->IMECancelComposition();
}
}

The above event are triggered by the sample framework. Please refer to the OS-specific code in the WindowWin/Mac/Linux classes to get an idea what to monitor in your own applications.

On Windows we also want to draw the candidate list ourselves. We position the view under the current caret:

// The caret has changed it's position in the View. You can use this message to move
// your candidate list or composition window
virtual void OnCaretRectChanged(unsigned x, unsigned y, unsigned width, unsigned height)
{
if(m_IMEView)
m_IMEView->Move(x, y+height);
if(m_MainWindow)
m_MainWindow->SetCaretRect(x, y, width, height);
}

To show or hide the candidate list we again rely on the events received by the OS:

virtual void OnIMEShowCandidateList() COHERENT_OVERRIDE
{
if(m_IMEView)
{
m_IMEView->InstallCallback(std::bind(&Application::OnIMECandidateSelected, this, std::placeholders::_1));
m_IMEView->SetShow(true);
}
}
virtual void OnIMEUpdateCandidateList() COHERENT_OVERRIDE
{
auto candidates = m_Window.GetIMECandidateList();
if(m_IMEView)
{
m_IMEView->SetCandidates(candidates.Candidates, candidates.SelectedId, candidates.PageStartId, candidates.PageSize);
}
}
virtual void OnIMEHideCandidateList() COHERENT_OVERRIDE
{
if(m_IMEView)
m_IMEView->SetShow(false);
}

Refer to the WindowWin.cpp file for more information how to monitor these events.

When we have new candidates we send the to the auxilliary View via a binding event and they will be drawn in it as a list:

void IMEView::UpdateCandidates()
{
if(!m_IsReadyForBindings || m_Candidates.empty())
return;
m_View->TriggerEvent("UpdateCandidates", m_Candidates, m_SelectedId, m_PageStartId, m_PageSize);
}

We just draw the candidate list View on-top of the main View. Windows handles correctly pressing a number to select a candidate or PageDown/Up to change the page of the candidates. However we would also like to be able to select a candidate by clicking on it with the mouse. To achieve this, on every mouse event, if the candidate list is showing, we check the coordinates of the mouse and send the event to the auxilliary view if we detect that the user is clicking on it:

virtual void OnMouseEvent(const Coherent::UI::MouseEventData& event)
{
if (ShouldForwardInputEvent())
{
// If the candidate list is being shown and we have clicked in it - send
// the mouse event there, so the user can select candidates with the mouse too
if(m_IMEView && m_IMEView->IsShowing())
{
auto imePos = m_IMEView->GetPosition();
if( (event.X >= imePos.X) && (event.X <= imePos.X + imePos.Width)
&& (event.Y >= imePos.Y) && (event.Y <= imePos.Y + imePos.Height))
{
auto imeEvt = event;
imeEvt.X -= imePos.X;
imeEvt.Y -= imePos.Y;
m_IMEView->GetView()->MouseEvent(imeEvt);
return;
}
}
m_ViewListener->GetView()->MouseEvent(event);
}
}

When a candidate is clicked, the JavaScript code triggers an event and the Application is notified of the id of the clicked item and passes it to the OS via the framework:

virtual void OnIMECandidateSelected(int id)
{
m_Window.IMECandidateSelected(id);
}