use bevy::input::mouse::{MouseMotion, MouseWheel}; use bevy::prelude::*; use bevy_panorbit_camera::PanOrbitCamera; use crate::db::{DbChannel, DbReadRequest}; use crate::helix::{HelixHierarchy, HelixState, compute_focal_point}; use crate::ingest::{HistoryQueryState, JetstreamEventMarker, LoadedRanges, TimeWindow}; const SCROLL_SENSITIVITY: f32 = 0.5; const ZOOM_INTERPOLATION_FACTOR: f32 = 5.0; const DRAG_SENSITIVITY: f32 = 0.0005; const INERTIA_DECAY_RATE: f32 = 3.0; const VELOCITY_EPSILON: f32 = 0.0001; #[derive(Resource, Debug, Default)] pub struct DragState { pub is_dragging: bool, pub velocity: f32, } #[derive(Resource, Debug)] pub struct ZoomConfig { pub zoom_button: MouseButton, pub drag_sensitivity: f32, } impl Default for ZoomConfig { fn default() -> Self { Self { zoom_button: MouseButton::Other(8), // forward side button (trackball) drag_sensitivity: 0.02, } } } /// Scroll wheel → zoom level. pub fn scroll_zoom_level( mut scroll_events: MessageReader, mut state: ResMut, hierarchy: Res, ) { if hierarchy.levels.is_empty() { return; } let mut delta = 0.0; for event in scroll_events.read() { delta += event.y; } if delta != 0.0 { let delta_level = delta * SCROLL_SENSITIVITY; let new_target = state.target_level - delta_level; let max_level = (hierarchy.levels.len() - 1) as f32; state.target_level = new_target.clamp(0.0, max_level); } } /// Right-drag pans through time with inertia. pub fn drag_pan_time( mut motion_events: MessageReader, mouse_buttons: Res>, mut state: ResMut, mut drag: ResMut, time: Res