use crate::CurrentWidgetState;
use crate::widgets::{BindToID, IgnoreParentState, UIGenID, UIWidgetState};
use bevy::prelude::*;
use std::collections::HashMap;
#[derive(Resource, Default)]
pub struct BoundStateIndex {
by_widget: HashMap<usize, Vec<Entity>>,
by_entity: HashMap<Entity, usize>,
}
pub struct StateService;
impl Plugin for StateService {
fn build(&self, app: &mut App) {
app.init_resource::<BoundStateIndex>();
app.register_type::<Pickable>();
app.add_systems(
PostUpdate,
(refresh_bound_state_index, update_widget_states).chain(),
);
app.add_systems(
Update,
(
internal_state_check.run_if(resource_changed::<CurrentWidgetState>),
handle_tab_focus,
unfocus_disabled,
),
);
}
}
fn remove_from_bound_state_index(index: &mut BoundStateIndex, entity: Entity, widget_id: usize) {
let should_remove = if let Some(entries) = index.by_widget.get_mut(&widget_id) {
entries.retain(|current| *current != entity);
entries.is_empty()
} else {
false
};
if should_remove {
index.by_widget.remove(&widget_id);
}
}
fn refresh_bound_state_index(
mut index: ResMut<BoundStateIndex>,
query: Query<(Entity, &BindToID), Or<(Added<BindToID>, Changed<BindToID>)>>,
mut removed: RemovedComponents<BindToID>,
) {
for entity in removed.read() {
if let Some(previous_id) = index.by_entity.remove(&entity) {
remove_from_bound_state_index(&mut index, entity, previous_id);
}
}
for (entity, bind_to) in query.iter() {
let widget_id = bind_to.0;
if let Some(previous_id) = index.by_entity.insert(entity, widget_id) {
if previous_id != widget_id {
remove_from_bound_state_index(&mut index, entity, previous_id);
}
}
let entries = index.by_widget.entry(widget_id).or_default();
if !entries.contains(&entity) {
entries.push(entity);
}
}
}
pub fn update_widget_states(
main_query: Query<(&UIGenID, &UIWidgetState), (Changed<UIWidgetState>, With<UIGenID>)>,
index: Option<Res<BoundStateIndex>>,
mut inner_query: Query<
(Entity, &BindToID, &mut UIWidgetState),
(Without<UIGenID>, Without<IgnoreParentState>),
>,
) {
for (widget_id, widget_state) in main_query.iter() {
if let Some(bound_index) = index.as_ref() {
if let Some(bound_entities) = bound_index.by_widget.get(&widget_id.get()) {
for bound_entity in bound_entities {
let Ok((_, _, mut bound_state)) = inner_query.get_mut(*bound_entity) else {
continue;
};
copy_widget_state(&mut bound_state, widget_state);
}
continue;
}
}
for (_, bind_to, mut bound_state) in inner_query.iter_mut() {
if bind_to.0 != widget_id.get() {
continue;
}
copy_widget_state(&mut bound_state, widget_state);
}
}
}
fn copy_widget_state(target: &mut UIWidgetState, source: &UIWidgetState) {
target.hovered = source.hovered;
target.focused = source.focused;
target.readonly = source.readonly;
target.disabled = source.disabled;
target.checked = source.checked;
}
fn internal_state_check(
current_state_element: Res<CurrentWidgetState>,
mut query: Query<(&mut UIWidgetState, &UIGenID), With<UIGenID>>,
) {
for (mut state, gen_id) in query.iter_mut() {
if gen_id.get() == current_state_element.widget_id {
continue;
}
state.focused = false;
}
}
fn handle_tab_focus(
mut widget_query: Query<(Entity, &mut UIWidgetState, &UIGenID)>,
keyboard: Res<ButtonInput<KeyCode>>,
mut current_state: ResMut<CurrentWidgetState>,
) {
if !keyboard.just_pressed(KeyCode::Tab) {
return;
}
let reverse = keyboard.pressed(KeyCode::ShiftLeft) || keyboard.pressed(KeyCode::ShiftRight);
let mut focusable_widgets: Vec<_> = widget_query
.iter_mut()
.filter(|(_, state, _)| !state.disabled)
.collect();
focusable_widgets.sort_by_key(|(_, _, id)| id.get());
let focusable_count = focusable_widgets.len();
if focusable_count == 0 {
return;
}
let mut focused_index = None;
for (index, (_, state, _)) in focusable_widgets.iter().enumerate() {
if state.focused {
focused_index = Some(index);
break;
}
}
match focused_index {
Some(current_index) => {
focusable_widgets[current_index].1.focused = false;
let next_index = if reverse {
(current_index + focusable_count - 1) % focusable_count
} else {
(current_index + 1) % focusable_count
};
focusable_widgets[next_index].1.focused = true;
current_state.widget_id = focusable_widgets[next_index].2.get();
}
None => {
let next_index = if reverse { focusable_count - 1 } else { 0 };
focusable_widgets[next_index].1.focused = true;
current_state.widget_id = focusable_widgets[next_index].2.get();
}
}
}
fn unfocus_disabled(mut state_query: Query<&mut UIWidgetState, Changed<UIWidgetState>>) {
for mut widget_state in &mut state_query {
if widget_state.disabled && widget_state.focused {
widget_state.focused = false;
}
}
}