use crate::ecs::ui::state::UiStateTrait as _;
use winit::keyboard::KeyCode;
use crate::ecs::ui::components::UiWidgetState;
use crate::ecs::world::World;
use super::InteractionSnapshot;
pub(super) fn handle_tab_bar(
world: &mut World,
entity: freecs::Entity,
data: &crate::ecs::ui::components::UiTabBarData,
frame_keys: &[(KeyCode, bool)],
focused_entity: Option<freecs::Entity>,
) {
let mut new_selected = data.selected_tab;
let mut changed = false;
for (index, tab_entity) in data.tab_entities.iter().enumerate() {
let tab_clicked = world
.ui
.get_ui_node_interaction(*tab_entity)
.map(|i| i.clicked)
.unwrap_or(false);
if tab_clicked && data.selected_tab != index {
new_selected = index;
changed = true;
break;
}
}
let any_tab_focused = data
.tab_entities
.iter()
.any(|tab| focused_entity == Some(*tab));
if any_tab_focused {
let tab_count = data.tab_entities.len();
for &(key, pressed) in frame_keys {
if !pressed {
continue;
}
match key {
KeyCode::ArrowLeft if new_selected > 0 => {
new_selected -= 1;
changed = true;
}
KeyCode::ArrowRight if new_selected + 1 < tab_count => {
new_selected += 1;
changed = true;
}
_ => {}
}
}
}
if changed {
let theme = world.resources.retained_ui.theme_state.active_theme();
let active_bg = theme.accent_color;
let inactive_bg = theme.background_color;
let old_entity = data.tab_entities[data.selected_tab];
let new_entity = data.tab_entities[new_selected];
if let Some(color) = world.ui.get_ui_node_color_mut(old_entity) {
color.colors[crate::ecs::ui::state::UiBase::INDEX] = Some(inactive_bg);
}
if let Some(color) = world.ui.get_ui_node_color_mut(new_entity) {
color.colors[crate::ecs::ui::state::UiBase::INDEX] = Some(active_bg);
}
}
if let Some(UiWidgetState::TabBar(widget_data)) = world.ui.get_ui_widget_state_mut(entity) {
widget_data.selected_tab = new_selected;
widget_data.changed = changed;
}
if changed {
world.resources.retained_ui.frame_events.push(
crate::ecs::ui::resources::UiEvent::TabChanged {
entity,
tab_index: new_selected,
},
);
}
}
pub(super) fn handle_dropdown(
world: &mut World,
entity: freecs::Entity,
interaction: &InteractionSnapshot,
data: &crate::ecs::ui::components::UiDropdownData,
frame_keys: &[(KeyCode, bool)],
focused_entity: Option<freecs::Entity>,
) {
let mut selected_index = data.selected_index;
let mut changed = false;
let mut open = data.open;
let mut hovered_index = data.hovered_index;
let is_theme_dropdown = data.is_theme_dropdown;
let was_open = data.open;
let option_count = data.options.len();
let searchable = data.searchable;
let filter_input_entity = data.filter_input_entity;
let mut filtered_indices = data.filtered_indices.clone();
let mut filter_text = data.filter_text.clone();
if interaction.clicked {
open = !open;
}
if !open && focused_entity == Some(entity) {
for &(key, pressed) in frame_keys {
if pressed
&& (key == KeyCode::ArrowDown || key == KeyCode::Space || key == KeyCode::Enter)
{
open = true;
hovered_index = Some(selected_index);
break;
}
}
}
if open && !was_open && searchable {
if let Some(filter_entity) = filter_input_entity {
if let Some(UiWidgetState::TextInput(input_data)) =
world.ui.get_ui_widget_state_mut(filter_entity)
{
input_data.text.clear();
input_data.cursor_position = 0;
input_data.selection_start = None;
input_data.changed = false;
}
if let Some(UiWidgetState::TextInput(input_data)) =
world.ui.get_ui_widget_state(filter_entity)
{
world
.resources
.text_cache
.set_text(input_data.text_slot, "");
}
world.resources.retained_ui.focused_entity = Some(filter_entity);
}
filter_text.clear();
filtered_indices = (0..option_count).collect();
}
if open
&& searchable
&& let Some(filter_entity) = filter_input_entity
{
let current_filter = world
.ui
.get_ui_widget_state(filter_entity)
.and_then(|ws| {
if let UiWidgetState::TextInput(input_data) = ws {
Some(input_data.text.clone())
} else {
None
}
})
.unwrap_or_default();
if current_filter != filter_text {
filter_text = current_filter;
let lower_filter = filter_text.to_lowercase();
filtered_indices = (0..option_count)
.filter(|idx| {
lower_filter.is_empty()
|| data.options[*idx].to_lowercase().contains(&lower_filter)
})
.collect();
hovered_index = None;
for (index, popup_entity) in data.popup_entities.iter().enumerate() {
let visible = filtered_indices.contains(&index);
if let Some(node) = world.ui.get_ui_layout_node_mut(*popup_entity) {
node.visible = visible;
}
}
}
}
if open {
let visible_count = if searchable {
filtered_indices.len()
} else {
option_count
};
for &(key, pressed) in frame_keys {
if !pressed {
continue;
}
match key {
KeyCode::ArrowDown if visible_count > 0 => {
let current_visible_pos = hovered_index
.and_then(|hi| {
if searchable {
filtered_indices.iter().position(|&idx| idx == hi)
} else {
Some(hi)
}
})
.unwrap_or(0);
let next_pos = if current_visible_pos + 1 >= visible_count {
0
} else {
current_visible_pos + 1
};
hovered_index = Some(if searchable {
filtered_indices[next_pos]
} else {
next_pos
});
}
KeyCode::ArrowUp if visible_count > 0 => {
let current_visible_pos = hovered_index
.and_then(|hi| {
if searchable {
filtered_indices.iter().position(|&idx| idx == hi)
} else {
Some(hi)
}
})
.unwrap_or(0);
let prev_pos = if current_visible_pos == 0 {
visible_count.saturating_sub(1)
} else {
current_visible_pos - 1
};
hovered_index = Some(if searchable {
filtered_indices[prev_pos]
} else {
prev_pos
});
}
KeyCode::Enter => {
if let Some(idx) = hovered_index {
selected_index = idx;
changed = true;
open = false;
let selected_text = data.options[idx].clone();
world
.resources
.text_cache
.set_text(data.header_text_slot, selected_text);
}
}
KeyCode::Escape => {
open = false;
}
_ => {}
}
}
for (index, popup_entity) in data.popup_entities.iter().enumerate() {
if searchable && !filtered_indices.contains(&index) {
continue;
}
let popup_interaction = world.ui.get_ui_node_interaction(*popup_entity);
let popup_clicked = popup_interaction.map(|i| i.clicked).unwrap_or(false);
let popup_hovered = popup_interaction.map(|i| i.hovered).unwrap_or(false);
if popup_hovered {
hovered_index = Some(index);
}
if popup_clicked {
selected_index = index;
changed = true;
open = false;
let selected_text = data.options[index].clone();
world
.resources
.text_cache
.set_text(data.header_text_slot, selected_text);
break;
}
}
for (index, popup_entity) in data.popup_entities.iter().enumerate() {
let is_highlighted = hovered_index == Some(index);
if let Some(weights) = world.ui.get_ui_state_weights_mut(*popup_entity) {
weights.weights[crate::ecs::ui::state::UiHover::INDEX] =
if is_highlighted { 1.0 } else { 0.0 };
}
}
let mouse_just_pressed = world
.resources
.input
.mouse
.state
.contains(crate::ecs::input::resources::MouseState::LEFT_JUST_PRESSED);
let filter_hovered = filter_input_entity
.and_then(|fe| world.ui.get_ui_node_interaction(fe))
.map(|i| i.hovered)
.unwrap_or(false);
if mouse_just_pressed && !interaction.hovered && hovered_index.is_none() && !filter_hovered
{
open = false;
}
}
if searchable && was_open && !open {
if let Some(filter_entity) = filter_input_entity
&& world.resources.retained_ui.focused_entity == Some(filter_entity)
{
world.resources.retained_ui.focused_entity = None;
}
for popup_entity in &data.popup_entities {
if let Some(node) = world.ui.get_ui_layout_node_mut(*popup_entity) {
node.visible = true;
}
}
}
if is_theme_dropdown {
if open && hovered_index != data.hovered_index {
world
.resources
.retained_ui
.theme_state
.set_preview(hovered_index);
}
if was_open && !open {
world.resources.retained_ui.theme_state.clear_preview();
}
if changed {
world
.resources
.retained_ui
.theme_state
.select_theme(selected_index);
}
}
let popup_visible_changed = world
.ui
.get_ui_layout_node(data.popup_container_entity)
.is_some_and(|node| node.visible != open);
if popup_visible_changed {
world.resources.retained_ui.layout_dirty = true;
}
if let Some(node) = world.ui.get_ui_layout_node_mut(data.popup_container_entity) {
node.visible = open;
}
if let Some(UiWidgetState::Dropdown(widget_data)) = world.ui.get_ui_widget_state_mut(entity) {
widget_data.selected_index = selected_index;
widget_data.changed = changed;
widget_data.open = open;
widget_data.hovered_index = hovered_index;
widget_data.filter_text = filter_text;
widget_data.filtered_indices = filtered_indices;
}
if changed {
world.resources.retained_ui.frame_events.push(
crate::ecs::ui::resources::UiEvent::DropdownChanged {
entity,
selected_index,
},
);
}
}
pub(super) fn handle_multi_select(
world: &mut World,
entity: freecs::Entity,
interaction: &InteractionSnapshot,
data: &crate::ecs::ui::components::UiMultiSelectData,
) {
let mut selected_indices = data.selected_indices.clone();
let mut changed = false;
let mut open = data.open;
let mut hovered_index = data.hovered_index;
let popup_container_entity = data.popup_container_entity;
let popup_entities = data.popup_entities.clone();
let check_entities = data.check_entities.clone();
if interaction.clicked {
open = !open;
}
if open {
for (index, &popup_entity) in popup_entities.iter().enumerate() {
if let Some(popup_interaction) = world.ui.get_ui_node_interaction(popup_entity) {
if popup_interaction.clicked {
if selected_indices.contains(&index) {
selected_indices.remove(&index);
} else {
selected_indices.insert(index);
}
changed = true;
}
if popup_interaction.hovered {
hovered_index = Some(index);
}
}
}
let mouse_state = world.resources.input.mouse.state;
let mouse_just_pressed =
mouse_state.contains(crate::ecs::input::MouseState::LEFT_JUST_PRESSED);
if mouse_just_pressed
&& !interaction.hovered
&& !world
.ui
.get_ui_node_interaction(popup_container_entity)
.is_some_and(|i| i.hovered)
{
open = false;
}
}
let header_text = if selected_indices.is_empty() {
"None selected".to_string()
} else if selected_indices.len() == 1 {
let idx = *selected_indices.iter().next().unwrap();
data.options[idx].clone()
} else {
format!("{} selected", selected_indices.len())
};
world
.resources
.text_cache
.set_text(data.header_text_slot, &header_text);
for (index, &check_entity) in check_entities.iter().enumerate() {
let check_text = if selected_indices.contains(&index) {
"\u{2713}"
} else {
" "
};
if let Some(crate::ecs::ui::components::UiNodeContent::Text { text_slot, .. }) =
world.ui.get_ui_node_content(check_entity)
{
let slot = *text_slot;
world.resources.text_cache.set_text(slot, check_text);
}
}
let ms_popup_changed = world
.ui
.get_ui_layout_node(popup_container_entity)
.is_some_and(|node| node.visible != open);
if ms_popup_changed {
world.resources.retained_ui.layout_dirty = true;
}
if let Some(node) = world.ui.get_ui_layout_node_mut(popup_container_entity) {
node.visible = open;
}
if let Some(UiWidgetState::MultiSelect(widget_data)) = world.ui.get_ui_widget_state_mut(entity)
{
widget_data.selected_indices = selected_indices.clone();
widget_data.changed = changed;
widget_data.open = open;
widget_data.hovered_index = hovered_index;
}
if changed {
let selected_vec: Vec<usize> = selected_indices.into_iter().collect();
world.resources.retained_ui.frame_events.push(
crate::ecs::ui::resources::UiEvent::MultiSelectChanged {
entity,
selected_indices: selected_vec,
},
);
}
}
pub(super) fn handle_date_picker(
world: &mut World,
entity: freecs::Entity,
interaction: &InteractionSnapshot,
data: &crate::ecs::ui::components::UiDatePickerData,
) {
let mut year = data.year;
let mut month = data.month;
let mut day = data.day;
let mut changed = false;
let mut open = data.open;
let popup_entity = data.popup_entity;
let day_entities = data.day_entities.clone();
let day_text_slots = data.day_text_slots.clone();
let prev_month_entity = data.prev_month_entity;
let next_month_entity = data.next_month_entity;
let month_label_slot = data.month_label_slot;
let header_text_slot = data.header_text_slot;
if interaction.clicked {
open = !open;
}
let mut repopulate = false;
if open {
if let Some(prev_interaction) = world.ui.get_ui_node_interaction(prev_month_entity)
&& prev_interaction.clicked
{
if month == 1 {
month = 12;
year -= 1;
} else {
month -= 1;
}
repopulate = true;
}
if let Some(next_interaction) = world.ui.get_ui_node_interaction(next_month_entity)
&& next_interaction.clicked
{
if month == 12 {
month = 1;
year += 1;
} else {
month += 1;
}
repopulate = true;
}
let first_dow = crate::ecs::ui::widgets::day_of_week(year, month, 1);
let days = crate::ecs::ui::widgets::days_in_month(year, month);
for (cell_index, &day_entity) in day_entities.iter().enumerate() {
if let Some(day_interaction) = world.ui.get_ui_node_interaction(day_entity)
&& day_interaction.clicked
{
let cell_u32 = cell_index as u32;
if cell_u32 >= first_dow && cell_u32 < first_dow + days {
let clicked_day = cell_u32 - first_dow + 1;
day = clicked_day;
changed = true;
open = false;
}
}
}
let mouse_state = world.resources.input.mouse.state;
let mouse_just_pressed =
mouse_state.contains(crate::ecs::input::MouseState::LEFT_JUST_PRESSED);
if mouse_just_pressed
&& !interaction.hovered
&& !world
.ui
.get_ui_node_interaction(popup_entity)
.is_some_and(|i| i.hovered)
{
open = false;
}
}
if repopulate {
let accent_color = world
.resources
.retained_ui
.theme_state
.active_theme()
.accent_color;
crate::ecs::ui::widgets::populate_calendar_grid(
world,
year,
month,
day,
&day_text_slots,
&day_entities,
accent_color,
);
world.resources.text_cache.set_text(
month_label_slot,
crate::ecs::ui::widgets::format_month_year(year, month),
);
}
if changed {
let accent_color = world
.resources
.retained_ui
.theme_state
.active_theme()
.accent_color;
crate::ecs::ui::widgets::populate_calendar_grid(
world,
year,
month,
day,
&day_text_slots,
&day_entities,
accent_color,
);
world
.resources
.text_cache
.set_text(header_text_slot, format!("{year:04}-{month:02}-{day:02}"));
}
if let Some(node) = world.ui.get_ui_layout_node_mut(popup_entity) {
node.visible = open;
}
if let Some(UiWidgetState::DatePicker(widget_data)) = world.ui.get_ui_widget_state_mut(entity) {
widget_data.year = year;
widget_data.month = month;
widget_data.day = day;
widget_data.changed = changed;
widget_data.open = open;
}
if changed {
world.resources.retained_ui.frame_events.push(
crate::ecs::ui::resources::UiEvent::DatePickerChanged {
entity,
year,
month,
day,
},
);
}
}
pub(super) fn handle_menu(
world: &mut World,
entity: freecs::Entity,
interaction: &InteractionSnapshot,
data: &crate::ecs::ui::components::UiMenuData,
) {
let mut clicked_item = None;
let mut open = data.open;
if interaction.clicked {
open = !open;
}
if open {
let mut any_popup_hovered = false;
for (index, popup_entity) in data.popup_entities.iter().enumerate() {
let popup_interaction = world.ui.get_ui_node_interaction(*popup_entity);
let popup_clicked = popup_interaction.map(|i| i.clicked).unwrap_or(false);
if popup_interaction.map(|i| i.hovered).unwrap_or(false) {
any_popup_hovered = true;
}
if popup_clicked {
clicked_item = Some(index);
open = false;
break;
}
}
let mouse_just_pressed = world
.resources
.input
.mouse
.state
.contains(crate::ecs::input::resources::MouseState::LEFT_JUST_PRESSED);
if mouse_just_pressed && !interaction.hovered && !any_popup_hovered {
open = false;
}
}
let menu_popup_changed = world
.ui
.get_ui_layout_node(data.popup_container_entity)
.is_some_and(|node| node.visible != open);
if menu_popup_changed {
world.resources.retained_ui.layout_dirty = true;
}
if let Some(node) = world.ui.get_ui_layout_node_mut(data.popup_container_entity) {
node.visible = open;
}
if let Some(UiWidgetState::Menu(widget_data)) = world.ui.get_ui_widget_state_mut(entity) {
widget_data.clicked_item = clicked_item;
widget_data.open = open;
}
if let Some(item_index) = clicked_item {
world
.resources
.retained_ui
.frame_events
.push(crate::ecs::ui::resources::UiEvent::MenuItemClicked { entity, item_index });
}
}