//! Click-to-select star systems + selection visuals + info panel refresh. //! //! Picking is done in **screen space**: each star's world position is projected //! to viewport coordinates, and we find the closest one within a pixel //! threshold. This is cheaper and more intuitive than raycasting against //! individual sphere meshes for an unbounded number of tiny stars. use bevy::prelude::*; use bevy::window::PrimaryWindow; use super::StarSystem; use crate::camera::MainCamera; use crate::gameplay::galaxy_creation::params::SelectedStar; use crate::gameplay::galaxy_creation::ui::GalaxyInfoPanel; use crate::ui::util::cursor_over_ui; // ── Tunables ──────────────────────────────────────────────────────────────── /// Cursor-to-star screen distance (in logical pixels) below which a click is /// treated as a hit. The docs demo uses similar ~16px tolerance. const SELECTION_PIXEL_THRESHOLD: f32 = 18.0; /// Target visual scale for the selected star; non-selected stars animate back to 1.0. const SELECTED_SCALE: f32 = 2.2; /// Lerp speed (per second) for the scale animation. Higher = snappier. const SELECTION_LERP_SPEED: f32 = 10.0; // ── Pick on click ─────────────────────────────────────────────────────────── pub fn select_star_on_click( mouse_input: Res>, primary_window: Query<&Window, With>, camera_query: Query<(&Camera, &GlobalTransform), With>, stars: Query<(Entity, &Transform), With>, ui_nodes: Query<(&bevy::ui::ComputedNode, &GlobalTransform)>, mut selected: ResMut, ) { if !mouse_input.just_pressed(MouseButton::Left) { return; } let Ok(window) = primary_window.single() else { return; }; if cursor_over_ui(window, &ui_nodes) { return; } let Some(cursor) = window.cursor_position() else { return; }; let Ok((camera, camera_gt)) = camera_query.single() else { return; }; // Find the closest star (by screen-pixel distance) within threshold. let mut best: Option<(Entity, f32)> = None; for (entity, transform) in &stars { let Ok(viewport) = camera.world_to_viewport(camera_gt, transform.translation) else { continue; }; let dist = viewport.distance(cursor); if dist >= SELECTION_PIXEL_THRESHOLD { continue; } if best.is_none_or(|(_, d)| dist < d) { best = Some((entity, dist)); } } // Always assign so change detection fires even when clearing the selection. let new_selection = best.map(|(entity, _)| entity); if selected.0 != new_selection { selected.0 = new_selection; } } // ── Visual highlight ──────────────────────────────────────────────────────── /// Smoothly lerp every star's scale toward 1.0 (default) or [`SELECTED_SCALE`] /// when it's the currently selected entity. pub fn animate_selected_star( selected: Res, mut stars: Query<(Entity, &mut Transform), With>, time: Res