use bevy::{
input::{
keyboard::{Key, KeyboardInput},
mouse::MouseButtonInput,
},
prelude::*,
};
use bevy_hui::prelude::*;
pub struct HuiInputWidgetPlugin;
impl Plugin for HuiInputWidgetPlugin {
fn build(&self, app: &mut App) {
app.register_type::<TextInput>();
app.register_type::<TextFilter>();
app.add_systems(
Update,
(
unfocus,
focus.after(unfocus),
write_input,
sync_text_preview,
),
);
app.add_systems(Startup, setup);
}
}
pub const TAG_FILTER: &'static str = "filter";
#[derive(Component, Default, Reflect)]
#[reflect]
pub struct TextInput {
pub value: String,
pub filter: TextFilter,
}
#[derive(Default, Reflect)]
#[reflect]
pub enum TextFilter {
#[default]
None,
Text,
Number,
}
impl TextFilter {
pub fn apply(&self, c: &char) -> bool {
match self {
TextFilter::None => true,
TextFilter::Text => c.is_ascii_punctuation() || c.is_alphabetic(),
TextFilter::Number => c.is_numeric() || c == &'.',
}
}
}
impl From<&str> for TextFilter {
fn from(value: &str) -> Self {
match value {
"text" => TextFilter::Text,
"number" => TextFilter::Number,
_ => TextFilter::None,
}
}
}
fn setup(mut html_funcs: HtmlFunctions) {
html_funcs.register(
"init_input",
|In(entity), mut cmd: Commands, tags: Query<&Tags>| {
let filter = tags
.get(entity)
.ok()
.map(|tags| {
tags.get(TAG_FILTER)
.map(|str_val| TextFilter::from(str_val.as_str()))
})
.flatten()
.unwrap_or_default();
cmd.entity(entity).insert(TextInput {
value: default(),
filter,
});
},
);
}
fn focus(
mut cmd: Commands,
text_inputs: Query<(Entity, &Interaction), (With<TextInput>, Without<UiActive>)>,
) {
for (entity, interaction) in text_inputs.iter() {
if matches!(interaction, Interaction::Pressed) {
cmd.entity(entity).insert(UiActive);
}
}
}
fn unfocus(
mut cmd: Commands,
text_inputs: Query<Entity, (With<TextInput>, With<UiActive>)>,
mut mouse_events: MessageReader<MouseButtonInput>,
) {
for event in mouse_events.read() {
if !event.state.is_pressed() {
continue;
}
for entity in text_inputs.iter() {
cmd.entity(entity).remove::<UiActive>();
}
}
}
fn write_input(
mut cmd: Commands,
mut events: MessageReader<KeyboardInput>,
mut text_inputs: Query<(Entity, &mut TextInput), With<UiActive>>,
) {
if text_inputs.is_empty() {
return;
}
for input in events.read() {
if !input.state.is_pressed() {
continue;
}
match input.logical_key {
Key::Character(ref char) => {
text_inputs.iter_mut().for_each(|(_, mut txt)| {
for c in char.chars() {
if txt.filter.apply(&c) {
txt.value.push(c);
}
}
});
}
Key::Enter => {
text_inputs.iter().for_each(|(ent, _)| {
cmd.entity(ent).remove::<UiActive>().remove::<UiActive>();
});
}
Key::Backspace => {
text_inputs.iter_mut().for_each(|(_, mut txt)| {
_ = txt.value.pop();
});
}
Key::Space => {
text_inputs
.iter_mut()
.for_each(|(_, mut txt)| txt.value.push(' '));
}
_ => (),
}
}
}
fn sync_text_preview(
mut cmd: Commands,
inputs: Query<(Entity, &TextInput, &UiTarget), Changed<TextInput>>,
mut texts: Query<&mut Text>,
) {
for (entity, text_input, target) in inputs.iter() {
_ = texts.get_mut(**target).map(|mut text| {
text.0.clone_from(&text_input.value);
});
cmd.trigger(UiChangedEvent { entity: entity });
}
}