docs/bevy/examples/ui/text/ime_support.rs 3.7 K · 114 lines · raw · history

1 //! Demonstrates IME (Input Method Editor) support for text input.
2 //!
3 //! IME allows users to input characters that aren't directly on their keyboard,
4 //! such as Chinese, Japanese, and Korean characters.
5 //!
6 //! To use IME input, the system must have fonts installed that support the target script.
7 //! This example uses [`FontSource::SansSerif`], which resolves to a system sans-serif font.
8 //! On systems without e.g. CJK fonts installed, CJK input will render as boxes or question marks.
9 use bevy::color::palettes::css::DARK_GREY;
10 use bevy::color::palettes::tailwind::SLATE_300;
11 use bevy::input_focus::{
12 tab_navigation::{TabGroup, TabIndex, TabNavigationPlugin},
13 InputFocus,
14 };
15 use bevy::prelude::*;
16 use bevy::text::{EditableText, TextCursorStyle};
17 use bevy::ui_widgets::TextInput;
18
19 fn main() {
20 App::new()
21 .add_plugins(DefaultPlugins)
22 .add_plugins(TabNavigationPlugin)
23 .add_systems(Startup, setup)
24 .add_systems(Update, text_submission)
25 .run();
26 }
27
28 #[derive(Component)]
29 struct TextOutput;
30
31 fn setup(mut commands: Commands) {
32 commands.spawn(Camera2d);
33
34 let instructions = commands
35 .spawn((
36 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!"),
37 TextFont {
38 font_size: FontSize::Px(20.0),
39 ..default()
40 },
41 ))
42 .id();
43
44 let text_input = commands
45 .spawn((
46 Node {
47 width: px(400),
48 height: px(250),
49 border: px(3).all(),
50 padding: px(8).all(),
51 ..default()
52 },
53 // SansSerif resolves to a system sans-serif font, which on most CJK systems
54 // includes support for Chinese, Japanese, and Korean characters.
55 // Note that using system fonts requires the "bevy/system-fonts" feature to be enabled.
56 TextFont {
57 font: FontSource::sans_serif(),
58 font_size: FontSize::Px(32.0),
59 ..default()
60 },
61 BorderColor::from(Color::from(SLATE_300)),
62 TextInput,
63 EditableText {
64 allow_newlines: true,
65 ..default()
66 },
67 TextLayout::no_wrap(),
68 TextCursorStyle::default(),
69 TabIndex(0),
70 BackgroundColor(DARK_GREY.into()),
71 ))
72 .id();
73
74 let text_output = commands
75 .spawn((
76 Text::new("Your text here!"),
77 TextFont {
78 font: FontSource::sans_serif(),
79 font_size: FontSize::Px(32.0),
80 ..default()
81 },
82 TextOutput,
83 ))
84 .id();
85
86 commands
87 .spawn((
88 Node {
89 flex_direction: FlexDirection::Column,
90 padding: px(24.0).all(),
91 row_gap: px(16),
92 ..default()
93 },
94 TabGroup::new(0),
95 ))
96 .add_children(&[instructions, text_input, text_output]);
97 }
98
99 fn text_submission(
100 input_focus: Res<InputFocus>,
101 keyboard_input: Res<ButtonInput<KeyCode>>,
102 mut text_input: Query<&mut EditableText>,
103 mut text_output: Single<&mut Text, With<TextOutput>>,
104 ) {
105 if keyboard_input.just_pressed(KeyCode::Enter)
106 && (keyboard_input.pressed(KeyCode::ControlLeft)
107 || keyboard_input.pressed(KeyCode::ControlRight))
108 && let Some(focused_entity) = input_focus.get()
109 && let Ok(mut input) = text_input.get_mut(focused_entity)
110 {
111 text_output.0 = input.value().to_string();
112 input.clear();
113 }
114 }