Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
bevyengine
GitHub Repository: bevyengine/bevy
Path: blob/main/examples/ui/text/multiline_text_input.rs
30635 views
1
//! Demonstrates a single, minimal multiline [`EditableText`] widget.
2
3
use bevy::color::palettes::css::DARK_SLATE_GRAY;
4
use bevy::color::palettes::tailwind::SLATE_300;
5
use bevy::input::keyboard::{Key, KeyboardInput};
6
use bevy::input_focus::tab_navigation::{TabGroup, TabIndex, TabNavigationPlugin};
7
use bevy::input_focus::{AutoFocus, FocusedInput};
8
use bevy::prelude::*;
9
use bevy::text::{EditableText, EditableTextFilter, TextCursorStyle};
10
use bevy::ui_widgets::SelectAllOnFocus;
11
12
fn main() {
13
App::new()
14
.add_plugins((DefaultPlugins, TabNavigationPlugin))
15
.add_systems(Startup, setup)
16
.run();
17
}
18
19
#[derive(Component)]
20
struct MultilineInput;
21
22
#[derive(Component)]
23
struct VisibleLinesInput;
24
25
#[derive(Component)]
26
struct FontSizeInput;
27
28
#[derive(Component)]
29
struct SelectionRadiusInput;
30
31
fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
32
commands.spawn(Camera2d);
33
34
commands
35
.spawn(Node {
36
width: percent(100.),
37
height: percent(100.),
38
justify_content: JustifyContent::Center,
39
align_items: AlignItems::Center,
40
..default()
41
})
42
.with_children(|parent| {
43
parent
44
.spawn((
45
Node {
46
flex_direction: FlexDirection::Column,
47
align_items: AlignItems::End,
48
row_gap: px(10.),
49
..default()
50
},
51
TabGroup::default(),
52
))
53
.with_children(|parent| {
54
parent
55
.spawn((
56
Node {
57
width: px(450.),
58
border: px(2.).all(),
59
padding: px(8.).all(),
60
..default()
61
},
62
EditableText {
63
visible_lines: Some(8.),
64
allow_newlines: true,
65
..default()
66
},
67
TextLayout {
68
linebreak: LineBreak::WordOrCharacter,
69
..default()
70
},
71
TextCursorStyle {
72
color: Color::WHITE,
73
selected_text_color: Some(Color::BLACK),
74
..default()
75
},
76
TextFont {
77
font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
78
font_size: FontSize::Px(30.),
79
..default()
80
},
81
BackgroundColor(DARK_SLATE_GRAY.into()),
82
BorderColor::all(SLATE_300),
83
MultilineInput,
84
TabIndex(0),
85
AutoFocus,
86
))
87
.observe(
88
|on: On<FocusedInput<KeyboardInput>>,
89
keys: Res<ButtonInput<Key>>,
90
input_query: Query<&EditableText, With<MultilineInput>>| {
91
if !(on.input.state.is_pressed()
92
&& on.input.logical_key == Key::Enter
93
&& keys.pressed(Key::Control))
94
{
95
return;
96
}
97
let Ok(input) = input_query.get(on.focused_entity) else {
98
return;
99
};
100
101
let mut output = String::new();
102
output.reserve(input.value().into_iter().map(str::len).sum());
103
for sub_str in input.value() {
104
output.push_str(sub_str);
105
}
106
107
info!("{output}" );
108
},
109
);
110
111
parent
112
.spawn((
113
Node {
114
flex_direction: FlexDirection::Row,
115
column_gap: px(10.),
116
..default()
117
},
118
children![
119
(
120
Text::new("visible lines:"),
121
TextFont {
122
font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
123
font_size: FontSize::Px(30.),
124
..default()
125
},
126
),
127
(
128
Node {
129
width: px(100.),
130
border: px(2.).all(),
131
..default()
132
},
133
TextFont {
134
font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
135
font_size: FontSize::Px(30.),
136
..default()
137
},
138
TextLayout {
139
justify: Justify::End,
140
..default()
141
},
142
BackgroundColor(DARK_SLATE_GRAY.into()),
143
BorderColor::all(SLATE_300),
144
EditableText::new("8"),
145
EditableTextFilter::new(|c| c.is_ascii_digit() || c == '.'),
146
TextCursorStyle {
147
color: Color::WHITE,
148
selected_text_color: Some(Color::BLACK),
149
unfocused_selection_color: Color::NONE,
150
..default()
151
},
152
SelectAllOnFocus,
153
VisibleLinesInput,
154
TabIndex(1),
155
)
156
],
157
))
158
.observe(
159
|on: On<FocusedInput<KeyboardInput>>,
160
mut query_set: ParamSet<(
161
Query<&EditableText, With<VisibleLinesInput>>,
162
Query<&mut EditableText, With<MultilineInput>>,
163
)>| {
164
if !(on.input.state.is_pressed()
165
&& on.input.logical_key == Key::Enter)
166
{
167
return;
168
}
169
170
let visible_lines_query = query_set.p0();
171
let Ok(input) = visible_lines_query.get(on.original_event_target())
172
else {
173
return;
174
};
175
176
let mut output = String::new();
177
output.reserve(input.value().into_iter().map(str::len).sum());
178
for sub_str in input.value() {
179
output.push_str(sub_str);
180
}
181
182
let Ok(lines) = output.parse::<f32>() else {
183
return;
184
};
185
186
let mut multiline_query = query_set.p1();
187
let Ok(mut multiline_input) = multiline_query.single_mut() else {
188
return;
189
};
190
191
multiline_input.visible_lines = Some(lines.clamp(1., 10.));
192
},
193
);
194
195
parent
196
.spawn((
197
Node {
198
flex_direction: FlexDirection::Row,
199
column_gap: px(10.),
200
..default()
201
},
202
children![
203
(
204
Text::new("font size:"),
205
TextFont {
206
font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
207
font_size: FontSize::Px(30.),
208
..default()
209
},
210
),
211
(
212
Node {
213
width: px(100.),
214
border: px(2.).all(),
215
..default()
216
},
217
TextFont {
218
font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
219
font_size: FontSize::Px(30.),
220
..default()
221
},
222
TextLayout {
223
justify: Justify::End,
224
..default()
225
},
226
BackgroundColor(DARK_SLATE_GRAY.into()),
227
BorderColor::all(SLATE_300),
228
EditableText::new("30"),
229
EditableTextFilter::new(|c| c.is_ascii_digit()),
230
TextCursorStyle {
231
color: Color::WHITE,
232
selected_text_color: Some(Color::BLACK),
233
unfocused_selection_color: Color::NONE,
234
..default()
235
},
236
SelectAllOnFocus,
237
FontSizeInput,
238
TabIndex(2),
239
)
240
],
241
))
242
.observe(
243
|on: On<FocusedInput<KeyboardInput>>,
244
font_size_input_query: Query<&EditableText, With<FontSizeInput>>,
245
mut multiline_input_font: Single<
246
&mut TextFont,
247
With<MultilineInput>,
248
>| {
249
if !(on.input.state.is_pressed()
250
&& on.input.logical_key == Key::Enter)
251
{
252
return;
253
}
254
255
let Ok(input) =
256
font_size_input_query.get(on.original_event_target())
257
else {
258
return;
259
};
260
261
let mut output = String::new();
262
output.reserve(input.value().into_iter().map(str::len).sum());
263
for sub_str in input.value() {
264
output.push_str(sub_str);
265
}
266
267
let Ok(font_size) = output.parse::<f32>() else {
268
return;
269
};
270
271
multiline_input_font.font_size =
272
FontSize::Px(font_size.clamp(5., 50.));
273
},
274
);
275
276
parent
277
.spawn((
278
Node {
279
flex_direction: FlexDirection::Row,
280
column_gap: px(10.),
281
..default()
282
},
283
children![
284
(
285
Text::new("corner radius:"),
286
TextFont {
287
font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
288
font_size: FontSize::Px(30.),
289
..default()
290
},
291
),
292
(
293
Node {
294
width: px(100.),
295
border: px(2.).all(),
296
..default()
297
},
298
TextFont {
299
font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),
300
font_size: FontSize::Px(30.),
301
..default()
302
},
303
TextLayout {
304
justify: Justify::End,
305
..default()
306
},
307
BackgroundColor(DARK_SLATE_GRAY.into()),
308
BorderColor::all(SLATE_300),
309
EditableText::new("0"),
310
EditableTextFilter::new(|c| c.is_ascii_digit() || c == '.'),
311
TextCursorStyle {
312
color: Color::WHITE,
313
selected_text_color: Some(Color::BLACK),
314
..default()
315
},
316
SelectionRadiusInput,
317
TabIndex(2),
318
)
319
],
320
))
321
.observe(
322
|on: On<FocusedInput<KeyboardInput>>,
323
radius_input_query: Query<
324
&EditableText,
325
With<SelectionRadiusInput>,
326
>,
327
mut cursor_style: Single<
328
&mut TextCursorStyle,
329
With<MultilineInput>,
330
>| {
331
if !(on.input.state.is_pressed()
332
&& on.input.logical_key == Key::Enter)
333
{
334
return;
335
}
336
337
let Ok(input) = radius_input_query.get(on.original_event_target())
338
else {
339
return;
340
};
341
342
let mut output = String::new();
343
output.reserve(input.value().into_iter().map(str::len).sum());
344
for sub_str in input.value() {
345
output.push_str(sub_str);
346
}
347
348
let Ok(radius) = output.parse::<f32>() else {
349
return;
350
};
351
352
cursor_style.selection_radius = radius.clamp(0., 0.5);
353
},
354
);
355
});
356
});
357
}
358
359