tessera_ui/
cursor.rs

1//! Cursor state management and event handling system.
2//!
3//! This module provides comprehensive cursor and touch event handling for the Tessera UI framework.
4//! It manages cursor position tracking, event queuing, touch gesture recognition, and inertial
5//! scrolling for smooth user interactions.
6//!
7//! # Key Features
8//!
9//! - **Multi-touch Support**: Tracks multiple simultaneous touch points with unique IDs
10//! - **Inertial Scrolling**: Provides smooth momentum-based scrolling after touch gestures
11//! - **Event Queuing**: Maintains a bounded queue of cursor events for processing
12//! - **Velocity Tracking**: Calculates touch velocities for natural gesture recognition
13//! - **Cross-platform**: Handles both mouse and touch input events consistently
14//!
15//! # Usage
16//!
17//! The main entry point is [`CursorState`], which maintains all cursor-related state:
18//!
19//! ```rust,ignore
20//! use tessera_ui::cursor::CursorState;
21//! use tessera_ui::PxPosition;
22//!
23//! let mut cursor_state = CursorState::default();
24//!
25//! // Handle touch start
26//! cursor_state.handle_touch_start(0, PxPosition::new(100.0, 200.0));
27//!
28//! // Process events
29//! let events = cursor_state.take_events();
30//! for event in events {
31//!     match event.content {
32//!         CursorEventContent::Pressed(_) => println!("Touch started"),
33//!         CursorEventContent::Scroll(scroll) => {
34//!             println!("Scroll: dx={}, dy={}", scroll.delta_x, scroll.delta_y);
35//!         }
36//!         _ => {}
37//!     }
38//! }
39//! ```
40
41use std::{
42    collections::{HashMap, VecDeque},
43    time::Instant,
44};
45
46use crate::PxPosition;
47
48/// Maximum number of events to keep in the queue to prevent memory issues during UI jank.
49const KEEP_EVENTS_COUNT: usize = 10;
50
51/// Controls how quickly inertial scrolling decelerates (higher = faster slowdown).
52const INERTIA_DECAY_CONSTANT: f32 = 5.0;
53
54/// Minimum velocity threshold below which inertial scrolling stops (pixels per second).
55const MIN_INERTIA_VELOCITY: f32 = 10.0;
56
57/// Minimum velocity from a gesture required to start inertial scrolling (pixels per second).
58const INERTIA_MIN_VELOCITY_THRESHOLD_FOR_START: f32 = 50.0;
59
60/// Multiplier applied to initial inertial velocity (typically 1.0 for natural feel).
61const INERTIA_MOMENTUM_FACTOR: f32 = 1.0;
62
63/// Tracks the state of a single touch point for gesture recognition and velocity calculation.
64///
65/// This struct maintains the necessary information to track touch movement, calculate
66/// velocities, and determine when to trigger inertial scrolling.
67///
68/// # Example
69///
70/// ```rust,ignore
71/// let touch_state = TouchPointState {
72///     last_position: PxPosition::new(100.0, 200.0),
73///     last_update_time: Instant::now(),
74///     velocity_history: VecDeque::new(),
75/// };
76/// ```
77#[derive(Debug, Clone)]
78struct TouchPointState {
79    /// The last recorded position of this touch point.
80    last_position: PxPosition,
81    /// Timestamp of the last position update.
82    last_update_time: Instant,
83    /// Rolling history of velocity samples for momentum calculation.
84    ///
85    /// Contains tuples of (timestamp, velocity_x, velocity_y) for the last 100ms
86    /// of touch movement, used to calculate average velocity for inertial scrolling.
87    velocity_history: VecDeque<(Instant, f32, f32)>,
88}
89
90/// Represents an active inertial scrolling session.
91///
92/// When a touch gesture ends with sufficient velocity, this struct tracks
93/// the momentum and gradually decelerates the scroll movement over time.
94///
95/// # Example
96///
97/// ```rust,ignore
98/// let inertia = ActiveInertia {
99///     velocity_x: 200.0,  // pixels per second
100///     velocity_y: -150.0, // pixels per second  
101///     last_tick_time: Instant::now(),
102/// };
103/// ```
104#[derive(Debug, Clone)]
105struct ActiveInertia {
106    /// Current horizontal velocity in pixels per second.
107    velocity_x: f32,
108    /// Current vertical velocity in pixels per second.
109    velocity_y: f32,
110    /// Timestamp of the last inertia calculation update.
111    last_tick_time: Instant,
112}
113
114/// Configuration settings for touch scrolling behavior.
115///
116/// This struct controls various aspects of how touch gestures are interpreted
117/// and converted into scroll events.
118///
119/// # Example
120///
121/// ```rust,ignore
122/// let config = TouchScrollConfig {
123///     min_move_threshold: 3.0,  // More sensitive
124///     enabled: true,
125/// };
126/// ```
127#[derive(Debug, Clone)]
128struct TouchScrollConfig {
129    /// Minimum movement distance in pixels required to trigger a scroll event.
130    ///
131    /// Smaller values make scrolling more sensitive but may cause jitter.
132    /// Larger values require more deliberate movement but provide stability.
133    min_move_threshold: f32,
134    /// Whether touch scrolling is currently enabled.
135    enabled: bool,
136}
137
138impl Default for TouchScrollConfig {
139    fn default() -> Self {
140        Self {
141            // Reduced threshold for more responsive touch
142            min_move_threshold: 5.0,
143            enabled: true,
144        }
145    }
146}
147
148/// Central state manager for cursor and touch interactions.
149///
150/// `CursorState` is the main interface for handling all cursor-related events in the Tessera
151/// UI framework. It manages cursor position tracking, event queuing, multi-touch support,
152/// and provides smooth inertial scrolling for touch gestures.
153///
154/// # Key Responsibilities
155///
156/// - **Position Tracking**: Maintains current cursor/touch position
157/// - **Event Management**: Queues and processes cursor events with bounded storage
158/// - **Multi-touch Support**: Tracks multiple simultaneous touch points
159/// - **Inertial Scrolling**: Provides momentum-based scrolling after touch gestures
160/// - **Cross-platform Input**: Handles both mouse and touch events uniformly
161///
162/// # Usage
163///
164/// ```rust,ignore
165/// use tessera_ui::cursor::{CursorState, CursorEventContent};
166/// use tessera_ui::PxPosition;
167///
168/// let mut cursor_state = CursorState::default();
169///
170/// // Handle a touch gesture
171/// cursor_state.handle_touch_start(0, PxPosition::new(100.0, 200.0));
172/// cursor_state.handle_touch_move(0, PxPosition::new(110.0, 190.0));
173/// cursor_state.handle_touch_end(0);
174///
175/// // Process accumulated events
176/// let events = cursor_state.take_events();
177/// for event in events {
178///     match event.content {
179///         CursorEventContent::Pressed(_) => println!("Touch started"),
180///         CursorEventContent::Scroll(scroll) => {
181///             println!("Scrolling: dx={}, dy={}", scroll.delta_x, scroll.delta_y);
182///         }
183///         CursorEventContent::Released(_) => println!("Touch ended"),
184///     }
185/// }
186/// ```
187///
188/// # Thread Safety
189///
190/// `CursorState` is not thread-safe and should be used from a single thread,
191/// typically the main UI thread where input events are processed.
192#[derive(Default)]
193pub struct CursorState {
194    /// Current cursor position, if any cursor is active.
195    position: Option<PxPosition>,
196    /// Bounded queue of cursor events awaiting processing.
197    events: VecDeque<CursorEvent>,
198    /// Active touch points mapped by their unique touch IDs.
199    touch_points: HashMap<u64, TouchPointState>,
200    /// Configuration settings for touch scrolling behavior.
201    touch_scroll_config: TouchScrollConfig,
202    /// Current inertial scrolling state, if active.
203    active_inertia: Option<ActiveInertia>,
204    /// If true, the cursor position will be cleared on the next frame.
205    clear_position_on_next_frame: bool,
206}
207
208impl CursorState {
209    /// Cleans up the cursor state at the end of a frame.
210    pub(crate) fn frame_cleanup(&mut self) {
211        if self.clear_position_on_next_frame {
212            self.update_position(None);
213            self.clear_position_on_next_frame = false;
214        }
215    }
216
217    /// Adds a cursor event to the processing queue.
218    ///
219    /// Events are stored in a bounded queue to prevent memory issues during UI performance
220    /// problems. If the queue exceeds [`KEEP_EVENTS_COUNT`], the oldest events are discarded.
221    ///
222    /// # Arguments
223    ///
224    /// * `event` - The cursor event to add to the queue
225    ///
226    /// # Example
227    ///
228    /// ```rust,ignore
229    /// use tessera_ui::cursor::{CursorState, CursorEvent, CursorEventContent, PressKeyEventType};
230    /// use std::time::Instant;
231    ///
232    /// let mut cursor_state = CursorState::default();
233    /// let event = CursorEvent {
234    ///     timestamp: Instant::now(),
235    ///     content: CursorEventContent::Pressed(PressKeyEventType::Left),
236    /// };
237    /// cursor_state.push_event(event);
238    /// ```
239    pub fn push_event(&mut self, event: CursorEvent) {
240        self.events.push_back(event);
241
242        // Maintain bounded queue size to prevent memory issues during UI jank
243        if self.events.len() > KEEP_EVENTS_COUNT {
244            self.events.pop_front();
245        }
246    }
247
248    /// Updates the current cursor position.
249    ///
250    /// This method accepts any type that can be converted into `Option<PxPosition>`,
251    /// allowing for flexible position updates including clearing the position by
252    /// passing `None`.
253    ///
254    /// # Arguments
255    ///
256    /// * `position` - New cursor position or `None` to clear the position
257    ///
258    /// # Example
259    ///
260    /// ```rust,ignore
261    /// use tessera_ui::cursor::CursorState;
262    /// use tessera_ui::PxPosition;
263    ///
264    /// let mut cursor_state = CursorState::default();
265    ///
266    /// // Set position
267    /// cursor_state.update_position(PxPosition::new(100.0, 200.0));
268    ///
269    /// // Clear position
270    /// cursor_state.update_position(None);
271    /// ```
272    pub fn update_position(&mut self, position: impl Into<Option<PxPosition>>) {
273        self.position = position.into();
274    }
275
276    /// Processes active inertial scrolling and generates scroll events.
277    ///
278    /// This method is called internally to update inertial scrolling state and generate
279    /// appropriate scroll events. It handles velocity decay over time and stops inertia
280    /// when velocity falls below the minimum threshold.
281    ///
282    /// The method calculates scroll deltas based on current velocity and elapsed time,
283    /// applies exponential decay to the velocity, and queues scroll events for processing.
284    ///
285    /// # Implementation Details
286    ///
287    /// - Uses exponential decay with [`INERTIA_DECAY_CONSTANT`] for natural deceleration
288    /// - Stops inertia when velocity drops below [`MIN_INERTIA_VELOCITY`]
289    /// - Generates scroll events with calculated position deltas
290    /// - Handles edge cases like zero delta time gracefully
291    fn process_and_queue_inertial_scroll(&mut self) {
292        // Handle active inertia with clear, small responsibilities.
293        if let Some(mut inertia) = self.active_inertia.take() {
294            let now = Instant::now();
295            let delta_time = now.duration_since(inertia.last_tick_time).as_secs_f32();
296
297            if delta_time <= 0.0 {
298                // Called multiple times in the same instant; reinsert for next frame.
299                self.active_inertia = Some(inertia);
300                return;
301            }
302
303            // Compute scroll delta and emit event if meaningful.
304            let scroll_delta_x = inertia.velocity_x * delta_time;
305            let scroll_delta_y = inertia.velocity_y * delta_time;
306            if scroll_delta_x.abs() > 0.01 || scroll_delta_y.abs() > 0.01 {
307                self.push_scroll_event(now, scroll_delta_x, scroll_delta_y);
308            }
309
310            // Apply exponential decay to velocities.
311            let decay = (-INERTIA_DECAY_CONSTANT * delta_time).exp();
312            inertia.velocity_x *= decay;
313            inertia.velocity_y *= decay;
314            inertia.last_tick_time = now;
315
316            // Reinsert inertia only if still above threshold.
317            if inertia.velocity_x.abs() >= MIN_INERTIA_VELOCITY
318                || inertia.velocity_y.abs() >= MIN_INERTIA_VELOCITY
319            {
320                self.active_inertia = Some(inertia);
321            }
322        }
323    }
324
325    // Helper: push a scroll event with consistent construction.
326    fn push_scroll_event(&mut self, timestamp: Instant, dx: f32, dy: f32) {
327        self.push_event(CursorEvent {
328            timestamp,
329            content: CursorEventContent::Scroll(ScrollEventConent {
330                delta_x: dx,
331                delta_y: dy,
332            }),
333        });
334    }
335
336    // Helper: record a velocity sample and prune old samples (~100ms window).
337    fn record_velocity_sample(touch_state: &mut TouchPointState, now: Instant, vx: f32, vy: f32) {
338        touch_state.velocity_history.push_back((now, vx, vy));
339        while let Some(&(sample_time, _, _)) = touch_state.velocity_history.front() {
340            if now.duration_since(sample_time).as_millis() > 100 {
341                touch_state.velocity_history.pop_front();
342            } else {
343                break;
344            }
345        }
346    }
347
348    // Helper: compute average velocity from recent samples.
349    fn compute_average_velocity(touch_state: &TouchPointState) -> Option<(f32, f32)> {
350        if touch_state.velocity_history.is_empty() {
351            return None;
352        }
353        let mut sum_x = 0.0f32;
354        let mut sum_y = 0.0f32;
355        let count = touch_state.velocity_history.len() as f32;
356        for &(_, vx, vy) in &touch_state.velocity_history {
357            sum_x += vx;
358            sum_y += vy;
359        }
360        Some((sum_x / count, sum_y / count))
361    }
362
363    /// Retrieves and clears all pending cursor events.
364    ///
365    /// This method processes any active inertial scrolling, then returns all queued
366    /// cursor events and clears the internal event queue. Events are returned in
367    /// chronological order (oldest first).
368    ///
369    /// This is typically called once per frame by the UI framework to process
370    /// all accumulated input events.
371    ///
372    /// # Returns
373    ///
374    /// A vector of [`CursorEvent`]s ordered from oldest to newest.
375    ///
376    /// # Example
377    ///
378    /// ```rust,ignore
379    /// use tessera_ui::cursor::CursorState;
380    ///
381    /// let mut cursor_state = CursorState::default();
382    ///
383    /// // ... handle some input events ...
384    ///
385    /// // Process all events at once
386    /// let events = cursor_state.take_events();
387    /// for event in events {
388    ///     println!("Event at {:?}: {:?}", event.timestamp, event.content);
389    /// }
390    /// ```
391    ///
392    /// # Note
393    ///
394    /// Events are ordered from oldest to newest to ensure proper event processing order.
395    pub fn take_events(&mut self) -> Vec<CursorEvent> {
396        self.process_and_queue_inertial_scroll();
397        self.events.drain(..).collect()
398    }
399
400    /// Clears all cursor state and pending events.
401    ///
402    /// This method resets the cursor state to its initial condition by:
403    /// - Clearing all queued events
404    /// - Removing cursor position information
405    /// - Stopping any active inertial scrolling
406    /// - Clearing all touch point tracking
407    ///
408    /// This is typically used when the UI context changes significantly,
409    /// such as when switching between different UI screens or when input
410    /// focus changes.
411    ///
412    /// # Example
413    ///
414    /// ```rust,ignore
415    /// use tessera_ui::cursor::CursorState;
416    ///
417    /// let mut cursor_state = CursorState::default();
418    ///
419    /// // ... handle various input events ...
420    ///
421    /// // Reset everything when changing UI context
422    /// cursor_state.clear();
423    /// ```
424    pub fn clear(&mut self) {
425        self.events.clear();
426        self.update_position(None);
427        self.active_inertia = None;
428        self.touch_points.clear();
429        self.clear_position_on_next_frame = false;
430    }
431
432    /// Returns the current cursor position, if any.
433    ///
434    /// The position represents the last known location of the cursor or active touch point.
435    /// Returns `None` if no cursor is currently active or if the position has been cleared.
436    ///
437    /// # Returns
438    ///
439    /// - `Some(PxPosition)` if a cursor position is currently tracked
440    /// - `None` if no cursor is active
441    ///
442    /// # Example
443    ///
444    /// ```rust,ignore
445    /// use tessera_ui::cursor::CursorState;
446    /// use tessera_ui::PxPosition;
447    ///
448    /// let mut cursor_state = CursorState::default();
449    ///
450    /// // Initially no position
451    /// assert_eq!(cursor_state.position(), None);
452    ///
453    /// // After setting position
454    /// cursor_state.update_position(PxPosition::new(100.0, 200.0));
455    /// assert_eq!(cursor_state.position(), Some(PxPosition::new(100.0, 200.0)));
456    /// ```
457    pub fn position(&self) -> Option<PxPosition> {
458        self.position
459    }
460
461    /// Handles the start of a touch gesture.
462    ///
463    /// This method registers a new touch point and generates a press event. It also
464    /// stops any active inertial scrolling since a new touch interaction has begun.
465    ///
466    /// # Arguments
467    ///
468    /// * `touch_id` - Unique identifier for this touch point
469    /// * `position` - Initial position of the touch in pixel coordinates
470    ///
471    /// # Example
472    ///
473    /// ```rust,ignore
474    /// use tessera_ui::cursor::CursorState;
475    /// use tessera_ui::PxPosition;
476    ///
477    /// let mut cursor_state = CursorState::default();
478    /// cursor_state.handle_touch_start(0, PxPosition::new(100.0, 200.0));
479    ///
480    /// // This generates a Pressed event and updates the cursor position
481    /// let events = cursor_state.take_events();
482    /// assert_eq!(events.len(), 1);
483    /// ```
484    pub fn handle_touch_start(&mut self, touch_id: u64, position: PxPosition) {
485        self.active_inertia = None; // Stop any existing inertia on new touch
486        let now = Instant::now();
487
488        self.touch_points.insert(
489            touch_id,
490            TouchPointState {
491                last_position: position,
492                last_update_time: now,
493                velocity_history: VecDeque::new(),
494            },
495        );
496        self.update_position(position);
497        let press_event = CursorEvent {
498            timestamp: now,
499            content: CursorEventContent::Pressed(PressKeyEventType::Left),
500        };
501        self.push_event(press_event);
502    }
503
504    /// Handles touch movement and generates scroll events when appropriate.
505    ///
506    /// This method tracks touch movement, calculates velocities for inertial scrolling,
507    /// and generates scroll events when the movement exceeds the minimum threshold.
508    /// It also maintains a velocity history for momentum calculation.
509    ///
510    /// # Arguments
511    ///
512    /// * `touch_id` - Unique identifier for the touch point being moved
513    /// * `current_position` - New position of the touch in pixel coordinates
514    ///
515    /// # Returns
516    ///
517    /// - `Some(CursorEvent)` containing a scroll event if movement exceeds threshold
518    /// - `None` if movement is below threshold or touch scrolling is disabled
519    ///
520    /// # Example
521    ///
522    /// ```rust,ignore
523    /// use tessera_ui::cursor::CursorState;
524    /// use tessera_ui::PxPosition;
525    ///
526    /// let mut cursor_state = CursorState::default();
527    /// cursor_state.handle_touch_start(0, PxPosition::new(100.0, 200.0));
528    ///
529    /// // Move touch point - may generate scroll event
530    /// if let Some(scroll_event) = cursor_state.handle_touch_move(0, PxPosition::new(110.0, 190.0)) {
531    ///     println!("Scroll detected!");
532    /// }
533    /// ```
534    pub fn handle_touch_move(
535        &mut self,
536        touch_id: u64,
537        current_position: PxPosition,
538    ) -> Option<CursorEvent> {
539        let now = Instant::now();
540        self.update_position(current_position);
541
542        if !self.touch_scroll_config.enabled {
543            return None;
544        }
545
546        if let Some(touch_state) = self.touch_points.get_mut(&touch_id) {
547            let delta_x = (current_position.x - touch_state.last_position.x).to_f32();
548            let delta_y = (current_position.y - touch_state.last_position.y).to_f32();
549            let move_distance = (delta_x * delta_x + delta_y * delta_y).sqrt();
550
551            if move_distance >= self.touch_scroll_config.min_move_threshold {
552                // Stop any active inertia when user actively moves the touch.
553                self.active_inertia = None;
554
555                let time_delta = now
556                    .duration_since(touch_state.last_update_time)
557                    .as_secs_f32();
558
559                if time_delta > 0.0 {
560                    let velocity_x = delta_x / time_delta;
561                    let velocity_y = delta_y / time_delta;
562                    // Record and prune velocity samples via helper.
563                    Self::record_velocity_sample(touch_state, now, velocity_x, velocity_y);
564                }
565
566                touch_state.last_position = current_position;
567                touch_state.last_update_time = now;
568
569                // Return a scroll event for immediate feedback.
570                return Some(CursorEvent {
571                    timestamp: now,
572                    content: CursorEventContent::Scroll(ScrollEventConent {
573                        delta_x, // Direct scroll delta for touch move
574                        delta_y,
575                    }),
576                });
577            }
578        }
579        None
580    }
581
582    /// Handles the end of a touch gesture and potentially starts inertial scrolling.
583    ///
584    /// This method processes the end of a touch interaction by:
585    /// - Calculating average velocity from recent touch movement
586    /// - Starting inertial scrolling if velocity exceeds the threshold
587    /// - Generating a release event
588    /// - Cleaning up touch point tracking
589    ///
590    /// # Arguments
591    ///
592    /// * `touch_id` - Unique identifier for the touch point that ended
593    ///
594    /// # Example
595    ///
596    /// ```rust,ignore
597    /// use tessera_ui::cursor::CursorState;
598    /// use tessera_ui::PxPosition;
599    ///
600    /// let mut cursor_state = CursorState::default();
601    /// cursor_state.handle_touch_start(0, PxPosition::new(100.0, 200.0));
602    /// cursor_state.handle_touch_move(0, PxPosition::new(150.0, 180.0));
603    /// cursor_state.handle_touch_end(0);
604    ///
605    /// // May start inertial scrolling based on gesture velocity
606    /// let events = cursor_state.take_events();
607    /// // Events may include scroll events from inertia
608    /// ```
609    pub fn handle_touch_end(&mut self, touch_id: u64) {
610        let now = Instant::now();
611
612        if let Some(touch_state) = self.touch_points.get(&touch_id) {
613            if self.touch_scroll_config.enabled {
614                if let Some((avg_vx, avg_vy)) = Self::compute_average_velocity(touch_state) {
615                    let velocity_magnitude = (avg_vx * avg_vx + avg_vy * avg_vy).sqrt();
616                    if velocity_magnitude > INERTIA_MIN_VELOCITY_THRESHOLD_FOR_START {
617                        self.active_inertia = Some(ActiveInertia {
618                            velocity_x: avg_vx * INERTIA_MOMENTUM_FACTOR,
619                            velocity_y: avg_vy * INERTIA_MOMENTUM_FACTOR,
620                            last_tick_time: now,
621                        });
622                    } else {
623                        self.active_inertia = None;
624                    }
625                } else {
626                    self.active_inertia = None;
627                }
628            } else {
629                self.active_inertia = None; // Scrolling disabled
630            }
631        } else {
632            self.active_inertia = None; // No touch state present
633        }
634
635        self.touch_points.remove(&touch_id);
636        let release_event = CursorEvent {
637            timestamp: now,
638            content: CursorEventContent::Released(PressKeyEventType::Left),
639        };
640        self.push_event(release_event);
641
642        if self.touch_points.is_empty() && self.active_inertia.is_none() {
643            self.clear_position_on_next_frame = true;
644        }
645    }
646}
647
648/// Represents a single cursor or touch event with timing information.
649///
650/// `CursorEvent` encapsulates all types of cursor interactions including presses,
651/// releases, and scroll actions. Each event includes a timestamp for precise
652/// timing and ordering of input events.
653///
654/// # Example
655///
656/// ```rust,ignore
657/// use tessera_ui::cursor::{CursorEvent, CursorEventContent, PressKeyEventType};
658/// use std::time::Instant;
659///
660/// let event = CursorEvent {
661///     timestamp: Instant::now(),
662///     content: CursorEventContent::Pressed(PressKeyEventType::Left),
663/// };
664///
665/// match event.content {
666///     CursorEventContent::Pressed(button) => println!("Button pressed: {:?}", button),
667///     CursorEventContent::Released(button) => println!("Button released: {:?}", button),
668///     CursorEventContent::Scroll(scroll) => {
669///         println!("Scroll: dx={}, dy={}", scroll.delta_x, scroll.delta_y);
670///     }
671/// }
672/// ```
673#[derive(Debug, Clone)]
674pub struct CursorEvent {
675    /// Timestamp indicating when this event occurred.
676    pub timestamp: Instant,
677    /// The specific type and data of this cursor event.
678    pub content: CursorEventContent,
679}
680
681/// Contains scroll movement data for scroll events.
682///
683/// `ScrollEventConent` represents the amount of scrolling that occurred,
684/// with positive values typically indicating rightward/downward movement
685/// and negative values indicating leftward/upward movement.
686///
687/// # Example
688///
689/// ```rust,ignore
690/// use tessera_ui::cursor::ScrollEventConent;
691///
692/// let scroll = ScrollEventConent {
693///     delta_x: 10.0,   // Scroll right 10 pixels
694///     delta_y: -20.0,  // Scroll up 20 pixels
695/// };
696///
697/// println!("Horizontal scroll: {}", scroll.delta_x);
698/// println!("Vertical scroll: {}", scroll.delta_y);
699/// ```
700#[derive(Debug, Clone, PartialEq)]
701pub struct ScrollEventConent {
702    /// Horizontal scroll distance in pixels.
703    ///
704    /// Positive values indicate rightward scrolling,
705    /// negative values indicate leftward scrolling.
706    pub delta_x: f32,
707    /// Vertical scroll distance in pixels.
708    ///
709    /// Positive values indicate downward scrolling,
710    /// negative values indicate upward scrolling.
711    pub delta_y: f32,
712}
713
714/// Enumeration of all possible cursor event types.
715///
716/// `CursorEventContent` represents the different kinds of interactions
717/// that can occur with cursor or touch input, including button presses,
718/// releases, and scroll actions.
719///
720/// # Example
721///
722/// ```rust,ignore
723/// use tessera_ui::cursor::{CursorEventContent, PressKeyEventType, ScrollEventConent};
724///
725/// // Handle different event types
726/// match event_content {
727///     CursorEventContent::Pressed(PressKeyEventType::Left) => {
728///         println!("Left button pressed");
729///     }
730///     CursorEventContent::Released(PressKeyEventType::Right) => {
731///         println!("Right button released");
732///     }
733///     CursorEventContent::Scroll(scroll) => {
734///         println!("Scrolled by ({}, {})", scroll.delta_x, scroll.delta_y);
735///     }
736/// }
737/// ```
738#[derive(Debug, Clone, PartialEq)]
739pub enum CursorEventContent {
740    /// A cursor button or touch point was pressed.
741    Pressed(PressKeyEventType),
742    /// A cursor button or touch point was released.
743    Released(PressKeyEventType),
744    /// A scroll action occurred (mouse wheel, touch drag, or inertial scroll).
745    Scroll(ScrollEventConent),
746}
747
748impl CursorEventContent {
749    /// Creates a cursor press/release event from winit mouse button events.
750    ///
751    /// This method converts winit's mouse button events into Tessera's cursor event format.
752    /// It handles the three standard mouse buttons (left, right, middle) and ignores
753    /// any additional buttons that may be present on some mice.
754    ///
755    /// # Arguments
756    ///
757    /// * `state` - Whether the button was pressed or released
758    /// * `button` - Which mouse button was affected
759    ///
760    /// # Returns
761    ///
762    /// - `Some(CursorEventContent)` for supported mouse buttons
763    /// - `None` for unsupported mouse buttons
764    ///
765    /// # Example
766    ///
767    /// ```rust,ignore
768    /// use tessera_ui::cursor::CursorEventContent;
769    /// use winit::event::{ElementState, MouseButton};
770    ///
771    /// let press_event = CursorEventContent::from_press_event(
772    ///     ElementState::Pressed,
773    ///     MouseButton::Left
774    /// );
775    ///
776    /// if let Some(event) = press_event {
777    ///     println!("Created cursor event: {:?}", event);
778    /// }
779    /// ```
780    pub fn from_press_event(
781        state: winit::event::ElementState,
782        button: winit::event::MouseButton,
783    ) -> Option<Self> {
784        let event_type = match button {
785            winit::event::MouseButton::Left => PressKeyEventType::Left,
786            winit::event::MouseButton::Right => PressKeyEventType::Right,
787            winit::event::MouseButton::Middle => PressKeyEventType::Middle,
788            _ => return None, // Ignore other buttons
789        };
790        let state = match state {
791            winit::event::ElementState::Pressed => Self::Pressed(event_type),
792            winit::event::ElementState::Released => Self::Released(event_type),
793        };
794        Some(state)
795    }
796
797    /// Creates a scroll event from winit mouse wheel events.
798    ///
799    /// This method converts winit's mouse scroll delta into Tessera's scroll event format.
800    /// It handles both line-based scrolling (typical mouse wheels) and pixel-based
801    /// scrolling (trackpads, precision mice) by applying appropriate scaling.
802    ///
803    /// # Arguments
804    ///
805    /// * `delta` - The scroll delta from winit
806    ///
807    /// # Returns
808    ///
809    /// A `CursorEventContent::Scroll` event with scaled delta values.
810    ///
811    /// # Example
812    ///
813    /// ```rust,ignore
814    /// use tessera_ui::cursor::CursorEventContent;
815    /// use winit::event::MouseScrollDelta;
816    ///
817    /// let scroll_event = CursorEventContent::from_scroll_event(
818    ///     MouseScrollDelta::LineDelta(0.0, 1.0)  // Scroll down one line
819    /// );
820    ///
821    /// match scroll_event {
822    ///     CursorEventContent::Scroll(scroll) => {
823    ///         println!("Scroll delta: ({}, {})", scroll.delta_x, scroll.delta_y);
824    ///     }
825    ///     _ => {}
826    /// }
827    /// ```
828    pub fn from_scroll_event(delta: winit::event::MouseScrollDelta) -> Self {
829        let (delta_x, delta_y) = match delta {
830            winit::event::MouseScrollDelta::LineDelta(x, y) => (x, y),
831            winit::event::MouseScrollDelta::PixelDelta(delta) => (delta.x as f32, delta.y as f32),
832        };
833
834        const MOUSE_WHEEL_SPEED_MULTIPLIER: f32 = 50.0;
835        Self::Scroll(ScrollEventConent {
836            delta_x: delta_x * MOUSE_WHEEL_SPEED_MULTIPLIER,
837            delta_y: delta_y * MOUSE_WHEEL_SPEED_MULTIPLIER,
838        })
839    }
840}
841
842/// Represents the different types of cursor buttons or touch interactions.
843///
844/// `PressKeyEventType` identifies which button was pressed or released in
845/// a cursor event. This covers the three standard mouse buttons that are
846/// commonly supported across different platforms and input devices.
847///
848/// # Example
849///
850/// ```rust,ignore
851/// use tessera_ui::cursor::PressKeyEventType;
852///
853/// match button_type {
854///     PressKeyEventType::Left => println!("Primary button (usually left-click)"),
855///     PressKeyEventType::Right => println!("Secondary button (usually right-click)"),
856///     PressKeyEventType::Middle => println!("Middle button (usually scroll wheel click)"),
857/// }
858/// ```
859#[derive(Debug, Clone, PartialEq, Eq)]
860pub enum PressKeyEventType {
861    /// The primary mouse button (typically left button) or primary touch.
862    Left,
863    /// The secondary mouse button (typically right button).
864    Right,
865    /// The middle mouse button (typically scroll wheel click).
866    Middle,
867}