Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/input/text_input.rs
9304 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
(TextSpan::new("\n"), TextFont::from(font.clone()),),
53
],
54
));
55
56
commands.spawn((
57
Text2d::new(""),
58
TextFont {
59
font: font.clone().into(),
60
font_size: FontSize::Px(100.0),
61
..default()
62
},
63
));
64
}
65
66
fn toggle_ime(
67
input: Res<ButtonInput<MouseButton>>,
68
mut window: Single<&mut Window>,
69
status_text: Single<Entity, (With<Node>, With<Text>)>,
70
mut ui_writer: TextUiWriter,
71
) {
72
if input.just_pressed(MouseButton::Left) {
73
window.ime_position = window.cursor_position().unwrap();
74
window.ime_enabled = !window.ime_enabled;
75
76
*ui_writer.text(*status_text, 3) = format!("{}\n", window.ime_enabled);
77
}
78
}
79
80
#[derive(Component)]
81
struct Bubble {
82
timer: Timer,
83
}
84
85
fn bubbling_text(
86
mut commands: Commands,
87
mut bubbles: Query<(Entity, &mut Transform, &mut Bubble)>,
88
time: Res<Time>,
89
) {
90
for (entity, mut transform, mut bubble) in bubbles.iter_mut() {
91
if bubble.timer.tick(time.delta()).just_finished() {
92
commands.entity(entity).despawn();
93
}
94
transform.translation.y += time.delta_secs() * 100.0;
95
}
96
}
97
98
fn listen_ime_events(
99
mut ime_reader: MessageReader<Ime>,
100
status_text: Single<Entity, (With<Node>, With<Text>)>,
101
mut edit_text: Single<&mut Text2d, (Without<Node>, Without<Bubble>)>,
102
mut ui_writer: TextUiWriter,
103
) {
104
for ime in ime_reader.read() {
105
match ime {
106
Ime::Preedit { value, cursor, .. } if !cursor.is_none() => {
107
*ui_writer.text(*status_text, 7) = format!("{value}\n");
108
}
109
Ime::Preedit { cursor, .. } if cursor.is_none() => {
110
*ui_writer.text(*status_text, 7) = "\n".to_string();
111
}
112
Ime::Commit { value, .. } => {
113
edit_text.push_str(value);
114
}
115
Ime::Enabled { .. } => {
116
*ui_writer.text(*status_text, 5) = "true\n".to_string();
117
}
118
Ime::Disabled { .. } => {
119
*ui_writer.text(*status_text, 5) = "false\n".to_string();
120
}
121
_ => (),
122
}
123
}
124
}
125
126
fn listen_keyboard_input_events(
127
mut commands: Commands,
128
mut keyboard_input_reader: MessageReader<KeyboardInput>,
129
edit_text: Single<(&mut Text2d, &TextFont), (Without<Node>, Without<Bubble>)>,
130
) {
131
let (mut text, style) = edit_text.into_inner();
132
for keyboard_input in keyboard_input_reader.read() {
133
// Only trigger changes when the key is first pressed.
134
if !keyboard_input.state.is_pressed() {
135
continue;
136
}
137
138
match (&keyboard_input.logical_key, &keyboard_input.text) {
139
(Key::Enter, _) => {
140
if text.is_empty() {
141
continue;
142
}
143
let old_value = mem::take(&mut **text);
144
145
commands.spawn((
146
Text2d::new(old_value),
147
style.clone(),
148
Bubble {
149
timer: Timer::from_seconds(5.0, TimerMode::Once),
150
},
151
));
152
}
153
(Key::Backspace, _) => {
154
text.pop();
155
}
156
(_, Some(inserted_text)) => {
157
// Make sure the text doesn't have any control characters,
158
// which can happen when keys like Escape are pressed
159
if inserted_text.chars().all(is_printable_char) {
160
text.push_str(inserted_text);
161
}
162
}
163
_ => continue,
164
}
165
}
166
}
167
168
// this logic is taken from egui-winit:
169
// https://github.com/emilk/egui/blob/adfc0bebfc6be14cee2068dee758412a5e0648dc/crates/egui-winit/src/lib.rs#L1014-L1024
170
fn is_printable_char(chr: char) -> bool {
171
let is_in_private_use_area = ('\u{e000}'..='\u{f8ff}').contains(&chr)
172
|| ('\u{f0000}'..='\u{ffffd}').contains(&chr)
173
|| ('\u{100000}'..='\u{10fffd}').contains(&chr);
174
175
!is_in_private_use_area && !chr.is_ascii_control()
176
}
177
178