Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/input/text_input.rs
6595 views
1
//! Simple text input support
2
//!
3
//! Return creates a new line, backspace removes the last character.
4
//! Clicking toggle IME (Input Method Editor) support, but the font used as limited support of characters.
5
//! You should change the provided font with another one to test other languages input.
6
7
use std::mem;
8
9
use bevy::{
10
input::keyboard::{Key, KeyboardInput},
11
prelude::*,
12
};
13
14
fn main() {
15
App::new()
16
.add_plugins(DefaultPlugins)
17
.add_systems(Startup, setup_scene)
18
.add_systems(
19
Update,
20
(
21
toggle_ime,
22
listen_ime_events,
23
listen_keyboard_input_events,
24
bubbling_text,
25
),
26
)
27
.run();
28
}
29
30
fn setup_scene(mut commands: Commands, asset_server: Res<AssetServer>) {
31
commands.spawn(Camera2d);
32
33
// The default font has a limited number of glyphs, so use the full version for
34
// sections that will hold text input.
35
let font = asset_server.load("fonts/FiraMono-Medium.ttf");
36
37
commands.spawn((
38
Text::default(),
39
Node {
40
position_type: PositionType::Absolute,
41
top: px(12),
42
left: px(12),
43
..default()
44
},
45
children![
46
TextSpan::new("Click to toggle IME. Press return to start a new line.\n\n",),
47
TextSpan::new("IME Enabled: "),
48
TextSpan::new("false\n"),
49
TextSpan::new("IME Active: "),
50
TextSpan::new("false\n"),
51
TextSpan::new("IME Buffer: "),
52
(
53
TextSpan::new("\n"),
54
TextFont {
55
font: font.clone(),
56
..default()
57
},
58
),
59
],
60
));
61
62
commands.spawn((
63
Text2d::new(""),
64
TextFont {
65
font,
66
font_size: 100.0,
67
..default()
68
},
69
));
70
}
71
72
fn toggle_ime(
73
input: Res<ButtonInput<MouseButton>>,
74
mut window: Single<&mut Window>,
75
status_text: Single<Entity, (With<Node>, With<Text>)>,
76
mut ui_writer: TextUiWriter,
77
) {
78
if input.just_pressed(MouseButton::Left) {
79
window.ime_position = window.cursor_position().unwrap();
80
window.ime_enabled = !window.ime_enabled;
81
82
*ui_writer.text(*status_text, 3) = format!("{}\n", window.ime_enabled);
83
}
84
}
85
86
#[derive(Component)]
87
struct Bubble {
88
timer: Timer,
89
}
90
91
fn bubbling_text(
92
mut commands: Commands,
93
mut bubbles: Query<(Entity, &mut Transform, &mut Bubble)>,
94
time: Res<Time>,
95
) {
96
for (entity, mut transform, mut bubble) in bubbles.iter_mut() {
97
if bubble.timer.tick(time.delta()).just_finished() {
98
commands.entity(entity).despawn();
99
}
100
transform.translation.y += time.delta_secs() * 100.0;
101
}
102
}
103
104
fn listen_ime_events(
105
mut events: EventReader<Ime>,
106
status_text: Single<Entity, (With<Node>, With<Text>)>,
107
mut edit_text: Single<&mut Text2d, (Without<Node>, Without<Bubble>)>,
108
mut ui_writer: TextUiWriter,
109
) {
110
for event in events.read() {
111
match event {
112
Ime::Preedit { value, cursor, .. } if !cursor.is_none() => {
113
*ui_writer.text(*status_text, 7) = format!("{value}\n");
114
}
115
Ime::Preedit { cursor, .. } if cursor.is_none() => {
116
*ui_writer.text(*status_text, 7) = "\n".to_string();
117
}
118
Ime::Commit { value, .. } => {
119
edit_text.push_str(value);
120
}
121
Ime::Enabled { .. } => {
122
*ui_writer.text(*status_text, 5) = "true\n".to_string();
123
}
124
Ime::Disabled { .. } => {
125
*ui_writer.text(*status_text, 5) = "false\n".to_string();
126
}
127
_ => (),
128
}
129
}
130
}
131
132
fn listen_keyboard_input_events(
133
mut commands: Commands,
134
mut events: EventReader<KeyboardInput>,
135
edit_text: Single<(&mut Text2d, &TextFont), (Without<Node>, Without<Bubble>)>,
136
) {
137
let (mut text, style) = edit_text.into_inner();
138
for event in events.read() {
139
// Only trigger changes when the key is first pressed.
140
if !event.state.is_pressed() {
141
continue;
142
}
143
144
match (&event.logical_key, &event.text) {
145
(Key::Enter, _) => {
146
if text.is_empty() {
147
continue;
148
}
149
let old_value = mem::take(&mut **text);
150
151
commands.spawn((
152
Text2d::new(old_value),
153
style.clone(),
154
Bubble {
155
timer: Timer::from_seconds(5.0, TimerMode::Once),
156
},
157
));
158
}
159
(Key::Backspace, _) => {
160
text.pop();
161
}
162
(_, Some(inserted_text)) => {
163
// Make sure the text doesn't have any control characters,
164
// which can happen when keys like Escape are pressed
165
if inserted_text.chars().all(is_printable_char) {
166
text.push_str(inserted_text);
167
}
168
}
169
_ => continue,
170
}
171
}
172
}
173
174
// this logic is taken from egui-winit:
175
// https://github.com/emilk/egui/blob/adfc0bebfc6be14cee2068dee758412a5e0648dc/crates/egui-winit/src/lib.rs#L1014-L1024
176
fn is_printable_char(chr: char) -> bool {
177
let is_in_private_use_area = ('\u{e000}'..='\u{f8ff}').contains(&chr)
178
|| ('\u{f0000}'..='\u{ffffd}').contains(&chr)
179
|| ('\u{100000}'..='\u{10fffd}').contains(&chr);
180
181
!is_in_private_use_area && !chr.is_ascii_control()
182
}
183
184