use crate::ecs::{EditorMode, EditorWorld};
use crate::systems::input;
use crate::systems::mode::is_descendant_of;
use nightshade::ecs::input::resources::MouseState;
use nightshade::ecs::transform::queries::{chain_from_root_to_leaf, find_group_root};
use nightshade::prelude::*;
const CLICK_DRAG_THRESHOLD: f32 = 4.0;
pub fn update(editor_world: &mut EditorWorld, world: &mut World) {
let mouse = *nightshade::ecs::input::access::mouse_for_active(world);
let just_pressed = mouse.state.contains(MouseState::LEFT_JUST_PRESSED);
let just_released = mouse.state.contains(MouseState::LEFT_JUST_RELEASED);
let held = mouse.state.contains(MouseState::LEFT_CLICKED);
let in_viewport = input::mouse_in_active_viewport(world);
let ui_block = input::ui_capturing(world);
let hud_block = world.resources.user_interface.hud_wants_pointer;
if just_pressed {
editor_world.resources.picking.press_position = if in_viewport && !ui_block && !hud_block {
Some(mouse.position)
} else {
None
};
editor_world.resources.picking.is_dragging = false;
}
if held
&& let Some(start) = editor_world.resources.picking.press_position
&& nalgebra_glm::distance(&start, &mouse.position) > CLICK_DRAG_THRESHOLD
{
editor_world.resources.picking.is_dragging = true;
}
let mut pending_alt = false;
let mut pending_shift = false;
let mut requested_pick = false;
if just_released
&& let Some(start) = editor_world.resources.picking.press_position.take()
&& !editor_world.resources.picking.is_dragging
&& nalgebra_glm::distance(&start, &mouse.position) <= CLICK_DRAG_THRESHOLD
{
let render_scale = world.resources.graphics.render_scale.clamp(0.25, 4.0);
let pick_pos = world
.resources
.window
.active_viewport_rect
.and_then(|rect| {
let texture_width = ((rect.width * render_scale).round() as u32).max(1);
let texture_height = ((rect.height * render_scale).round() as u32).max(1);
surface_pick_coords(mouse.position, Some(rect), (texture_width, texture_height))
});
if let Some((x, y)) = pick_pos {
world.resources.gpu_picking.request_pick(x, y);
let keyboard = &world.resources.input.keyboard;
pending_alt = keyboard.is_key_pressed(KeyCode::AltLeft)
|| keyboard.is_key_pressed(KeyCode::AltRight);
pending_shift = keyboard.is_key_pressed(KeyCode::ShiftLeft)
|| keyboard.is_key_pressed(KeyCode::ShiftRight);
requested_pick = true;
}
}
if just_released {
editor_world.resources.picking.is_dragging = false;
}
if requested_pick {
editor_world.resources.picking.pending_alt = pending_alt;
editor_world.resources.picking.pending_shift = pending_shift;
}
if let Some(result) = world.resources.gpu_picking.take_result() {
let alt_held = std::mem::take(&mut editor_world.resources.picking.pending_alt);
let shift_held = std::mem::take(&mut editor_world.resources.picking.pending_shift);
let picked = result.entity_id.and_then(|id| {
world
.core
.query_entities(nightshade::ecs::world::RENDER_MESH)
.find(|entity| entity.id == id)
});
let (new_selection, expand_tree) = match picked {
None => {
reset_cycle(editor_world);
(None, false)
}
Some(leaf) => resolve_selection(editor_world, world, leaf, alt_held),
};
if shift_held {
if let Some(entity) = new_selection {
crate::systems::selection::toggle(editor_world, entity);
}
} else {
crate::systems::selection::set_primary(editor_world, new_selection);
}
if expand_tree {
let focus_target = editor_world.resources.ui.selected_entity;
focus_tree_on(editor_world, world, focus_target);
}
}
sync_outline(editor_world, world);
}
pub fn reset_cycle(editor_world: &mut EditorWorld) {
editor_world.resources.picking.last_pick_leaf = None;
editor_world.resources.picking.last_pick_root = None;
editor_world.resources.picking.cycle_depth = 0;
}
fn resolve_selection(
editor_world: &mut EditorWorld,
world: &World,
leaf: Entity,
alt_held: bool,
) -> (Option<Entity>, bool) {
if alt_held {
editor_world.resources.picking.last_pick_leaf = Some(leaf);
editor_world.resources.picking.last_pick_root = Some(leaf);
editor_world.resources.picking.cycle_depth = 0;
return (Some(leaf), true);
}
match editor_world.resources.mode.mode {
EditorMode::Edit { target } => {
reset_cycle(editor_world);
if is_descendant_of(world, leaf, target) {
(Some(leaf), true)
} else {
(None, false)
}
}
EditorMode::Object => {
let root = find_group_root(world, leaf);
let chain = chain_from_root_to_leaf(world, root, leaf);
let last_root = editor_world.resources.picking.last_pick_root;
let last_leaf = editor_world.resources.picking.last_pick_leaf;
let current_selection = editor_world.resources.ui.selected_entity;
let on_same_chain = current_selection.is_some_and(|selected| chain.contains(&selected));
let cycling = last_leaf == Some(leaf) && last_root == Some(root) && on_same_chain;
let depth = if cycling {
let next = editor_world.resources.picking.cycle_depth + 1;
if next >= chain.len() { 0 } else { next }
} else {
0
};
editor_world.resources.picking.last_pick_leaf = Some(leaf);
editor_world.resources.picking.last_pick_root = Some(root);
editor_world.resources.picking.cycle_depth = depth;
(Some(chain[depth]), !cycling)
}
}
}
fn focus_tree_on(editor_world: &mut EditorWorld, world: &mut World, selection: Option<Entity>) {
let Some(entity) = selection else {
return;
};
let tree = &mut editor_world.resources.ui_handles.tree;
let row_index = tree.all_rows.iter().position(|row| row.entity == entity);
let Some(row_index) = row_index else {
return;
};
let target_depth = tree.all_rows[row_index].depth;
if target_depth > 0 {
let mut depth_to_find = target_depth;
for row in tree.all_rows[..row_index].iter().rev() {
if row.depth < depth_to_find && row.has_children {
tree.expanded.insert(row.entity.id);
if depth_to_find == 1 {
break;
}
depth_to_find = row.depth;
}
}
}
tree.last_visible_start = usize::MAX;
let scroll_entity = world
.ui
.get_ui_virtual_list(tree.list)
.map(|d| d.scroll_entity);
if let Some(scroll_entity) = scroll_entity {
let list_height = world
.ui
.get_ui_layout_node(tree.list)
.map(|n| n.computed_rect.height())
.unwrap_or(0.0);
let dpi = world.resources.window.cached_scale_factor.max(1.0);
let logical_height = list_height / dpi;
let mut visible_position = 0usize;
let mut skip_until_depth: Option<usize> = None;
let filter_active = !tree.filter_lower.is_empty();
for (idx, row) in tree.all_rows.iter().enumerate() {
if let Some(depth) = skip_until_depth {
if row.depth > depth {
continue;
} else {
skip_until_depth = None;
}
}
let collapsed =
!tree.cameras_only && row.has_children && !tree.expanded.contains(&row.entity.id);
let matches_filter =
!filter_active || row.label.to_lowercase().contains(&tree.filter_lower);
if matches_filter {
if idx == row_index {
break;
}
visible_position += 1;
}
if collapsed && !filter_active {
skip_until_depth = Some(row.depth);
}
}
let row_top = visible_position as f32 * 22.0;
let row_bottom = row_top + 22.0;
let current_offset = world
.ui
.get_ui_scroll_area(scroll_entity)
.map(|d| d.scroll_offset)
.unwrap_or(0.0);
let mut new_offset = current_offset;
if row_top < current_offset {
new_offset = row_top;
} else if row_bottom > current_offset + logical_height && logical_height > 0.0 {
new_offset = row_bottom - logical_height;
}
if (new_offset - current_offset).abs() > 0.01 {
ui_scroll_area_set_offset(world, scroll_entity, new_offset);
}
}
}
fn sync_outline(editor_world: &EditorWorld, world: &mut World) {
crate::systems::selection::sync_to_engine(editor_world, world);
}