gfx/text_input.h
Composed text: what an input method has typed before it has decided what it typed.
#include <sonore/gfx/text_input.h>- 4types
- 2other declarations
- Guides: The native editor
On this page
Design notes
The problem
Japanese, Chinese and Korean are not typed one key per character. The user types a reading -- "nihon" -- the input method shows it, offers candidates, and only when the user accepts one does "日本" become text. Between the two there is a COMPOSITION (macOS calls it marked text, X11 a preedit): text that is on screen, in the field, at the caret, and is not yet the field's value.
Each platform hands it over differently, and all three need the same two answers from the field:
- WHERE the caret is, so the candidate list opens beside it. That is Component::caretBounds, and focusedCaretInRoot below turns it into the window's coordinates for all three peers: Windows hands the point to imm32, X11 to the XIC as XNSpotLocation, macOS returns it from firstRectForCharacterRange.
- WHAT is being composed, when the platform expects the APPLICATION to draw it. Windows does not (the IME draws its own composition window at the point above, which is the path window_win32.h takes and has tested). macOS always does: a view implementing NSTextInputClient is handed marked text and must show it. X11 does when the input style is on-the-spot (XIMPreeditCallbacks). Those two write a TextComposition into the focused field through TextInputSession, and the field paints it -- underlined, at its caret, never part of its value until the input method commits it.
Why the rules live here and not in the peers
NSTextInputClient is a dozen methods over UTF-16 ranges -- a marked range, a selected range inside it, replacement ranges in the document -- and the arithmetic of those ranges against a field that stores code points is where an implementation goes wrong: an emoji is two UTF-16 units and one character, and a range that splits it puts half a surrogate into a string. None of that needs a Mac. TextInputSession is that arithmetic, run against any TextInputTarget, and tests/mac_input_test.cpp drives it on Windows with real TextEditor and TextArea instances. What is left in window_cocoa.h is converting NSString and NSRange at the edge; what is left in window_x11.h is unpacking XIMText.
What the field promises
While a composition is showing, the field's selection is collapsed at its start, and the field does not edit: an input method consumes the keys while it composes, and a click or a key that does reach the field commits the composition first. getText() never includes it -- a value box must not commit "nihon" because a reader asked for its value mid-composition.
Types 4
What an input method is composing in a field.
In code points, as the fields store their text. Empty means nothing is being composed.
A component an input method can compose into.
The primitives a field already has, named so TextInputSession can reach them: its committed text, its selection, a replacement that applies the field's own rules, and where a character is drawn. Implemented by TextEditor and TextArea; a component returns itself from Component::textInputTarget.
Indices are code points of the committed text, except where they say SHOWN: the committed text with the composition in place at the caret.
A range in UTF-16 code units of the SHOWN text, as NSTextInputClient speaks them. kNotFound is NSNotFound (NSIntegerMax), which macOS uses for "no range" -- no marked text, or "replace whatever you think best".
The input-method protocol, once, over any TextInputTarget.
Named and shaped after NSTextInputClient because it is the most demanding of the three -- X11's preedit callbacks and Windows' composition string are each a subset of it, and the preedit* functions below are that subset in XIM's own terms (code points, a change at a position).
Functions 2
- fn
inline TextInputTarget* focusedTextInputTarget(const MouseRouter* router)The focused component's text input target, or null.gfx::focusedTextInputTarget · text_input.h:587 - fn
inline bool focusedCaretInRoot(const MouseRouter* router, Rect* out)The focused component's caret, in the ROOT's coordinates (logical units).gfx::focusedCaretInRoot · text_input.h:600