use crate::ecs::ui::state::UiStateTrait as _;
use nalgebra_glm::{Vec2, Vec4};
use winit::keyboard::KeyCode;
use crate::ecs::world::World;
use super::handle_scroll::{ScrollFrameInputs, apply_scroll_frame};
use super::snapshot_interaction;
pub(super) struct TreeViewContext<'a> {
pub ctrl_held: bool,
pub frame_keys: &'a [(KeyCode, bool)],
pub focused_entity: Option<freecs::Entity>,
pub scroll_delta: Vec2,
pub mouse_position: Vec2,
pub dpi_scale: f32,
}
pub(super) fn handle_tree_view(
world: &mut World,
entity: freecs::Entity,
data: &crate::ecs::ui::components::UiTreeViewData,
ctx: &TreeViewContext<'_>,
) {
let ctrl_held = ctx.ctrl_held;
let frame_keys = ctx.frame_keys;
let focused_entity = ctx.focused_entity;
if let Some(widget_data) = world.ui.get_ui_tree_view_mut(entity) {
widget_data.changed = false;
widget_data.context_menu_node = None;
}
let node_entities = data.node_entities.clone();
let multi_select = data.multi_select;
let tree_entity = entity;
for node_entity in &node_entities {
let Some(tree_node) = world.ui.get_ui_tree_node(*node_entity).cloned() else {
continue;
};
let row_interaction = snapshot_interaction(world, tree_node.row_entity);
let arrow_clicked = row_interaction.clicked
&& world
.ui
.get_ui_layout_node(tree_node.arrow_entity)
.map(|node| {
node.computed_rect
.contains(world.resources.input.mouse.position)
})
.unwrap_or(false);
if row_interaction.clicked && !arrow_clicked {
if !multi_select || !ctrl_held {
let previously_selected = data.selected_nodes.clone();
for prev in &previously_selected {
if *prev != *node_entity
&& let Some(prev_node) = world.ui.get_ui_tree_node(*prev).cloned()
{
if let Some(color) = world.ui.get_ui_node_color_mut(prev_node.row_entity) {
color.colors[crate::ecs::ui::state::UiSelected::INDEX] = None;
}
if let Some(weights) =
world.ui.get_ui_state_weights_mut(prev_node.row_entity)
{
weights.weights[crate::ecs::ui::state::UiSelected::INDEX] = 0.0;
}
if let Some(wd) = world.ui.get_ui_tree_node_mut(*prev) {
wd.selected = false;
}
}
}
}
let new_selected = if ctrl_held && multi_select {
!tree_node.selected
} else {
true
};
let accent = world
.resources
.retained_ui
.theme_state
.active_theme()
.accent_color;
let selected_bg = Vec4::new(accent.x, accent.y, accent.z, 0.3);
if let Some(color) = world.ui.get_ui_node_color_mut(tree_node.row_entity) {
color.colors[crate::ecs::ui::state::UiSelected::INDEX] = if new_selected {
Some(selected_bg)
} else {
None
};
}
if let Some(weights) = world.ui.get_ui_state_weights_mut(tree_node.row_entity) {
weights.weights[crate::ecs::ui::state::UiSelected::INDEX] =
if new_selected { 1.0 } else { 0.0 };
}
if let Some(wd) = world.ui.get_ui_tree_node_mut(*node_entity) {
wd.selected = new_selected;
}
let mut new_selection: Vec<freecs::Entity> = Vec::new();
for ne in &node_entities {
if let Some(nd) = world.ui.get_ui_tree_node(*ne)
&& nd.selected
{
new_selection.push(*ne);
}
}
if let Some(wd) = world.ui.get_ui_tree_view_mut(tree_entity) {
wd.selected_nodes = new_selection;
wd.changed = true;
}
world.resources.retained_ui.events_for_active_mut().push(
crate::ecs::ui::resources::UiEvent::TreeNodeSelected {
tree: tree_entity,
node: *node_entity,
selected: new_selected,
},
);
}
if arrow_clicked || row_interaction.double_clicked {
let new_expanded = !tree_node.expanded;
if new_expanded && tree_node.lazy && !tree_node.lazy_loaded {
world.resources.retained_ui.events_for_active_mut().push(
crate::ecs::ui::resources::UiEvent::TreeNodeExpandRequested {
tree: tree_entity,
node: *node_entity,
user_data: tree_node.user_data,
},
);
} else {
world.resources.text.cache.set_text(
tree_node.arrow_text_slot,
if new_expanded { "\u{25BC}" } else { "\u{25B6}" },
);
if let Some(node) = world
.ui
.get_ui_layout_node_mut(tree_node.children_container)
{
node.visible = new_expanded;
}
if let Some(wd) = world.ui.get_ui_tree_node_mut(*node_entity) {
wd.expanded = new_expanded;
}
world.resources.retained_ui.events_for_active_mut().push(
crate::ecs::ui::resources::UiEvent::TreeNodeToggled {
tree: tree_entity,
node: *node_entity,
expanded: new_expanded,
},
);
}
}
if row_interaction.right_clicked {
let mouse_position = world.resources.input.mouse.position;
if let Some(wd) = world.ui.get_ui_tree_view_mut(tree_entity) {
wd.context_menu_node = Some(*node_entity);
}
world.resources.retained_ui.events_for_active_mut().push(
crate::ecs::ui::resources::UiEvent::TreeNodeContextMenu {
tree: tree_entity,
node: *node_entity,
position: mouse_position,
},
);
}
}
if focused_entity == Some(entity) || focused_entity == Some(data.content_entity) {
let visible_nodes: Vec<freecs::Entity> = node_entities
.iter()
.filter(|ne| {
if let Some(tn) = world.ui.get_ui_tree_node(**ne).cloned() {
let mut current_parent = tn.parent_node;
while let Some(parent_entity) = current_parent {
if let Some(pn) = world.ui.get_ui_tree_node(parent_entity) {
if !pn.expanded {
return false;
}
current_parent = pn.parent_node;
} else {
break;
}
}
true
} else {
false
}
})
.copied()
.collect();
let current_focused = data
.selected_nodes
.first()
.copied()
.or_else(|| visible_nodes.first().copied());
let current_index =
current_focused.and_then(|cf| visible_nodes.iter().position(|n| *n == cf));
for &(key, pressed) in frame_keys {
if !pressed {
continue;
}
let new_focus = match key {
KeyCode::ArrowDown => current_index
.map(|idx| (idx + 1).min(visible_nodes.len().saturating_sub(1)))
.or(Some(0)),
KeyCode::ArrowUp => current_index.map(|idx| idx.saturating_sub(1)).or(Some(0)),
KeyCode::ArrowRight => {
if let Some(focus) = current_focused
&& let Some(tn) = world.ui.get_ui_tree_node(focus).cloned()
&& !tn.expanded
{
world
.resources
.text
.cache
.set_text(tn.arrow_text_slot, "\u{25BC}");
if let Some(node) = world.ui.get_ui_layout_node_mut(tn.children_container) {
node.visible = true;
}
if let Some(wd) = world.ui.get_ui_tree_node_mut(focus) {
wd.expanded = true;
}
world.resources.retained_ui.events_for_active_mut().push(
crate::ecs::ui::resources::UiEvent::TreeNodeToggled {
tree: tree_entity,
node: focus,
expanded: true,
},
);
}
None
}
KeyCode::ArrowLeft => {
if let Some(focus) = current_focused
&& let Some(tn) = world.ui.get_ui_tree_node(focus).cloned()
{
if tn.expanded {
world
.resources
.text
.cache
.set_text(tn.arrow_text_slot, "\u{25B6}");
if let Some(node) =
world.ui.get_ui_layout_node_mut(tn.children_container)
{
node.visible = false;
}
if let Some(wd) = world.ui.get_ui_tree_node_mut(focus) {
wd.expanded = false;
}
world.resources.retained_ui.events_for_active_mut().push(
crate::ecs::ui::resources::UiEvent::TreeNodeToggled {
tree: tree_entity,
node: focus,
expanded: false,
},
);
None
} else if let Some(parent) = tn.parent_node {
visible_nodes.iter().position(|n| *n == parent)
} else {
None
}
} else {
None
}
}
KeyCode::Enter | KeyCode::Space => {
if let Some(focus) = current_focused
&& let Some(tn) = world.ui.get_ui_tree_node(focus).cloned()
{
let new_expanded = !tn.expanded;
world.resources.text.cache.set_text(
tn.arrow_text_slot,
if new_expanded { "\u{25BC}" } else { "\u{25B6}" },
);
if let Some(node) = world.ui.get_ui_layout_node_mut(tn.children_container) {
node.visible = new_expanded;
}
if let Some(wd) = world.ui.get_ui_tree_node_mut(focus) {
wd.expanded = new_expanded;
}
world.resources.retained_ui.events_for_active_mut().push(
crate::ecs::ui::resources::UiEvent::TreeNodeToggled {
tree: tree_entity,
node: focus,
expanded: new_expanded,
},
);
}
None
}
_ => None,
};
if let Some(new_idx) = new_focus
&& new_idx < visible_nodes.len()
{
let new_node = visible_nodes[new_idx];
if let Some(old) = current_focused
&& let Some(old_tn) = world.ui.get_ui_tree_node(old).cloned()
{
if let Some(color) = world.ui.get_ui_node_color_mut(old_tn.row_entity) {
color.colors[crate::ecs::ui::state::UiSelected::INDEX] = None;
}
if let Some(weights) = world.ui.get_ui_state_weights_mut(old_tn.row_entity) {
weights.weights[crate::ecs::ui::state::UiSelected::INDEX] = 0.0;
}
if let Some(wd) = world.ui.get_ui_tree_node_mut(old) {
wd.selected = false;
}
}
let accent = world
.resources
.retained_ui
.theme_state
.active_theme()
.accent_color;
let selected_bg = Vec4::new(accent.x, accent.y, accent.z, 0.3);
if let Some(new_tn) = world.ui.get_ui_tree_node(new_node).cloned() {
if let Some(color) = world.ui.get_ui_node_color_mut(new_tn.row_entity) {
color.colors[crate::ecs::ui::state::UiSelected::INDEX] = Some(selected_bg);
}
if let Some(weights) = world.ui.get_ui_state_weights_mut(new_tn.row_entity) {
weights.weights[crate::ecs::ui::state::UiSelected::INDEX] = 1.0;
}
}
if let Some(wd) = world.ui.get_ui_tree_node_mut(new_node) {
wd.selected = true;
}
if let Some(wd) = world.ui.get_ui_tree_view_mut(tree_entity) {
wd.selected_nodes = vec![new_node];
wd.changed = true;
}
world.resources.retained_ui.events_for_active_mut().push(
crate::ecs::ui::resources::UiEvent::TreeNodeSelected {
tree: tree_entity,
node: new_node,
selected: true,
},
);
}
}
}
let result = apply_scroll_frame(
world,
ScrollFrameInputs {
entity,
content_entity: data.content_entity,
thumb_entity: data.thumb_entity,
track_entity: data.track_entity,
scroll_offset: data.scroll_offset,
thumb_dragging: data.thumb_dragging,
thumb_drag_start_offset: data.thumb_drag_start_offset,
snap_interval: None,
scroll_delta: ctx.scroll_delta,
mouse_position: ctx.mouse_position,
dpi_scale: ctx.dpi_scale,
},
);
if let Some(widget_data) = world.ui.get_ui_tree_view_mut(entity) {
widget_data.scroll_offset = result.scroll_offset;
widget_data.thumb_dragging = result.thumb_dragging;
widget_data.thumb_drag_start_offset = result.thumb_drag_start_offset;
}
}
pub(super) fn handle_modal_dialog(
world: &mut World,
entity: freecs::Entity,
data: &crate::ecs::ui::components::UiModalDialogData,
frame_keys: &[(KeyCode, bool)],
) {
if !world
.ui
.get_ui_layout_node(entity)
.is_some_and(|n| n.visible)
{
if let Some(widget_data) = world.ui.get_ui_modal_dialog_mut(entity) {
widget_data.result = None;
}
return;
}
let mut result = None;
if let Some(ok) = data.ok_button
&& world
.ui
.get_ui_node_interaction(ok)
.is_some_and(|i| i.clicked)
{
result = Some(true);
}
if let Some(cancel) = data.cancel_button
&& world
.ui
.get_ui_node_interaction(cancel)
.is_some_and(|i| i.clicked)
{
result = Some(false);
}
if world
.ui
.get_ui_node_interaction(data.backdrop_entity)
.is_some_and(|i| i.clicked)
{
result = Some(false);
}
for (key, is_pressed) in frame_keys {
if *is_pressed && *key == KeyCode::Escape {
result = Some(false);
}
}
if let Some(confirmed) = result {
if let Some(node) = world.ui.get_ui_layout_node_mut(data.backdrop_entity) {
node.visible = false;
}
if let Some(node) = world.ui.get_ui_layout_node_mut(entity) {
node.visible = false;
}
if let Some(widget_data) = world.ui.get_ui_modal_dialog_mut(entity) {
widget_data.result = Some(confirmed);
}
world.resources.retained_ui.overlays.active_modal = None;
world
.resources
.retained_ui
.frame
.events
.push(crate::ecs::ui::resources::UiEvent::ModalClosed { entity, confirmed });
}
}