UI (User Interface) / IME Support

Back to examples View in GitHub

Demonstrates IME (Input Method Editor) support for text input.

IME allows users to input characters that aren't directly on their keyboard, such as Chinese, Japanese, and Korean characters.

To use IME input, the system must have fonts installed that support the target script. This example uses [FontSource::SansSerif], which resolves to a system sans-serif font. On systems without e.g. CJK fonts installed, CJK input will render as boxes or question marks.

use bevy::color::palettes::css::DARK_GREY;
use bevy::color::palettes::tailwind::SLATE_300;
use bevy::input_focus::{
    tab_navigation::{TabGroup, TabIndex, TabNavigationPlugin},
    InputFocus,
};
use bevy::prelude::*;
use bevy::text::{EditableText, TextCursorStyle};

fn main() {
    App::new()
        .add_plugins(DefaultPlugins)
        .add_plugins(TabNavigationPlugin)
        .add_systems(Startup, setup)
        .add_systems(Update, text_submission)
        .run();
}

#[derive(Component)]
struct TextOutput;

fn setup(mut commands: Commands) {
    commands.spawn(Camera2d);

    let instructions = commands
        .spawn((
            Text::new("Type using your IME, then press Ctrl+Enter to submit. Your system default sans-serif font will be used, so make sure you have fonts installed that support the characters you want to input!"),
            TextFont {
                font_size: FontSize::Px(20.0),
                ..default()
            },
        ))
        .id();

    let text_input = commands
        .spawn((
            Node {
                width: px(400),
                height: px(250),
                border: px(3).all(),
                padding: px(8).all(),
                ..default()
            },
            // SansSerif resolves to a system sans-serif font, which on most CJK systems
            // includes support for Chinese, Japanese, and Korean characters.
            // Note that using system fonts requires the "bevy/system-fonts" feature to be enabled.
            TextFont {
                font: FontSource::SansSerif,
                font_size: FontSize::Px(32.0),
                ..default()
            },
            BorderColor::from(Color::from(SLATE_300)),
            EditableText {
                allow_newlines: true,
                ..default()
            },
            TextLayout::no_wrap(),
            TextCursorStyle::default(),
            TabIndex(0),
            BackgroundColor(DARK_GREY.into()),
        ))
        .id();

    let text_output = commands
        .spawn((
            Text::new("Your text here!"),
            TextFont {
                font: FontSource::SansSerif,
                font_size: FontSize::Px(32.0),
                ..default()
            },
            TextOutput,
        ))
        .id();

    commands
        .spawn((
            Node {
                flex_direction: FlexDirection::Column,
                padding: px(24.0).all(),
                row_gap: px(16),
                ..default()
            },
            TabGroup::new(0),
        ))
        .add_children(&[instructions, text_input, text_output]);
}

fn text_submission(
    input_focus: Res<InputFocus>,
    keyboard_input: Res<ButtonInput<KeyCode>>,
    mut text_input: Query<&mut EditableText>,
    mut text_output: Single<&mut Text, With<TextOutput>>,
) {
    if keyboard_input.just_pressed(KeyCode::Enter)
        && (keyboard_input.pressed(KeyCode::ControlLeft)
            || keyboard_input.pressed(KeyCode::ControlRight))
        && let Some(focused_entity) = input_focus.get()
        && let Ok(mut input) = text_input.get_mut(focused_entity)
    {
        text_output.0 = input.value().to_string();
        input.clear();
    }
}