Skip to main content

text_input/
text_input.rs

1//! Demonstrates a simple, unstyled [`TextInput`] widget.
2//!
3//! [`TextInput`] is a basic primitive for text input in Bevy UI.
4//! In most cases, this should be combined with other entities to create a compound widget
5//! that includes e.g. a background, border, and text label.
6//!
7//! Note that while Bevy does offer clipboard support, access to the system clipboard is gated
8//! behind an off-by-default feature (`system_clipboard` on `bevy_clipboard`).
9//! When this is disabled, clipboard operations (copy, cut, paste) will operate on a simple in-memory buffer
10//! that is not shared with the operating system.
11//! This means that, unless you enable this feature,
12//! you will not be able to copy text from your application and paste it into another application, or vice versa.
13//!
14//! Most applications that use text input will want to enable system clipboard support to meet user expectations for copy/paste behavior.
15//! It is off by default to avoid forcing clipboard permissions on applications that do not need it but wish to use Bevy's UI solution for other widgets,
16//! and to avoid including the `arboard` dependency on platforms where it is not supported or where clipboard access is not desired.
17//! While desktop platforms generally support clipboard access without special permissions, some platforms (notably web and mobile)
18//! may require additional permissions or user gestures to allow clipboard access;
19//! this approach allows developers to opt in to full clipboard support only when they genuinely need it.
20//!
21//! To test this example using the system feature, run `cargo run --example text_input --features="system_clipboard"`.
22//! To enable this feature in your own project, add the `system_clipboard` feature to your list of enabled features for `bevy` in your `Cargo.toml`.
23//!
24//! See the module documentation for [`editable_text`](bevy::ui_widgets::editable_text) for more details.
25use bevy::color::palettes::css::DARK_GREY;
26use bevy::color::palettes::tailwind::SLATE_300;
27use bevy::input_focus::AutoFocus;
28use bevy::input_focus::{
29    tab_navigation::{TabGroup, TabIndex, TabNavigationPlugin},
30    InputFocus,
31};
32use bevy::prelude::*;
33use bevy::text::{EditableText, TextCursorStyle};
34use bevy::ui_widgets::TextInput;
35
36fn main() {
37    App::new()
38        .add_plugins(DefaultPlugins)
39        .add_plugins(TabNavigationPlugin)
40        .add_systems(Startup, setup)
41        .add_systems(Update, text_submission)
42        .run();
43}
44
45#[derive(Component)]
46struct TextOutput;
47
48fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {
49    commands.spawn(Camera2d);
50
51    let root = commands
52        .spawn(Node {
53            align_items: AlignItems::Center,
54            flex_direction: FlexDirection::Column,
55            padding: px(20).all(),
56            row_gap: px(16),
57            margin: auto().all(),
58            ..default()
59        })
60        .id();
61
62    let text_instructions = commands
63        .spawn((
64            Text::new("Enter to submit text\nTab to switch inputs"),
65            TextFont {
66                font: asset_server.load("fonts/FiraSans-Bold.ttf").into(),
67                font_size: FontSize::Px(25.0),
68                ..default()
69            },
70        ))
71        .id();
72
73    let text_input_left = build_input_text(&mut commands, true, 24.0);
74    let text_input_right = build_input_text(&mut commands, false, 24.0);
75
76    let input_container = commands
77        .spawn((
78            Node {
79                column_gap: px(16),
80                ..default()
81            },
82            AutoFocus,
83            TabGroup::new(0),
84        ))
85        .id();
86
87    // Set up a text output to see the result of our text input
88    let text_output = commands
89        .spawn((
90            Node {
91                width: px(400),
92                border: px(2).all(),
93                padding: px(8).all(),
94                ..Default::default()
95            },
96            BorderColor::from(Color::from(SLATE_300)),
97            Text::new(""),
98            TextOutput,
99            TextLayout {
100                linebreak: LineBreak::WordOrCharacter,
101                ..default()
102            },
103            TextFont {
104                font_size: FontSize::Px(24.0),
105                ..default()
106            },
107        ))
108        .id();
109
110    commands
111        .entity(input_container)
112        .add_children(&[text_input_left, text_input_right]);
113
114    commands
115        .entity(root)
116        .add_children(&[text_instructions, input_container, text_output]);
117}
118
119fn build_input_text(commands: &mut Commands, is_left: bool, font_size: f32) -> Entity {
120    commands
121        .spawn((
122            Node {
123                border: px(2).all(),
124                ..Default::default()
125            },
126            BorderColor::from(Color::from(SLATE_300)),
127            Name::new(if is_left { "Left" } else { "Right" }),
128            TextInput,
129            EditableText {
130                visible_width: Some(10.),
131                allow_newlines: false,
132                ..Default::default()
133            },
134            TextLayout::no_wrap(),
135            TextFont {
136                font_size: FontSize::Px(font_size),
137                ..default()
138            },
139            TextCursorStyle::default(),
140            TabIndex(if is_left { 0 } else { 1 }),
141            BackgroundColor(DARK_GREY.into()),
142        ))
143        .id()
144}
145
146// Submit the text when Ctrl+Enter is pressed
147fn text_submission(
148    input_focus: Res<InputFocus>,
149    keyboard_input: Res<ButtonInput<KeyCode>>,
150    mut text_input: Query<(&mut EditableText, &Name)>,
151    mut text_output: Single<&mut Text, With<TextOutput>>,
152) {
153    if keyboard_input.just_pressed(KeyCode::Enter)
154        && let Some(focused_entity) = input_focus.get()
155        && let Ok((mut text_input, name)) = text_input.get_mut(focused_entity)
156    {
157        text_output.0 = format!("{:}: {:}", name, text_input.value());
158
159        text_input.clear();
160    }
161}