docs/bevy/examples/ui/text/text_input.rs 5.8 K · 161 lines · raw · history

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.
25 use bevy::color::palettes::css::DARK_GREY;
26 use bevy::color::palettes::tailwind::SLATE_300;
27 use bevy::input_focus::AutoFocus;
28 use bevy::input_focus::{
29 tab_navigation::{TabGroup, TabIndex, TabNavigationPlugin},
30 InputFocus,
31 };
32 use bevy::prelude::*;
33 use bevy::text::{EditableText, TextCursorStyle};
34 use bevy::ui_widgets::TextInput;
35
36 fn 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)]
46 struct TextOutput;
47
48 fn 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
119 fn 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
147 fn 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 }