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

Better IME support #658

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
2 changes: 1 addition & 1 deletion .gitignore
Expand Up @@ -82,4 +82,4 @@ tests/timeout
tests/title
tests/vulkan
tests/windows

tests/ime
4 changes: 3 additions & 1 deletion CMakeLists.txt
Expand Up @@ -205,6 +205,7 @@ endif()
if (_GLFW_WIN32)

list(APPEND glfw_PKG_LIBS "-lgdi32")
list(APPEND glfw_LIBRARIES "imm32")

if (GLFW_USE_HYBRID_HPG)
set(_GLFW_USE_HYBRID_HPG 1)
Expand Down Expand Up @@ -298,12 +299,13 @@ if (_GLFW_COCOA)

list(APPEND glfw_LIBRARIES
"-framework Cocoa"
"-framework Carbon"
"-framework IOKit"
"-framework CoreFoundation"
"-framework CoreVideo")

set(glfw_PKG_DEPS "")
set(glfw_PKG_LIBS "-framework Cocoa -framework IOKit -framework CoreFoundation -framework CoreVideo")
set(glfw_PKG_LIBS "-framework Cocoa -framework Carbon -framework IOKit -framework CoreFoundation -framework CoreVideo")
endif()

#--------------------------------------------------------------------
Expand Down
90 changes: 90 additions & 0 deletions docs/input.dox
Expand Up @@ -214,6 +214,96 @@ void character_callback(GLFWwindow* window, unsigned int codepoint)
}
@endcode

@subsection preedit IME Support

All desktop operating systems support IME (Input Method Editor) to input characters
that are not mapped with physical keys. IME have been popular among Eeastern Asian people.
And some operating systems start supporting voice input via IME mechanism.

GLFW provides IME support functions to help
you implement better text input features. You should add suitable visualization code for
preedit text.

IME works in front of actual character input events (@ref input_char).
If your application uses text input and you want to support IME,
you should register preedit callback to receive preedit text before committed.

@code
glfwSetPreeditCallback(window, preedit_callback);
@endcode

The callback function receives chunk of text and focused block information.

@code
static void preedit_callback(GLFWwindow* window, int strLength, unsigned int* string, int blockLength, int* blocks, int focusedBlock) {
}
@endcode

strLength and string parameter reprsent whole preedit text. Each character of the preedit string is a codepoint like @ref input_char.

If you want to type the text "寿司(sushi)", Usually the callback is called several times like the following sequence:

-# key event: s
-# preedit: [string: "s", block: [1], focusedBlock: 0]
-# key event: u
-# preedit: [string: "す", block: [1], focusedBlock: 0]
-# key event: s
-# preedit: [string: "すs", block: [2], focusedBlock: 0]
-# key event: h
-# preedit: [string: "すsh", block: [2], focusedBlock: 0]
-# key event: i
-# preedit: [string: "すし", block: [2], focusedBlock: 0]
-# key event: ' '
-# preedit: [string: "寿司", block: [2], focusedBlock: 0]
-# char: '寿'
-# char: '司'
-# preedit: [string: "", block: [], focusedBlock: 0]

If preedit text includes several semantic blocks, preedit callbacks returns several blocks after a space key pressed:

-# preedit: [string: "わたしはすしをたべます", block: [11], focusedBlock: 0]
-# preedit: [string: "私は寿司を食べます", block: [2, 7], focusedBlock: 1]

"blocks" is a list of block length. The above case, it contains the following blocks and second block is focused.

- 私は
- [寿司を食べます]

commited text(passed via regular @ref input_char event), unfocused block, focused block should have different text style.


GLFW provides helper function to teach suitable position of the candidate window to window system.
Window system decides the best position from text cursor geometry (x, y coords and height). You should call this function
in the above preedit text callback function.

@code
glfwSetPreeditCursorPos(window, x, y, h);
glfwGetPreeditCursorPos(window, &x, &y, &h);
@endcode

Sometimes IME task is interrupted by user or application. There are several functions to support these situation.
You can receive notification about IME status change(on/off) by using the following function:

@code
glfwSetIMEStatusCallback(window, imestatus_callback);
@endcode

imestatus_callback has simple sigunature like this:

@code
static void imestatus_callback(GLFWwindow* window) {
}
@endcode

You can implement the code that resets or commits preedit text when IME status is changed and preedit text is not empty.

When the focus is gone from text box, you can use the following functions to reset IME status:

@code
void glfwResetPreeditText(GLFWwindow* window);
void glfwSetIMEStatus(GLFWwindow* window, int active)
int glfwGetIMEStatus(GLFWwindow* window)
@endcode

