-
-
Notifications
You must be signed in to change notification settings - Fork 4.5k
Feathers text input widget #23645
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Feathers text input widget #23645
Changes from 12 commits
e922391
2b6fc89
def60bb
913773f
9305fde
7694101
ca4d51a
4410c7f
a7172a5
6ea50c1
813cd20
21516ff
a7ab5c7
e52ace5
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,214 @@ | ||
| use bevy_app::{Plugin, PreUpdate}; | ||
| use bevy_ecs::{ | ||
| change_detection::{DetectChanges, DetectChangesMut}, | ||
| component::Component, | ||
| entity::Entity, | ||
| hierarchy::ChildOf, | ||
| lifecycle::RemovedComponents, | ||
| query::{Added, Has, With}, | ||
| schedule::IntoScheduleConfigs, | ||
| system::{Commands, Query, Res}, | ||
| }; | ||
| use bevy_input_focus::{tab_navigation::TabIndex, InputFocus, InputFocusVisible}; | ||
| use bevy_picking::PickingSystems; | ||
| use bevy_scene::prelude::*; | ||
| use bevy_text::{EditableText, FontSize, FontWeight, LineBreak, TextCursorStyle, TextLayout}; | ||
| use bevy_ui::{ | ||
| px, AlignItems, BorderColor, BorderRadius, Display, InteractionDisabled, JustifyContent, Node, | ||
| UiRect, Val, | ||
| }; | ||
|
|
||
| use crate::{ | ||
| constants::{fonts, size}, | ||
| cursor::EntityCursor, | ||
| font_styles::InheritableFont, | ||
| theme::{ThemeBackgroundColor, ThemeBorderColor, ThemeFontColor, ThemedText, UiTheme}, | ||
| tokens, | ||
| }; | ||
|
|
||
| /// Marker to indicate a text input widget with feathers styling. | ||
| #[derive(Component, Default, Clone)] | ||
| struct FeathersTextInputContainer; | ||
|
|
||
| /// Marker to indicate a the inner part of the text input widget. | ||
| #[derive(Component, Default, Clone)] | ||
| struct FeathersTextInput; | ||
|
|
||
| /// Parameters for the text input template, passed to [`text_input`] function. | ||
| pub struct TextInputProps { | ||
| /// Max characters | ||
| pub max_characters: Option<usize>, | ||
| } | ||
|
|
||
| /// Decorative frame around a text input widget. This is a separate entity to allow icons | ||
| /// (such as "search" or "clear") to be inserted adjacent to the input. | ||
| pub fn text_input_container() -> impl Scene { | ||
| bsn! { | ||
| Node { | ||
| height: size::ROW_HEIGHT, | ||
| display: Display::Flex, | ||
| justify_content: JustifyContent::Center, | ||
| align_items: AlignItems::Center, | ||
| padding: UiRect::axes(Val::Px(4.0), Val::Px(0.)), | ||
| border: UiRect::all(Val::Px(2.0)), | ||
| flex_grow: 1.0, | ||
| border_radius: {BorderRadius::all(px(4.0))}, | ||
| column_gap: px(4), | ||
| } | ||
| FeathersTextInputContainer | ||
| ThemeBorderColor(tokens::TEXT_INPUT_BG) | ||
| ThemeBackgroundColor(tokens::TEXT_INPUT_BG) | ||
| ThemeFontColor(tokens::TEXT_INPUT_TEXT) | ||
| InheritableFont { | ||
| font: fonts::REGULAR, | ||
| font_size: FontSize::Px(13.0), | ||
| weight: FontWeight::NORMAL, | ||
| } | ||
| } | ||
| } | ||
|
|
||
| /// Scene function to spawn a text input. For proper styling, this should be enclosed by a | ||
| /// `text_input_container`. | ||
| /// | ||
| /// ```ignore | ||
| /// :text_input_container | ||
| /// Children [ | ||
| /// text_input(props) | ||
| /// ] | ||
| /// ``` | ||
| /// | ||
| /// # Arguments | ||
| /// * `props` - construction properties for the text input. | ||
| pub fn text_input(props: TextInputProps) -> impl Scene { | ||
| bsn! { | ||
| Node { | ||
| flex_grow: 1.0, | ||
| } | ||
| FeathersTextInput | ||
| EditableText { | ||
| cursor_width: 0.3, | ||
| max_characters: {props.max_characters}, | ||
| } | ||
| TextLayout { | ||
| linebreak: LineBreak::NoWrap, | ||
| } | ||
| TabIndex(0) | ||
| ThemedText | ||
| EntityCursor::System(bevy_window::SystemCursorIcon::Text) | ||
| TextCursorStyle::default() | ||
| } | ||
| } | ||
|
|
||
| fn update_text_cursor_color( | ||
| mut q_text_input: Query<&mut TextCursorStyle, With<FeathersTextInput>>, | ||
| theme: Res<UiTheme>, | ||
| ) { | ||
| if theme.is_changed() { | ||
| for mut cursor_style in q_text_input.iter_mut() { | ||
| cursor_style.color = theme.color(&tokens::TEXT_INPUT_CURSOR); | ||
| cursor_style.selection_color = theme.color(&tokens::TEXT_INPUT_SELECTION); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fn update_text_input_styles( | ||
| q_inputs: Query< | ||
| (Entity, Has<InteractionDisabled>, &ThemeFontColor), | ||
| (With<FeathersTextInput>, Added<InteractionDisabled>), | ||
| >, | ||
| mut commands: Commands, | ||
| ) { | ||
| for (input_ent, disabled, font_color) in q_inputs.iter() { | ||
| set_text_input_styles(input_ent, disabled, font_color, &mut commands); | ||
| } | ||
| } | ||
|
|
||
| fn update_text_input_styles_remove( | ||
| q_inputs: Query<(Entity, Has<InteractionDisabled>, &ThemeFontColor), With<FeathersTextInput>>, | ||
| mut removed_disabled: RemovedComponents<InteractionDisabled>, | ||
| mut commands: Commands, | ||
| ) { | ||
| removed_disabled.read().for_each(|ent| { | ||
| if let Ok((input_ent, disabled, font_color)) = q_inputs.get(ent) { | ||
| set_text_input_styles(input_ent, disabled, font_color, &mut commands); | ||
| } | ||
| }); | ||
| } | ||
|
|
||
| fn update_text_input_focus( | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Can we hook into the existing FocusIndicator infrastructure here?
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I can't, for two reasons:
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Kk, that's fine, but leave a comment to this effect please :)
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Comment added; I suppose it's possible to generalize |
||
| q_inputs: Query<(), With<FeathersTextInput>>, | ||
| q_input_containers: Query<(Entity, &mut BorderColor), With<FeathersTextInputContainer>>, | ||
| parents: Query<&ChildOf>, | ||
| focus: Res<InputFocus>, | ||
| focus_visible: Res<InputFocusVisible>, | ||
| theme: Res<UiTheme>, | ||
| ) { | ||
| if focus.is_changed() { | ||
| let focus_parent = focus.0.and_then(|focus_ent| { | ||
| if focus_visible.0 && q_inputs.contains(focus_ent) { | ||
| parents | ||
| .iter_ancestors(focus_ent) | ||
| .find(|ent| q_input_containers.contains(*ent)) | ||
| } else { | ||
| None | ||
| } | ||
| }); | ||
|
|
||
| for (container, mut border_color) in q_input_containers { | ||
| let new_border_color = if Some(container) == focus_parent { | ||
| theme.color(&tokens::FOCUS_RING) | ||
| } else { | ||
| theme.color(&tokens::TEXT_INPUT_BG) | ||
| }; | ||
|
|
||
| border_color.set_if_neq(BorderColor::all(new_border_color)); | ||
| } | ||
| } | ||
| } | ||
|
|
||
| fn set_text_input_styles( | ||
| input_ent: Entity, | ||
| disabled: bool, | ||
| font_color: &ThemeFontColor, | ||
| commands: &mut Commands, | ||
| ) { | ||
| let font_color_token = match disabled { | ||
alice-i-cecile marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| true => tokens::TEXT_INPUT_TEXT_DISABLED, | ||
| false => tokens::TEXT_INPUT_TEXT, | ||
| }; | ||
|
|
||
| let cursor_shape = match disabled { | ||
| true => bevy_window::SystemCursorIcon::NotAllowed, | ||
| false => bevy_window::SystemCursorIcon::Text, | ||
| }; | ||
|
|
||
| // Change font color | ||
| if font_color.0 != font_color_token { | ||
| commands | ||
| .entity(input_ent) | ||
| .insert(ThemeFontColor(font_color_token)); | ||
| } | ||
|
|
||
| // Change cursor shape | ||
| commands | ||
| .entity(input_ent) | ||
| .insert(EntityCursor::System(cursor_shape)); | ||
| } | ||
|
|
||
| /// Plugin which registers the systems for updating the text input styles. | ||
| pub struct TextInputPlugin; | ||
|
|
||
| impl Plugin for TextInputPlugin { | ||
| fn build(&self, app: &mut bevy_app::App) { | ||
| app.add_systems( | ||
| PreUpdate, | ||
| ( | ||
| update_text_cursor_color, | ||
| update_text_input_styles, | ||
| update_text_input_styles_remove, | ||
| update_text_input_focus, | ||
| ) | ||
| .in_set(PickingSystems::Last), | ||
| ); | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.