Path: blob/main/examples/ui/text/multiple_text_inputs.rs
30635 views
//! Demonstrates multiple text inputs1//!2//! This example arranges three text inputs in a 3x3 grid layout. The first column of each row is an [`EditableText`] text input node, the second column is a `Text` node3//! that is kept synchronized with the [`EditableText`]'s contents by the [`synchronize_output_text`] system, and the third column is updated4//! by the [`submit_text`] system when the user submits the [`EditableText`]'s text by pressing `Enter`.56use bevy::color::palettes::tailwind::SLATE_300;7use bevy::input::keyboard::Key;8use bevy::input_focus::tab_navigation::NavAction;9use bevy::input_focus::{tab_navigation::TabNavigation, AutoFocus, FocusCause};10use bevy::input_focus::{11tab_navigation::{TabGroup, TabIndex, TabNavigationPlugin},12InputFocus,13};14use bevy::prelude::*;15use bevy::text::{EditableText, TextCursorStyle};1617fn main() {18App::new()19// `EditableTextInputPlugin` is part of `DefaultPlugins`20.add_plugins((DefaultPlugins, TabNavigationPlugin))21.add_systems(Startup, setup)22.add_systems(23Update,24(25synchronize_output_text,26submit_text,27update_row_border_colors,28),29)30.run();31}3233#[derive(Component)]34struct TextOutput;3536#[derive(Component)]37struct SubmitOutput;3839#[derive(Component)]40struct TextInputRow(usize);4142fn setup(mut commands: Commands, asset_server: Res<AssetServer>) {43commands.spawn(Camera2d);4445let font = TextFont {46font: asset_server.load("fonts/FiraMono-Medium.ttf").into(),47font_size: FontSize::Px(24.),48..default()49};5051commands52.spawn((53Node {54width: percent(100.),55height: percent(100.),56display: Display::Grid,57justify_content: JustifyContent::Center,58align_content: AlignContent::Center,59grid_template_columns: RepeatedGridTrack::px(3, 320.),60grid_template_rows: RepeatedGridTrack::auto(6),61row_gap: px(8.),62column_gap: px(8.),63..default()64},65TabGroup::default(),66))67.with_children(|parent| {68parent.spawn((69Text::new("Multiple Text Inputs Example"),70Node {71grid_column: GridPlacement::span(3),72justify_self: JustifySelf::Center,73margin: px(16).bottom(),74..default()75},76TextColor::WHITE,77font.clone(),78));7980let label_font = font.clone().with_font_size(14.);81for label in ["EditableText", "value", "submission"] {82parent.spawn((83Text::new(label),84label_font.clone(),85Node {86justify_self: JustifySelf::Center,87margin: px(-4).bottom(),88..default()89},90));91}9293for row in 0..3 {94let mut input = parent.spawn((95Node {96border: px(4.).all(),97padding: px(4.).all(),98..default()99},100EditableText::new(format!("Initial text {row}")),101TextCursorStyle::default(),102font.clone(),103BackgroundColor(bevy::color::palettes::css::DARK_GREY.into()),104TextInputRow(row),105TextLayout::no_wrap(),106TabIndex(row as i32),107BorderColor::all(SLATE_300),108));109if row == 0 {110input.insert(AutoFocus);111}112113parent.spawn((114Node {115border: px(4.).all(),116padding: px(4.).all(),117overflow: Overflow::clip_x(),118overflow_clip_margin: OverflowClipMargin {119visual_box: VisualBox::ContentBox,120..default()121},122..default()123},124BackgroundColor(bevy::color::palettes::css::DARK_SLATE_BLUE.into()),125BorderColor::all(Color::WHITE),126children![(127Text::default(),128TextLayout::no_wrap(),129font.clone(),130BackgroundColor(bevy::color::palettes::css::DARK_SLATE_GRAY.into()),131BorderColor::all(Color::WHITE),132TextInputRow(row),133TextOutput,134)],135));136137parent.spawn((138Node {139border: px(4.).all(),140padding: px(4.).all(),141overflow: Overflow::clip_x(),142overflow_clip_margin: OverflowClipMargin {143visual_box: VisualBox::ContentBox,144..default()145},146147..default()148},149BackgroundColor(bevy::color::palettes::css::DARK_SLATE_BLUE.into()),150BorderColor::all(Color::WHITE),151children![(152Text::default(),153TextLayout::no_wrap(),154font.clone(),155TextInputRow(row),156SubmitOutput,157)],158));159}160161parent.spawn((162Text::new("Press Enter to submit"),163Node {164grid_column: GridPlacement::span(3),165justify_self: JustifySelf::Center,166margin: px(16).top(),167..default()168},169font.clone(),170));171});172}173174/// This system keeps the text of the [`TextOutput`] [`Text`] nodes synchronized with the text175/// of the [`EditableText`] node on the same row.176fn synchronize_output_text(177changed_inputs: Query<(&EditableText, &TextInputRow), Changed<EditableText>>,178mut outputs: Query<(&mut Text, &TextInputRow), With<TextOutput>>,179) {180for (editable_text, input_row) in &changed_inputs {181for (mut text, output_row) in &mut outputs {182if output_row.0 == input_row.0 {183// `EditableText::value()` returns a `SplitString` because Parley may keep IME preedit text184// in a contiguous range of the editor’s internal `String` buffer during composition.185// The returned `SplitString` omits that preedit range, exposing only the text before and after it.186//187// To avoid allocating a new `String`, we reserve the total length of the `SplitString`'s slices,188// then append them to the output `Text`.189text.0.clear();190text.0191.reserve(editable_text.value().into_iter().map(str::len).sum());192for sub_str in editable_text.value() {193text.0.push_str(sub_str);194}195}196}197}198}199200// Submit the focused input's text when Enter is pressed.201fn submit_text(202mut input_focus: ResMut<InputFocus>,203keyboard_input: Res<ButtonInput<Key>>,204mut text_input: Query<(&mut EditableText, &TextInputRow)>,205mut text_output: Query<(&mut Text, &TextInputRow), With<SubmitOutput>>,206tab_navigation: TabNavigation,207) {208if keyboard_input.just_pressed(Key::Enter)209&& let Some(focused_entity) = input_focus.get()210&& let Ok((mut editable_text, input_row)) = text_input.get_mut(focused_entity)211{212for (mut text, output_row) in &mut text_output {213if input_row.0 == output_row.0 {214text.0.clear();215text.0216.reserve(editable_text.value().into_iter().map(str::len).sum());217for sub_str in editable_text.value() {218text.0.push_str(sub_str);219}220break;221}222}223editable_text.clear();224225if let Ok(next) = tab_navigation.navigate(&input_focus, NavAction::Next) {226input_focus.set(next, FocusCause::Navigated);227}228}229}230231/// Dim a row's border colors when its [`EditableText`] does not have input focus.232fn update_row_border_colors(233input_focus: Res<InputFocus>,234input_rows: Query<&TextInputRow, With<EditableText>>,235mut row_borders: Query<(&TextInputRow, &mut BorderColor, Has<EditableText>)>,236) {237if !input_focus.is_changed() {238return;239}240241let focused_row = input_focus242.get()243.and_then(|focused_entity| input_rows.get(focused_entity).ok())244.map(|row| row.0);245246for (row, mut border_color, is_input) in &mut row_borders {247let mut color = if is_input {248SLATE_300.into()249} else {250Color::WHITE251};252if Some(row.0) != focused_row {253color = color.darker(0.75);254}255border_color.set_all(color);256}257}258259260