Skip to main content

multiline_text_input/
multiline_text_input.rs

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