Bevy Version:0.14(upcoming / release candidate)

IME Input

Bevy has support for IMEs (Input Method Editors), which is how people perform text input in languages with more complex scripts, like East Asian languages, and how non-keyboard text input methods (such as handwriting recognition) work. It requires some special handling from you, however.

If you'd like all international users to be able to input text in their language, the way they usually do in other GUI apps on their OS, you should support IMEs. If you want good accessibility for disabled users or users who prefer alternative text input methods like handwriting recognition, you should support IMEs. This should be in addition to supporting text input via the keyboard, which is how most users will input text.

How IMEs Work

IMEs work by using a special "buffer", which shows the current in-progress text suggestions and allows users to preview and compose the next part of their text before confirming it. The text suggestions are provided by the OS, but your app needs to display them for the user.

For example, imagine you have a text input box in your UI. You show the text that the user has already inputted, with a cursor at the end.

If IME is enabled, you will get Ime::Preedit events for "pending" text. You should show that "unconfirmed" text in the text input box, but with different formatting to be visually distinct.

When the user confirms their desired input, you will get an Ime::Commit event with the final text. You should then discard any previous "uncofirmed" text and append the new text to your actual text input string.

How to support IMEs in your Bevy app

First, you need to inform the OS when your application is expecting text input. You don't want the IME to accidentally activate during gameplay, etc.

Whenever you want the user to input text, you enable "IME mode" on the Window. When you are done, disable it.

If the user is not using an IME, nothing happens when you enable "IME mode". You will still get keyboard events as usual and you can accept text input that way.

If the user has an IME, you will get an Ime::Enabled event. At that point, your application will no longer receive any KeyboardInput events.

You can then handle Ime::Preedit events for pending/unconfirmed text, and Ime::Commit for final/confirmed text.

// for this simple example, we will just enable/disable IME mode on mouse click
fn ime_toggle(
    mousebtn: Res<ButtonInput<MouseButton>>,
    mut q_window: Query<&mut Window, With<PrimaryWindow>>,
) {
    if mousebtn.just_pressed(MouseButton::Left) {
        let mut window = q_window.single_mut();

        // toggle "IME mode"
        window.ime_enabled = !window.ime_enabled;

        // We need to tell the OS the on-screen coordinates where the text will
        // be displayed; for this simple example, let's just use the mouse cursor.
        // In a real app, this might be the position of a UI text field, etc.
        window.ime_position = window.cursor_position().unwrap();
    }
}

fn ime_input(
    mut evr_ime: EventReader<Ime>,
) {
    for ev in evr_ime.read() {
        match ev {
            Ime::Commit { value, .. } => {
                println!("IME confirmed text: {}", value);
            }
            Ime::Preedit { value, cursor, .. } => {
                println!("IME buffer: {:?}, cursor: {:?}", value, cursor);
            }
            Ime::Enabled { .. } => {
                println!("IME mode enabled!");
            }
            Ime::Disabled { .. } => {
                println!("IME mode disabled!");
            }
        }
    }
}

For the sake of brevity, this example just prints the events to the console.

In a real app, you will want to display the "pre-edit" text on-screen, and use different formatting to show the cursor. On "commit", you can append the provided text to the actual string where you normally accept text input.