use std::collections::HashMap;
use std::time::Duration;
use bevy::camera::visibility::RenderLayers;
use bevy::prelude::*;
use crate::styles::components::UiStyle;
use crate::styles::paint::Colored;
use crate::styles::{Background, CssClass, CssSource, FontVal, TagName};
use crate::utils::keycode_to_char;
use crate::widgets::{BindToID, InputCap, InputField, InputType, InputValue, UIGenID, UIWidgetState, WidgetId, WidgetKind};
use crate::{CurrentWidgetState, ExtendedUiConfiguration, ImageCache};
#[derive(Component)]
struct InputFieldBase;
#[derive(Component)]
struct InputFieldText;
#[derive(Component)]
struct InputFieldIcon;
#[derive(Component)]
struct InputFieldIconImage;
#[derive(Component)]
struct InputCursor;
#[derive(Component)]
struct InputContainer;
#[derive(Component)]
struct OverlayLabel;
#[derive(Resource, Default)]
struct KeyRepeatTimers {
timers: HashMap<KeyCode, Timer>,
}
#[derive(Component)]
struct OriginalWidth(pub f32);
#[derive(Resource)]
pub struct CursorBlinkTimer {
pub timer: Timer,
}
impl Default for CursorBlinkTimer {
fn default() -> Self {
Self {
timer: Timer::from_seconds(0.95, TimerMode::Repeating),
}
}
}
pub struct InputWidget;
impl Plugin for InputWidget {
fn build(&self, app: &mut App) {
app.insert_resource(KeyRepeatTimers::default());
app.insert_resource(CursorBlinkTimer::default());
app.add_systems(
Update,
(
internal_node_creation_system,
sync_input_field_updates,
update_cursor_visibility,
update_cursor_position,
handle_typing,
handle_input_horizontal_scroll,
calculate_correct_text_container_width,
handle_overlay_label,
),
);
}
}
fn internal_node_creation_system(
mut commands: Commands,
mut query: Query<
(Entity, &UIGenID, &mut InputField, Option<&CssSource>),
(With<InputField>, Without<InputFieldBase>),
>,
config: Res<ExtendedUiConfiguration>,
asset_server: Res<AssetServer>,
mut image_cache: ResMut<ImageCache>,
) {
let layer = *config.render_layers.first().unwrap_or(&1);
for (entity, id, mut field, source_opt) in query.iter_mut() {
let css_source = source_opt.cloned().unwrap_or_default();
if !field.text.is_empty() {
field.cursor_position = field.text.len();
}
commands
.entity(entity)
.insert((
Name::new(format!("Input-{}", field.entry)),
Node::default(),
WidgetId {
id: field.entry,
kind: WidgetKind::InputField,
},
BackgroundColor::default(),
ImageNode::default(),
BorderColor::default(),
BoxShadow::new(
Colored::TRANSPARENT,
Val::Px(0.),
Val::Px(0.),
Val::Px(0.),
Val::Px(0.),
),
ZIndex::default(),
Pickable::default(),
css_source.clone(),
TagName(String::from("input")),
RenderLayers::layer(layer),
InputFieldBase,
InputValue(field.text.clone()),
))
.with_children(|builder| {
let icon_path = field
.icon_path
.as_deref()
.map(str::trim)
.filter(|path| !path.is_empty());
if let Some(icon_path) = icon_path {
let owned_icon = icon_path.to_string();
let handle = image_cache
.map
.entry(owned_icon.clone())
.or_insert_with(|| asset_server.load(owned_icon))
.clone();
builder.spawn((
Name::new(format!("Input-Icon-{}", field.entry)),
Node::default(),
BackgroundColor::default(),
ImageNode::default(),
BorderColor::default(),
ZIndex::default(),
UIWidgetState::default(),
css_source.clone(),
CssClass(vec!["in-icon-container".to_string()]),
Pickable::IGNORE,
RenderLayers::layer(layer),
InputFieldIcon,
BindToID(id.0),
children![(
Name::new(format!("Icon-{}", field.entry)),
ImageNode {
image: handle,
..default()
},
ZIndex::default(),
UIWidgetState::default(),
css_source.clone(),
CssClass(vec!["in-icon".to_string()]),
Pickable::IGNORE,
RenderLayers::layer(layer),
InputFieldIconImage,
BindToID(id.0),
)],
));
}
builder.spawn((
Name::new(format!("Input-Label-{}", field.entry)),
Node::default(),
Text::new(field.label.clone()),
TextColor::default(),
TextLayout::default(),
TextFont::default(),
ZIndex::default(),
UIWidgetState::default(),
css_source.clone(),
CssClass(vec!["input-label".to_string()]),
Pickable::IGNORE,
RenderLayers::layer(layer),
OverlayLabel,
BindToID(id.0),
));
builder
.spawn((
Name::new(format!("Input-Text-Container-{}", field.entry)),
Node::default(),
BackgroundColor::default(),
BorderColor::default(),
ZIndex::default(),
UIWidgetState::default(),
css_source.clone(),
CssClass(vec!["in-text-container".to_string()]),
Pickable::IGNORE,
OriginalWidth(-1.),
RenderLayers::layer(layer),
InputContainer,
BindToID(id.0),
children![
(
Name::new(format!("Cursor-{}", field.entry)),
Node::default(),
BackgroundColor::default(),
ImageNode::default(),
BorderColor::default(),
ZIndex::default(),
UIWidgetState::default(),
css_source.clone(),
CssClass(vec!["input-cursor".to_string()]),
Visibility::Hidden,
Pickable::IGNORE,
RenderLayers::layer(layer),
InputCursor,
BindToID(id.0),
),
(
Name::new(format!("Text-{}", field.entry)),
Node::default(),
Text::new(field.text.clone()),
TextColor::default(),
TextLayout::default(),
TextFont::default(),
ZIndex::default(),
UIWidgetState::default(),
css_source.clone(),
CssClass(vec!["input-text".to_string()]),
Pickable::IGNORE,
RenderLayers::layer(layer),
InputFieldText,
BindToID(id.0),
)
],
))
.insert(ImageNode::default());
})
.observe(on_internal_click)
.observe(on_internal_cursor_entered)
.observe(on_internal_cursor_leave);
}
}
fn sync_input_field_updates(
mut commands: Commands,
config: Res<ExtendedUiConfiguration>,
asset_server: Res<AssetServer>,
mut image_cache: ResMut<ImageCache>,
mut query: Query<
(
Entity,
&mut InputField,
&mut InputValue,
&UIGenID,
&UIWidgetState,
Option<&CssSource>,
),
(With<InputFieldBase>, Changed<InputField>),
>,
children_query: Query<&Children, With<InputFieldBase>>,
mut text_query: Query<(&mut Text, &BindToID), (With<InputFieldText>, Without<OverlayLabel>)>,
mut label_query: Query<(&mut Text, &BindToID), (With<OverlayLabel>, Without<InputFieldText>)>,
icon_query: Query<(Entity, &BindToID), With<InputFieldIcon>>,
mut icon_image_query: Query<(&mut ImageNode, &BindToID), With<InputFieldIconImage>>,
) {
let layer = *config.render_layers.first().unwrap_or(&1);
let mut icon_entities: HashMap<usize, Entity> = HashMap::new();
for (entity, bind) in icon_query.iter() {
icon_entities.insert(bind.0, entity);
}
for (entity, mut field, mut input_value, ui_id, state, source_opt) in query.iter_mut() {
let css_source = source_opt.cloned().unwrap_or_default();
field.cursor_position = field.cursor_position.min(field.text.len());
if input_value.0 != field.text {
input_value.0 = field.text.clone();
}
for (mut text, bind_id) in text_query.iter_mut() {
if bind_id.0 != ui_id.0 {
continue;
}
if state.focused {
set_visible_text(&*field, &mut text);
} else if field.text.is_empty() {
text.0.clear();
} else {
set_visible_text(&*field, &mut text);
}
}
for (mut label_text, bind_id) in label_query.iter_mut() {
if bind_id.0 != ui_id.0 {
continue;
}
label_text.0 = field.label.clone();
}
let icon_path = field
.icon_path
.as_deref()
.map(str::trim)
.filter(|path| !path.is_empty());
if let Some(icon_path) = icon_path {
let owned_icon = icon_path.to_string();
let handle = image_cache
.map
.entry(owned_icon.clone())
.or_insert_with(|| asset_server.load(owned_icon))
.clone();
if let Some(icon_entity) = icon_entities.get(&ui_id.0).copied() {
for (mut image_node, bind_id) in icon_image_query.iter_mut() {
if bind_id.0 != ui_id.0 {
continue;
}
image_node.image = handle.clone();
}
commands.entity(icon_entity).insert(Visibility::Inherited);
} else {
let icon_entity = commands
.spawn((
Name::new(format!("Input-Icon-{}", field.entry)),
Node::default(),
BackgroundColor::default(),
ImageNode::default(),
BorderColor::default(),
ZIndex::default(),
UIWidgetState::default(),
css_source.clone(),
CssClass(vec!["in-icon-container".to_string()]),
Pickable::IGNORE,
RenderLayers::layer(layer),
InputFieldIcon,
BindToID(ui_id.0),
children![(
Name::new(format!("Icon-{}", field.entry)),
ImageNode {
image: handle,
..default()
},
ZIndex::default(),
UIWidgetState::default(),
css_source.clone(),
CssClass(vec!["in-icon".to_string()]),
Pickable::IGNORE,
RenderLayers::layer(layer),
InputFieldIconImage,
BindToID(ui_id.0),
)],
)).id();
if children_query.get(entity).is_ok() {
commands.entity(entity).insert_children(0, &[icon_entity]);
} else {
commands.entity(entity).add_child(icon_entity);
}
}
} else if let Some(icon_entity) = icon_entities.get(&ui_id.0).copied() {
commands.entity(icon_entity).despawn();
}
}
}
fn update_cursor_visibility(
time: Res<Time>,
mut cursor_blink_timer: ResMut<CursorBlinkTimer>,
mut cursor_query: Query<
(
&mut Visibility,
&mut BackgroundColor,
&mut UiStyle,
&BindToID,
),
With<InputCursor>,
>,
input_field_query: Query<(&InputField, &UIWidgetState, &UIGenID), With<InputFieldBase>>,
mut text_query: Query<(&mut Text, &BindToID), With<InputFieldText>>,
) {
cursor_blink_timer.timer.tick(time.delta());
#[derive(Clone)]
struct FieldView {
focused: bool,
disabled: bool,
input_type: InputType,
text: String,
placeholder: String,
}
let mut fields: HashMap<usize, FieldView> = HashMap::new();
for (field, state, ui_id) in input_field_query.iter() {
fields.insert(
ui_id.0,
FieldView {
focused: state.focused,
disabled: state.disabled,
input_type: field.input_type.clone(),
text: field.text.clone(),
placeholder: field.placeholder.clone(),
},
);
}
for (mut visibility, mut background, mut styles, bind_cursor_id) in cursor_query.iter_mut() {
let Some(field) = fields.get(&bind_cursor_id.0) else {
continue;
};
if field.focused && !field.disabled {
let alpha =
(cursor_blink_timer.timer.elapsed_secs() * 2.0 * std::f32::consts::PI).sin() * 0.5
+ 0.5;
background.0.set_alpha(alpha);
for (_, style) in styles.styles.iter_mut() {
style.normal.background = Some(Background {
color: background.0,
..default()
});
}
let needs_show = !matches!(*visibility, Visibility::Inherited | Visibility::Visible);
if needs_show {
*visibility = Visibility::Inherited;
for (mut text, bind_id) in text_query.iter_mut() {
if bind_id.0 != bind_cursor_id.0 {
continue;
}
let shown = if field.input_type == InputType::Password {
if field.text.is_empty() {
field.placeholder.clone()
} else {
"*".repeat(field.text.chars().count())
}
} else if field.text.is_empty() {
field.placeholder.clone()
} else {
field.text.clone()
};
text.0 = shown;
}
}
} else {
if !matches!(*visibility, Visibility::Hidden) {
*visibility = Visibility::Hidden;
for (mut text, bind_id) in text_query.iter_mut() {
if bind_id.0 != bind_cursor_id.0 {
continue;
}
if field.text.is_empty() {
text.0.clear();
}
}
}
}
}
}
fn update_cursor_position(
mut key_repeat: ResMut<KeyRepeatTimers>,
mut cursor_query: Query<(&mut Node, &mut UiStyle, &BindToID), With<InputCursor>>,
mut text_field_query: Query<
(&mut InputField, &UIGenID, &UIWidgetState),
(With<InputField>, Without<InputCursor>),
>,
text_query: Query<(&TextFont, &BindToID), (With<InputFieldText>, Without<InputCursor>)>,
keyboard_input: Res<ButtonInput<KeyCode>>,
time: Res<Time>,
) {
let initial_delay = 0.3;
let repeat_rate = 0.07;
let mut fonts: HashMap<usize, TextFont> = HashMap::new();
for (font, bind) in text_query.iter() {
fonts.insert(bind.0, font.clone());
}
for (mut cursor_node, mut styles, bind_id) in cursor_query.iter_mut() {
let Some((mut text_field, _ui_id, state)) = text_field_query
.iter_mut()
.find(|(_, ui_id, _)| ui_id.0 == bind_id.0)
else {
continue;
};
if !state.focused || state.disabled {
continue;
}
if keyboard_input.just_pressed(KeyCode::ArrowLeft) {
text_field.cursor_position = text_field.cursor_position.saturating_sub(1);
key_repeat.timers.insert(
KeyCode::ArrowLeft,
Timer::from_seconds(initial_delay, TimerMode::Once),
);
}
if keyboard_input.just_pressed(KeyCode::ArrowRight) {
text_field.cursor_position =
(text_field.cursor_position + 1).min(text_field.text.len());
key_repeat.timers.insert(
KeyCode::ArrowRight,
Timer::from_seconds(initial_delay, TimerMode::Once),
);
}
for key in [KeyCode::ArrowLeft, KeyCode::ArrowRight] {
if keyboard_input.pressed(key) {
if let Some(timer) = key_repeat.timers.get_mut(&key) {
timer.tick(time.delta());
if timer.is_finished() {
match key {
KeyCode::ArrowLeft => {
text_field.cursor_position =
text_field.cursor_position.saturating_sub(1);
}
KeyCode::ArrowRight => {
text_field.cursor_position =
(text_field.cursor_position + 1).min(text_field.text.len());
}
_ => {}
}
timer.set_duration(Duration::from_secs_f32(repeat_rate));
timer.reset();
}
}
}
}
let Some(text_font) = fonts.get(&bind_id.0) else {
continue;
};
let cursor_x_position =
calculate_cursor_x_position(&text_field, text_field.cursor_position, text_font);
cursor_node.left = Val::Px(cursor_x_position);
for (_, style) in styles.styles.iter_mut() {
style.normal.left = Some(cursor_node.left);
}
}
key_repeat
.timers
.retain(|key, _| keyboard_input.pressed(*key));
}
fn calculate_correct_text_container_width(
query: Query<
(&InputField, &UIGenID),
(
With<InputField>,
Without<InputContainer>, // <-- add this to make queries disjoint
Or<(Added<InputField>, Changed<UiStyle>, Changed<InputField>)>,
),
>,
mut container_query: Query<(&mut UiStyle, &mut OriginalWidth, &BindToID), With<InputContainer>>,
) {
for (input_field, ui_id) in query.iter() {
if input_field.icon_path.is_none() {
continue;
}
for (mut style, mut original_width, bind_id) in container_query.iter_mut() {
if bind_id.0 != ui_id.0 {
continue;
}
let Some(active) = style.active_style.clone() else {
continue;
};
let current = match active.width.unwrap_or_default() {
Val::Percent(percent) => percent,
_ => 100.,
};
if original_width.0 == -1.0 {
original_width.0 = current;
}
if original_width.0 > current {
continue;
}
for (_, value) in style.styles.iter_mut() {
value.normal.width = Some(Val::Percent((current - 15.0).max(0.0)));
}
}
}
}
fn handle_input_horizontal_scroll(
query: Query<(&InputField, &UIGenID, &UIWidgetState), With<InputFieldBase>>,
mut scroll_query: Query<(&mut ScrollPosition, &BindToID), With<InputContainer>>,
text_node_query: Query<(&ComputedNode, &BindToID, &TextFont), With<InputFieldText>>,
) {
let mut text_meta: HashMap<usize, (Vec2, f32)> = HashMap::new();
for (node, bind_id, font) in text_node_query.iter() {
text_meta.insert(bind_id.0, (node.size(), font.font_size));
}
for (input_field, ui_id, state) in query.iter() {
if !state.focused || state.disabled {
continue;
}
let Some((text_size, font_size)) = text_meta.get(&ui_id.0).copied() else {
continue;
};
let char_width = font_size;
let cursor_x = input_field.cursor_position as f32 * char_width;
let available_width = text_size.x - 10.0;
for (mut scroll, bind_id) in scroll_query.iter_mut() {
if bind_id.0 != ui_id.0 {
continue;
}
match input_field.cap_text_at {
InputCap::NoCap => {
let visible_left = scroll.x;
let visible_right = scroll.x + available_width;
if cursor_x > visible_right {
scroll.x = cursor_x - available_width + char_width;
} else if cursor_x < visible_left {
scroll.x = cursor_x;
}
let total_text_width = input_field.text.len() as f32 * char_width;
if total_text_width < available_width {
scroll.x = 0.0;
}
}
_ => {}
}
}
}
}
fn handle_typing(
time: Res<Time>,
mut key_repeat: ResMut<KeyRepeatTimers>,
mut query: Query<(&mut InputField, &mut InputValue, &mut UIWidgetState, &UiStyle, &UIGenID)>,
keyboard: Res<ButtonInput<KeyCode>>,
mut text_query: Query<
(
&mut Text,
&mut TextColor,
&UiStyle,
&ComputedNode,
&BindToID,
),
(With<InputFieldText>, With<BindToID>),
>,
) {
let shift = keyboard.pressed(KeyCode::ShiftLeft) || keyboard.pressed(KeyCode::ShiftRight);
let alt = keyboard.pressed(KeyCode::AltLeft) || keyboard.pressed(KeyCode::AltRight);
let initial_delay = 0.3;
let repeat_rate = 0.07;
let pressed: Vec<KeyCode> = keyboard.get_pressed().copied().collect();
for (mut in_field, mut input_value, mut state, style, ui_id) in query.iter_mut() {
if state.disabled {
state.focused = false;
continue;
}
if !state.focused {
continue;
}
for (mut text, mut text_color, widget_style, computed_node, bind_id) in
text_query.iter_mut()
{
if bind_id.0 != ui_id.0 {
continue;
}
if keyboard.just_pressed(KeyCode::Enter) {
state.focused = false;
if in_field.clear_after_focus_lost {
in_field.text.clear();
text.0 = in_field.text.clone();
if input_value.0 != in_field.text {
input_value.0 = in_field.text.clone();
}
}
continue;
}
if keyboard.just_pressed(KeyCode::Backspace) {
if in_field.cursor_position > 0 && !in_field.text.is_empty() {
let remove_at = in_field.cursor_position - 1;
in_field.cursor_position = remove_at;
in_field.text.remove(remove_at);
set_visible_text(&in_field, &mut text);
if input_value.0 != in_field.text {
input_value.0 = in_field.text.clone();
}
}
if in_field.text.is_empty() {
text_color.0 = get_active_text_color(widget_style);
text.0 = in_field.placeholder.clone();
}
key_repeat.timers.insert(
KeyCode::Backspace,
Timer::from_seconds(initial_delay, TimerMode::Once),
);
continue;
}
for key in &pressed {
let Some(ch) = keycode_to_char(*key, shift, alt) else {
continue;
};
if !in_field.input_type.is_valid_char(ch) {
continue;
}
if keyboard.just_pressed(*key) {
let pos = in_field.cursor_position;
if in_field.cap_text_at.get_value() > 0 {
let cap = in_field.cap_text_at.clone();
if pos >= cap.get_value() {
continue;
}
}
if in_field.cap_text_at == InputCap::CapAtNodeSize {
let font_px = style
.active_style
.as_ref()
.and_then(|s| s.font_size.as_ref())
.cloned()
.unwrap_or(FontVal::Px(13.))
.get(None);
let allowed_char_len = (computed_node.size().x / font_px).round() as usize;
if pos >= allowed_char_len {
continue;
}
}
in_field.text.insert(pos, ch);
in_field.cursor_position += 1;
set_visible_text(&in_field, &mut text);
text_color.0 = get_active_text_color(widget_style);
if input_value.0 != in_field.text {
input_value.0 = in_field.text.clone();
}
key_repeat
.timers
.insert(*key, Timer::from_seconds(initial_delay, TimerMode::Once));
continue;
}
if let Some(timer) = key_repeat.timers.get_mut(key) {
timer.tick(time.delta());
if timer.is_finished() {
let pos = in_field.cursor_position;
in_field.text.insert(pos, ch);
in_field.cursor_position += 1;
set_visible_text(&in_field, &mut text);
if input_value.0 != in_field.text {
input_value.0 = in_field.text.clone();
}
timer.set_duration(Duration::from_secs_f32(repeat_rate));
timer.reset();
}
}
}
if keyboard.pressed(KeyCode::Backspace) {
if let Some(timer) = key_repeat.timers.get_mut(&KeyCode::Backspace) {
timer.tick(time.delta());
if timer.is_finished() {
if in_field.cursor_position > 0 && !in_field.text.is_empty() {
let remove_at = in_field.cursor_position - 1;
in_field.cursor_position = remove_at;
in_field.text.remove(remove_at);
set_visible_text(&in_field, &mut text);
input_value.0 = in_field.text.clone();
timer.set_duration(Duration::from_secs_f32(repeat_rate));
timer.reset();
}
}
}
}
in_field.cursor_position = in_field.cursor_position.min(in_field.text.len());
}
}
key_repeat.timers.retain(|key, _| keyboard.pressed(*key));
}
fn handle_overlay_label(
query: Query<
(&UIWidgetState, &UIGenID, &InputField, &UiStyle, &Children),
(With<InputField>, Without<OverlayLabel>),
>,
mut label_query: Query<
(&BindToID, &mut Node, &mut TextFont, &mut UiStyle),
(With<OverlayLabel>, Without<InputField>),
>,
icon_container_query: Query<
(&UiStyle, &BindToID),
(With<InputFieldIcon>, Without<OverlayLabel>),
>,
) {
let mut icon_widths: HashMap<usize, f32> = HashMap::new();
for (icon_style, bind) in icon_container_query.iter() {
if let Some(active) = icon_style.active_style.clone() {
if let Some(Val::Px(w)) = active.width {
icon_widths.insert(bind.0, w);
}
}
}
for (state, gen_id, in_field, in_style, children) in query.iter() {
let Some(active_style) = in_style.active_style.clone() else {
continue;
};
let height = match active_style.height.unwrap_or_default() {
Val::Px(px) => px,
_ => 55.,
};
for child in children.iter() {
let Ok((bind_to, mut node, mut text_font, mut styles)) = label_query.get_mut(child)
else {
continue;
};
if bind_to.0 != gen_id.0 {
continue;
}
let center = (height / 2.0) - text_font.font_size / 1.5;
let on_top = text_font.font_size / 2.0;
if state.focused {
node.top = Val::Px(on_top);
text_font.font_size = 10.;
} else if in_field.text.is_empty() {
node.top = Val::Px(center);
text_font.font_size = 14.;
} else {
node.top = Val::Px(on_top);
text_font.font_size = 10.;
}
let expected_left = if let Some(w) = icon_widths.get(&gen_id.0).copied() {
5.0 + w
} else {
10.0
};
let left_now = match node.left {
Val::Px(px) => px,
_ => 10.0,
};
if (left_now - expected_left).abs() > f32::EPSILON {
node.left = Val::Px(expected_left);
}
for (_, style) in styles.styles.iter_mut() {
style.normal.top = Some(node.top);
style.normal.left = Some(node.left);
style.normal.font_size = Some(FontVal::Px(text_font.font_size));
}
}
}
}
fn calculate_cursor_x_position(
text_field: &InputField,
cursor_pos: usize,
style: &TextFont,
) -> f32 {
if text_field.text.is_empty() || cursor_pos == 0 {
return 0.0;
}
let cursor_pos = cursor_pos.min(text_field.text.len());
let text_substr = &text_field.text[..cursor_pos];
let text_width = calculate_text_width(text_substr, style);
text_width + 1.0
}
fn calculate_text_width(text: &str, style: &TextFont) -> f32 {
text.len() as f32 * style.font_size * 0.6
}
fn get_active_text_color(style: &UiStyle) -> Color {
style
.active_style
.as_ref()
.and_then(|s| s.color)
.unwrap_or(Color::WHITE)
}
fn set_visible_text(in_field: &InputField, out: &mut Text) {
if in_field.text.is_empty() {
out.0 = in_field.placeholder.clone();
} else if in_field.input_type == InputType::Password {
out.0 = "*".repeat(in_field.text.chars().count());
} else {
out.0 = in_field.text.clone();
}
}
fn on_internal_click(
mut trigger: On<Pointer<Click>>,
mut query: Query<(&mut UIWidgetState, &UIGenID), With<InputField>>,
bind_query: Query<&BindToID>,
mut current_widget_state: ResMut<CurrentWidgetState>,
) {
let target = trigger.event_target();
if let Ok((mut state, gen_id)) = query.get_mut(target) {
if !state.disabled {
state.focused = true;
current_widget_state.widget_id = gen_id.0;
}
} else if let Ok(bind) = bind_query.get(target) {
if let Some((mut state, gen_id)) = query.iter_mut().find(|(_, id)| id.0 == bind.0) {
if !state.disabled {
state.focused = true;
current_widget_state.widget_id = gen_id.0;
}
}
}
trigger.propagate(false);
}
fn on_internal_cursor_entered(
mut trigger: On<Pointer<Over>>,
mut query: Query<(&mut UIWidgetState, &UIGenID), With<InputField>>,
bind_query: Query<&BindToID>,
) {
let target = trigger.event_target();
if let Ok((mut state, _)) = query.get_mut(target) {
state.hovered = true;
} else if let Ok(bind) = bind_query.get(target) {
if let Some((mut state, _)) = query.iter_mut().find(|(_, id)| id.0 == bind.0) {
state.hovered = true;
}
}
trigger.propagate(false);
}
fn on_internal_cursor_leave(
mut trigger: On<Pointer<Out>>,
mut query: Query<(&mut UIWidgetState, &UIGenID), With<InputField>>,
bind_query: Query<&BindToID>,
) {
let target = trigger.event_target();
if let Ok((mut state, _)) = query.get_mut(target) {
state.hovered = false;
} else if let Ok(bind) = bind_query.get(target) {
if let Some((mut state, _)) = query.iter_mut().find(|(_, id)| id.0 == bind.0) {
state.hovered = false;
}
}
trigger.propagate(false);
}