@subsection input_key_name Key names

Expand Down
164 changes: 156 additions & 8 deletions include/GLFW/glfw3.h
Expand Up @@ -1003,6 +1003,7 @@ extern "C" {
#define GLFW_STICKY_MOUSE_BUTTONS 0x00033003
#define GLFW_LOCK_KEY_MODS 0x00033004
#define GLFW_RAW_MOUSE_MOTION 0x00033005
#define GLFW_IME 0x00033006

#define GLFW_CURSOR_NORMAL 0x00034001
#define GLFW_CURSOR_HIDDEN 0x00034002
Expand Down Expand Up @@ -1458,6 +1459,37 @@ typedef void (* GLFWcharfun)(GLFWwindow*,unsigned int);
*/
typedef void (* GLFWcharmodsfun)(GLFWwindow*,unsigned int,int);

/*! @brief The function signature for preedit callbacks.
*
* This is the function signature for preedit callback functions.
*
* @param[in] window The window that received the event.
* @param[in] length Preedit string length.
* @param[in] string Preedit string.
* @param[in] count Attributed block count.
* @param[in] blocksizes List of attributed block size.
* @param[in] focusedblock Focused block index.
*
* @sa @ref preedit
* @sa glfwSetPreeditCallback
*
* @ingroup input
*/
typedef void (* GLFWpreeditfun)(GLFWwindow*,int,unsigned int*,int,int*,int);

/*! @brief The function signature for IME status change callbacks.
*
* This is the function signature for IME status change callback functions.
*
* @param[in] window The window that received the event.
*
* @sa @ref preedit
* @sa glfwSetIMEStatusCallback
*
* @ingroup monitor
*/
typedef void (* GLFWimestatusfun)(GLFWwindow*);

/*! @brief The function signature for file drop callbacks.
*
* This is the function signature for file drop callbacks.
Expand Down Expand Up @@ -3844,13 +3876,13 @@ GLFWAPI void glfwPostEmptyEvent(void);
*
* This function returns the value of an input option for the specified window.
* The mode must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS,
* @ref GLFW_STICKY_MOUSE_BUTTONS, @ref GLFW_LOCK_KEY_MODS or
* @ref GLFW_RAW_MOUSE_MOTION.
* @ref GLFW_STICKY_MOUSE_BUTTONS, @ref GLFW_LOCK_KEY_MODS,
* @ref GLFW_RAW_MOUSE_MOTION or @ref GLFW_IME.
*
* @param[in] window The window to query.
* @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`,
* `GLFW_STICKY_MOUSE_BUTTONS`, `GLFW_LOCK_KEY_MODS` or
* `GLFW_RAW_MOUSE_MOTION`.
* `GLFW_STICKY_MOUSE_BUTTONS`, `GLFW_LOCK_KEY_MODS`
* `GLFW_RAW_MOUSE_MOTION` or `GLFW_IME`.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED and @ref
* GLFW_INVALID_ENUM.
Expand All @@ -3869,8 +3901,8 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode);
*
* This function sets an input mode option for the specified window. The mode
* must be one of @ref GLFW_CURSOR, @ref GLFW_STICKY_KEYS,
* @ref GLFW_STICKY_MOUSE_BUTTONS, @ref GLFW_LOCK_KEY_MODS or
* @ref GLFW_RAW_MOUSE_MOTION.
* @ref GLFW_STICKY_MOUSE_BUTTONS, @ref GLFW_LOCK_KEY_MODS,
* @ref GLFW_RAW_MOUSE_MOTION or @ref GLFW_IME.
*
* If the mode is `GLFW_CURSOR`, the value must be one of the following cursor
* modes:
Expand Down Expand Up @@ -3908,10 +3940,13 @@ GLFWAPI int glfwGetInputMode(GLFWwindow* window, int mode);
* attempting to set this will emit @ref GLFW_PLATFORM_ERROR. Call @ref
* glfwRawMouseMotionSupported to check for support.
*
* If the mode is `GLFW_IME`, the value must be either `GLFW_TRUE` to turn on IME,
* or `GLFW_FALSE` to turn off it.
*
* @param[in] window The window whose input mode to set.
* @param[in] mode One of `GLFW_CURSOR`, `GLFW_STICKY_KEYS`,
* `GLFW_STICKY_MOUSE_BUTTONS`, `GLFW_LOCK_KEY_MODS` or
* `GLFW_RAW_MOUSE_MOTION`.
* `GLFW_STICKY_MOUSE_BUTTONS`, `GLFW_LOCK_KEY_MODS`,
* `GLFW_RAW_MOUSE_MOTION` or `GLFW_IME`.
* @param[in] value The new value of the specified input mode.
*
* @errors Possible errors include @ref GLFW_NOT_INITIALIZED, @ref
Expand Down Expand Up @@ -4308,6 +4343,67 @@ GLFWAPI void glfwDestroyCursor(GLFWcursor* cursor);
*/
GLFWAPI void glfwSetCursor(GLFWwindow* window, GLFWcursor* cursor);

/*! @brief Retrieves the position of the text cursor relative to the client area of window.
*
* This function returns position hint to decide the candidate window.
*
* @param[in] window The window to set the text cursor for.
* @param[out] x The text cursor x position (relative position from window coordinates).
* @param[out] y The text cursor y position (relative position from window coordinates).
* @param[out] h The text cursor height.
*
* @par Thread Safety
* This function may only be called from the main thread.
*
* @sa @ref input_char
*
* @since Added in GLFW 3.X.
*
* @ingroup input
*/
GLFWAPI void glfwGetPreeditCursorPos(GLFWwindow* window, int *x, int *y, int *h);

/*! @brief Notify the text cursor position to window system to decide the candidate window position.
*
* This function teach position hint to decide the candidate window. The candidate window
* is a part of IME(Input Method Editor) and show several candidate strings.
*
* Windows sytems decide proper pisition from text cursor geometry.
* You should call this function in preedit callback.
*
* @param[in] window The window to set the text cursor for.
* @param[in] x The text cursor x position (relative position from window coordinates).
* @param[in] y The text cursor y position (relative position from window coordinates).
* @param[in] h The text cursor height.
*
* @par Thread Safety
* This function may only be called from the main thread.
*
* @sa @ref input_char
*
* @since Added in GLFW 3.X.
*
* @ingroup input
*/
GLFWAPI void glfwSetPreeditCursorPos(GLFWwindow* window, int x, int y, int h);

/*! @brief Reset IME input status.
*
* This function resets IME's preedit text.
*
* @param[in] window The window.
*
* @par Thread Safety
* This function may only be called from the main thread.
*
* @sa @ref preedit
*
* @since Added in GLFW 3.X.
*
* @ingroup input
*/
GLFWAPI void glfwResetPreeditText(GLFWwindow* window);

/*! @brief Sets the key callback.
*
* This function sets the key callback of the specified window, which is called
Expand Down Expand Up @@ -4422,6 +4518,58 @@ GLFWAPI GLFWcharfun glfwSetCharCallback(GLFWwindow* window, GLFWcharfun cbfun);
*/
GLFWAPI GLFWcharmodsfun glfwSetCharModsCallback(GLFWwindow* window, GLFWcharmodsfun cbfun);

/*! @brief Sets the preedit callback.
*
* This function sets the preedit callback of the specified
* window, which is called when an IME is processing text before commited.
*
* Callback receives relative position of input cursor inside preedit text and
* attributed text blocks. This callback is used for on-the-spot text editing
* with IME.
*
* @param[in] window The window whose callback to set.
* @param[in] cbfun The new callback, or `NULL` to remove the currently set
* callback.
* @return The previously set callback, or `NULL` if no callback was set or an
* error occurred.
*
* @par Thread Safety
* This function may only be called from the main thread.
*
* @sa @ref input_char
*
* @since Added in GLFW 3.X
*
* @ingroup input
*/
GLFWAPI GLFWpreeditfun glfwSetPreeditCallback(GLFWwindow* window, GLFWpreeditfun cbfun);

/*! @brief Sets the IME status change callback.
*
* This function sets the preedit callback of the specified
* window, which is called when an IME is processing text before commited.
*
* Callback receives relative position of input cursor inside preedit text and
* attributed text blocks. This callback is used for on-the-spot text editing
* with IME.
*
* @param[in] window The window whose callback to set.
* @param[in] cbfun The new callback, or `NULL` to remove the currently set
* callback.
* @return The previously set callback, or `NULL` if no callback was set or an
* error occurred.
*
* @par Thread Safety
* This function may only be called from the main thread.
*
* @sa @ref input_char
*
* @since Added in GLFW 3.X
*
* @ingroup input
*/
GLFWAPI GLFWimestatusfun glfwSetIMEStatusCallback(GLFWwindow* window, GLFWimestatusfun cbfun);

/*! @brief Sets the mouse button callback.
*
* This function sets the mouse button callback of the specified window, which
Expand Down