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}
205
206impl CursorState {
207    /// Adds a cursor event to the processing queue.
208    ///
209    /// Events are stored in a bounded queue to prevent memory issues during UI performance
210    /// problems. If the queue exceeds [`KEEP_EVENTS_COUNT`], the oldest events are discarded.
211    ///
212    /// # Arguments
213    ///
214    /// * `event` - The cursor event to add to the queue
215    ///
216    /// # Example
217    ///
218    /// ```rust,ignore
219    /// use tessera_ui::cursor::{CursorState, CursorEvent, CursorEventContent, PressKeyEventType};
220    /// use std::time::Instant;
221    ///
222    /// let mut cursor_state = CursorState::default();
223    /// let event = CursorEvent {
224    ///     timestamp: Instant::now(),
225    ///     content: CursorEventContent::Pressed(PressKeyEventType::Left),
226    /// };
227    /// cursor_state.push_event(event);
228    /// ```
229    pub fn push_event(&mut self, event: CursorEvent) {
230        self.events.push_back(event);
231
232        // Maintain bounded queue size to prevent memory issues during UI jank
233        if self.events.len() > KEEP_EVENTS_COUNT {
234            self.events.pop_front();
235        }
236    }
237
238    /// Updates the current cursor position.
239    ///
240    /// This method accepts any type that can be converted into `Option<PxPosition>`,
241    /// allowing for flexible position updates including clearing the position by
242    /// passing `None`.
243    ///
244    /// # Arguments
245    ///
246    /// * `position` - New cursor position or `None` to clear the position
247    ///
248    /// # Example
249    ///
250    /// ```rust,ignore
251    /// use tessera_ui::cursor::CursorState;
252    /// use tessera_ui::PxPosition;
253    ///
254    /// let mut cursor_state = CursorState::default();
255    ///
256    /// // Set position
257    /// cursor_state.update_position(PxPosition::new(100.0, 200.0));
258    ///
259    /// // Clear position
260    /// cursor_state.update_position(None);
261    /// ```
262    pub fn update_position(&mut self, position: impl Into<Option<PxPosition>>) {
263        self.position = position.into();
264    }
265
266    /// Processes active inertial scrolling and generates scroll events.
267    ///
268    /// This method is called internally to update inertial scrolling state and generate
269    /// appropriate scroll events. It handles velocity decay over time and stops inertia
270    /// when velocity falls below the minimum threshold.
271    ///
272    /// The method calculates scroll deltas based on current velocity and elapsed time,
273    /// applies exponential decay to the velocity, and queues scroll events for processing.
274    ///
275    /// # Implementation Details
276    ///
277    /// - Uses exponential decay with [`INERTIA_DECAY_CONSTANT`] for natural deceleration
278    /// - Stops inertia when velocity drops below [`MIN_INERTIA_VELOCITY`]
279    /// - Generates scroll events with calculated position deltas
280    /// - Handles edge cases like zero delta time gracefully
281    fn process_and_queue_inertial_scroll(&mut self) {
282        if let Some(mut inertia_data) = self.active_inertia.take() {
283            // Take ownership
284            let now = Instant::now();
285            let delta_time = now
286                .duration_since(inertia_data.last_tick_time)
287                .as_secs_f32();
288
289            let mut should_reinsert_inertia = true;
290
291            if delta_time > 0.0 {
292                let scroll_delta_x = inertia_data.velocity_x * delta_time;
293                let scroll_delta_y = inertia_data.velocity_y * delta_time;
294
295                if scroll_delta_x.abs() > 0.01 || scroll_delta_y.abs() > 0.01 {
296                    self.push_event(CursorEvent {
297                        timestamp: now,
298                        content: CursorEventContent::Scroll(ScrollEventConent {
299                            delta_x: scroll_delta_x,
300                            delta_y: scroll_delta_y,
301                        }),
302                    });
303                }
304
305                let decay_multiplier = (-INERTIA_DECAY_CONSTANT * delta_time).exp();
306                inertia_data.velocity_x *= decay_multiplier;
307                inertia_data.velocity_y *= decay_multiplier;
308                inertia_data.last_tick_time = now;
309
310                if inertia_data.velocity_x.abs() < MIN_INERTIA_VELOCITY
311                    && inertia_data.velocity_y.abs() < MIN_INERTIA_VELOCITY
312                {
313                    should_reinsert_inertia = false; // Stop inertia
314                }
315            } else {
316                // delta_time is zero or negative, reinsert without modification for next frame.
317                // This can happen if called multiple times in the same instant.
318            }
319
320            if should_reinsert_inertia {
321                self.active_inertia = Some(inertia_data); // Put it back if still active
322            }
323        }
324    }
325
326    /// Retrieves and clears all pending cursor events.
327    ///
328    /// This method processes any active inertial scrolling, then returns all queued
329    /// cursor events and clears the internal event queue. Events are returned in
330    /// chronological order (oldest first).
331    ///
332    /// This is typically called once per frame by the UI framework to process
333    /// all accumulated input events.
334    ///
335    /// # Returns
336    ///
337    /// A vector of [`CursorEvent`]s ordered from oldest to newest.
338    ///
339    /// # Example
340    ///
341    /// ```rust,ignore
342    /// use tessera_ui::cursor::CursorState;
343    ///
344    /// let mut cursor_state = CursorState::default();
345    ///
346    /// // ... handle some input events ...
347    ///
348    /// // Process all events at once
349    /// let events = cursor_state.take_events();
350    /// for event in events {
351    ///     println!("Event at {:?}: {:?}", event.timestamp, event.content);
352    /// }
353    /// ```
354    ///
355    /// # Note
356    ///
357    /// Events are ordered from oldest to newest to ensure proper event processing order.
358    pub fn take_events(&mut self) -> Vec<CursorEvent> {
359        self.process_and_queue_inertial_scroll();
360        self.events.drain(..).collect()
361    }
362
363    /// Clears all cursor state and pending events.
364    ///
365    /// This method resets the cursor state to its initial condition by:
366    /// - Clearing all queued events
367    /// - Removing cursor position information
368    /// - Stopping any active inertial scrolling
369    /// - Clearing all touch point tracking
370    ///
371    /// This is typically used when the UI context changes significantly,
372    /// such as when switching between different UI screens or when input
373    /// focus changes.
374    ///
375    /// # Example
376    ///
377    /// ```rust,ignore
378    /// use tessera_ui::cursor::CursorState;
379    ///
380    /// let mut cursor_state = CursorState::default();
381    ///
382    /// // ... handle various input events ...
383    ///
384    /// // Reset everything when changing UI context
385    /// cursor_state.clear();
386    /// ```
387    pub fn clear(&mut self) {
388        self.events.clear();
389        self.update_position(None);
390        self.active_inertia = None;
391        self.touch_points.clear();
392    }
393
394    /// Returns the current cursor position, if any.
395    ///
396    /// The position represents the last known location of the cursor or active touch point.
397    /// Returns `None` if no cursor is currently active or if the position has been cleared.
398    ///
399    /// # Returns
400    ///
401    /// - `Some(PxPosition)` if a cursor position is currently tracked
402    /// - `None` if no cursor is active
403    ///
404    /// # Example
405    ///
406    /// ```rust,ignore
407    /// use tessera_ui::cursor::CursorState;
408    /// use tessera_ui::PxPosition;
409    ///
410    /// let mut cursor_state = CursorState::default();
411    ///
412    /// // Initially no position
413    /// assert_eq!(cursor_state.position(), None);
414    ///
415    /// // After setting position
416    /// cursor_state.update_position(PxPosition::new(100.0, 200.0));
417    /// assert_eq!(cursor_state.position(), Some(PxPosition::new(100.0, 200.0)));
418    /// ```
419    pub fn position(&self) -> Option<PxPosition> {
420        self.position
421    }
422
423    /// Handles the start of a touch gesture.
424    ///
425    /// This method registers a new touch point and generates a press event. It also
426    /// stops any active inertial scrolling since a new touch interaction has begun.
427    ///
428    /// # Arguments
429    ///
430    /// * `touch_id` - Unique identifier for this touch point
431    /// * `position` - Initial position of the touch in pixel coordinates
432    ///
433    /// # Example
434    ///
435    /// ```rust,ignore
436    /// use tessera_ui::cursor::CursorState;
437    /// use tessera_ui::PxPosition;
438    ///
439    /// let mut cursor_state = CursorState::default();
440    /// cursor_state.handle_touch_start(0, PxPosition::new(100.0, 200.0));
441    ///
442    /// // This generates a Pressed event and updates the cursor position
443    /// let events = cursor_state.take_events();
444    /// assert_eq!(events.len(), 1);
445    /// ```
446    pub fn handle_touch_start(&mut self, touch_id: u64, position: PxPosition) {
447        self.active_inertia = None; // Stop any existing inertia on new touch
448        let now = Instant::now();
449
450        self.touch_points.insert(
451            touch_id,
452            TouchPointState {
453                last_position: position,
454                last_update_time: now,
455                velocity_history: VecDeque::new(),
456            },
457        );
458        self.update_position(position);
459        let press_event = CursorEvent {
460            timestamp: now,
461            content: CursorEventContent::Pressed(PressKeyEventType::Left),
462        };
463        self.push_event(press_event);
464    }
465
466    /// Handles touch movement and generates scroll events when appropriate.
467    ///
468    /// This method tracks touch movement, calculates velocities for inertial scrolling,
469    /// and generates scroll events when the movement exceeds the minimum threshold.
470    /// It also maintains a velocity history for momentum calculation.
471    ///
472    /// # Arguments
473    ///
474    /// * `touch_id` - Unique identifier for the touch point being moved
475    /// * `current_position` - New position of the touch in pixel coordinates
476    ///
477    /// # Returns
478    ///
479    /// - `Some(CursorEvent)` containing a scroll event if movement exceeds threshold
480    /// - `None` if movement is below threshold or touch scrolling is disabled
481    ///
482    /// # Example
483    ///
484    /// ```rust,ignore
485    /// use tessera_ui::cursor::CursorState;
486    /// use tessera_ui::PxPosition;
487    ///
488    /// let mut cursor_state = CursorState::default();
489    /// cursor_state.handle_touch_start(0, PxPosition::new(100.0, 200.0));
490    ///
491    /// // Move touch point - may generate scroll event
492    /// if let Some(scroll_event) = cursor_state.handle_touch_move(0, PxPosition::new(110.0, 190.0)) {
493    ///     println!("Scroll detected!");
494    /// }
495    /// ```
496    pub fn handle_touch_move(
497        &mut self,
498        touch_id: u64,
499        current_position: PxPosition,
500    ) -> Option<CursorEvent> {
501        let now = Instant::now();
502        self.update_position(current_position);
503
504        if !self.touch_scroll_config.enabled {
505            return None;
506        }
507
508        if let Some(touch_state) = self.touch_points.get_mut(&touch_id) {
509            let delta_x = (current_position.x - touch_state.last_position.x).to_f32();
510            let delta_y = (current_position.y - touch_state.last_position.y).to_f32();
511            let move_distance = (delta_x * delta_x + delta_y * delta_y).sqrt();
512
513            if move_distance >= self.touch_scroll_config.min_move_threshold {
514                self.active_inertia = None; // Stop inertia if significant movement occurs
515
516                let time_delta = now
517                    .duration_since(touch_state.last_update_time)
518                    .as_secs_f32();
519                if time_delta > 0.0 {
520                    let velocity_x = delta_x / time_delta;
521                    let velocity_y = delta_y / time_delta;
522                    touch_state
523                        .velocity_history
524                        .push_back((now, velocity_x, velocity_y));
525
526                    // Keep only recent velocity samples (last 100ms) for momentum calculation
527                    while let Some(&(sample_time, _, _)) = touch_state.velocity_history.front() {
528                        if now.duration_since(sample_time).as_millis() > 100 {
529                            touch_state.velocity_history.pop_front();
530                        } else {
531                            break;
532                        }
533                    }
534                }
535
536                touch_state.last_position = current_position;
537                touch_state.last_update_time = now;
538
539                return Some(CursorEvent {
540                    timestamp: now,
541                    content: CursorEventContent::Scroll(ScrollEventConent {
542                        delta_x, // Direct scroll delta for touch move
543                        delta_y,
544                    }),
545                });
546            }
547        }
548        None
549    }
550
551    /// Handles the end of a touch gesture and potentially starts inertial scrolling.
552    ///
553    /// This method processes the end of a touch interaction by:
554    /// - Calculating average velocity from recent touch movement
555    /// - Starting inertial scrolling if velocity exceeds the threshold
556    /// - Generating a release event
557    /// - Cleaning up touch point tracking
558    ///
559    /// # Arguments
560    ///
561    /// * `touch_id` - Unique identifier for the touch point that ended
562    ///
563    /// # Example
564    ///
565    /// ```rust,ignore
566    /// use tessera_ui::cursor::CursorState;
567    /// use tessera_ui::PxPosition;
568    ///
569    /// let mut cursor_state = CursorState::default();
570    /// cursor_state.handle_touch_start(0, PxPosition::new(100.0, 200.0));
571    /// cursor_state.handle_touch_move(0, PxPosition::new(150.0, 180.0));
572    /// cursor_state.handle_touch_end(0);
573    ///
574    /// // May start inertial scrolling based on gesture velocity
575    /// let events = cursor_state.take_events();
576    /// // Events may include scroll events from inertia
577    /// ```
578    pub fn handle_touch_end(&mut self, touch_id: u64) {
579        let now = Instant::now();
580
581        if let Some(touch_state) = self.touch_points.get(&touch_id) {
582            if !touch_state.velocity_history.is_empty() && self.touch_scroll_config.enabled {
583                let mut avg_velocity_x = 0.0;
584                let mut avg_velocity_y = 0.0;
585                let sample_count = touch_state.velocity_history.len();
586
587                for (_, vx, vy) in &touch_state.velocity_history {
588                    avg_velocity_x += vx;
589                    avg_velocity_y += vy;
590                }
591
592                if sample_count > 0 {
593                    avg_velocity_x /= sample_count as f32;
594                    avg_velocity_y /= sample_count as f32;
595                }
596
597                let velocity_magnitude =
598                    (avg_velocity_x * avg_velocity_x + avg_velocity_y * avg_velocity_y).sqrt();
599
600                if velocity_magnitude > INERTIA_MIN_VELOCITY_THRESHOLD_FOR_START {
601                    self.active_inertia = Some(ActiveInertia {
602                        velocity_x: avg_velocity_x * INERTIA_MOMENTUM_FACTOR,
603                        velocity_y: avg_velocity_y * INERTIA_MOMENTUM_FACTOR,
604                        last_tick_time: now,
605                    });
606                } else {
607                    self.active_inertia = None; // Ensure inertia is cleared if not starting
608                }
609            } else {
610                self.active_inertia = None; // Ensure inertia is cleared
611            }
612        } else {
613            self.active_inertia = None; // Ensure inertia is cleared if touch_state is None
614        }
615
616        self.touch_points.remove(&touch_id);
617        let release_event = CursorEvent {
618            timestamp: now,
619            content: CursorEventContent::Released(PressKeyEventType::Left),
620        };
621        self.push_event(release_event);
622
623        if self.touch_points.is_empty() && self.active_inertia.is_none() {
624            self.update_position(None);
625        }
626    }
627}
628
629/// Represents a single cursor or touch event with timing information.
630///
631/// `CursorEvent` encapsulates all types of cursor interactions including presses,
632/// releases, and scroll actions. Each event includes a timestamp for precise
633/// timing and ordering of input events.
634///
635/// # Example
636///
637/// ```rust,ignore
638/// use tessera_ui::cursor::{CursorEvent, CursorEventContent, PressKeyEventType};
639/// use std::time::Instant;
640///
641/// let event = CursorEvent {
642///     timestamp: Instant::now(),
643///     content: CursorEventContent::Pressed(PressKeyEventType::Left),
644/// };
645///
646/// match event.content {
647///     CursorEventContent::Pressed(button) => println!("Button pressed: {:?}", button),
648///     CursorEventContent::Released(button) => println!("Button released: {:?}", button),
649///     CursorEventContent::Scroll(scroll) => {
650///         println!("Scroll: dx={}, dy={}", scroll.delta_x, scroll.delta_y);
651///     }
652/// }
653/// ```
654#[derive(Debug, Clone)]
655pub struct CursorEvent {
656    /// Timestamp indicating when this event occurred.
657    pub timestamp: Instant,
658    /// The specific type and data of this cursor event.
659    pub content: CursorEventContent,
660}
661
662/// Contains scroll movement data for scroll events.
663///
664/// `ScrollEventConent` represents the amount of scrolling that occurred,
665/// with positive values typically indicating rightward/downward movement
666/// and negative values indicating leftward/upward movement.
667///
668/// # Example
669///
670/// ```rust,ignore
671/// use tessera_ui::cursor::ScrollEventConent;
672///
673/// let scroll = ScrollEventConent {
674///     delta_x: 10.0,   // Scroll right 10 pixels
675///     delta_y: -20.0,  // Scroll up 20 pixels
676/// };
677///
678/// println!("Horizontal scroll: {}", scroll.delta_x);
679/// println!("Vertical scroll: {}", scroll.delta_y);
680/// ```
681#[derive(Debug, Clone, PartialEq)]
682pub struct ScrollEventConent {
683    /// Horizontal scroll distance in pixels.
684    ///
685    /// Positive values indicate rightward scrolling,
686    /// negative values indicate leftward scrolling.
687    pub delta_x: f32,
688    /// Vertical scroll distance in pixels.
689    ///
690    /// Positive values indicate downward scrolling,
691    /// negative values indicate upward scrolling.
692    pub delta_y: f32,
693}
694
695/// Enumeration of all possible cursor event types.
696///
697/// `CursorEventContent` represents the different kinds of interactions
698/// that can occur with cursor or touch input, including button presses,
699/// releases, and scroll actions.
700///
701/// # Example
702///
703/// ```rust,ignore
704/// use tessera_ui::cursor::{CursorEventContent, PressKeyEventType, ScrollEventConent};
705///
706/// // Handle different event types
707/// match event_content {
708///     CursorEventContent::Pressed(PressKeyEventType::Left) => {
709///         println!("Left button pressed");
710///     }
711///     CursorEventContent::Released(PressKeyEventType::Right) => {
712///         println!("Right button released");
713///     }
714///     CursorEventContent::Scroll(scroll) => {
715///         println!("Scrolled by ({}, {})", scroll.delta_x, scroll.delta_y);
716///     }
717/// }
718/// ```
719#[derive(Debug, Clone, PartialEq)]
720pub enum CursorEventContent {
721    /// A cursor button or touch point was pressed.
722    Pressed(PressKeyEventType),
723    /// A cursor button or touch point was released.
724    Released(PressKeyEventType),
725    /// A scroll action occurred (mouse wheel, touch drag, or inertial scroll).
726    Scroll(ScrollEventConent),
727}
728
729impl CursorEventContent {
730    /// Creates a cursor press/release event from winit mouse button events.
731    ///
732    /// This method converts winit's mouse button events into Tessera's cursor event format.
733    /// It handles the three standard mouse buttons (left, right, middle) and ignores
734    /// any additional buttons that may be present on some mice.
735    ///
736    /// # Arguments
737    ///
738    /// * `state` - Whether the button was pressed or released
739    /// * `button` - Which mouse button was affected
740    ///
741    /// # Returns
742    ///
743    /// - `Some(CursorEventContent)` for supported mouse buttons
744    /// - `None` for unsupported mouse buttons
745    ///
746    /// # Example
747    ///
748    /// ```rust,ignore
749    /// use tessera_ui::cursor::CursorEventContent;
750    /// use winit::event::{ElementState, MouseButton};
751    ///
752    /// let press_event = CursorEventContent::from_press_event(
753    ///     ElementState::Pressed,
754    ///     MouseButton::Left
755    /// );
756    ///
757    /// if let Some(event) = press_event {
758    ///     println!("Created cursor event: {:?}", event);
759    /// }
760    /// ```
761    pub fn from_press_event(
762        state: winit::event::ElementState,
763        button: winit::event::MouseButton,
764    ) -> Option<Self> {
765        let event_type = match button {
766            winit::event::MouseButton::Left => PressKeyEventType::Left,
767            winit::event::MouseButton::Right => PressKeyEventType::Right,
768            winit::event::MouseButton::Middle => PressKeyEventType::Middle,
769            _ => return None, // Ignore other buttons
770        };
771        let state = match state {
772            winit::event::ElementState::Pressed => Self::Pressed(event_type),
773            winit::event::ElementState::Released => Self::Released(event_type),
774        };
775        Some(state)
776    }
777
778    /// Creates a scroll event from winit mouse wheel events.
779    ///
780    /// This method converts winit's mouse scroll delta into Tessera's scroll event format.
781    /// It handles both line-based scrolling (typical mouse wheels) and pixel-based
782    /// scrolling (trackpads, precision mice) by applying appropriate scaling.
783    ///
784    /// # Arguments
785    ///
786    /// * `delta` - The scroll delta from winit
787    ///
788    /// # Returns
789    ///
790    /// A `CursorEventContent::Scroll` event with scaled delta values.
791    ///
792    /// # Example
793    ///
794    /// ```rust,ignore
795    /// use tessera_ui::cursor::CursorEventContent;
796    /// use winit::event::MouseScrollDelta;
797    ///
798    /// let scroll_event = CursorEventContent::from_scroll_event(
799    ///     MouseScrollDelta::LineDelta(0.0, 1.0)  // Scroll down one line
800    /// );
801    ///
802    /// match scroll_event {
803    ///     CursorEventContent::Scroll(scroll) => {
804    ///         println!("Scroll delta: ({}, {})", scroll.delta_x, scroll.delta_y);
805    ///     }
806    ///     _ => {}
807    /// }
808    /// ```
809    pub fn from_scroll_event(delta: winit::event::MouseScrollDelta) -> Self {
810        let (delta_x, delta_y) = match delta {
811            winit::event::MouseScrollDelta::LineDelta(x, y) => (x, y),
812            winit::event::MouseScrollDelta::PixelDelta(delta) => (delta.x as f32, delta.y as f32),
813        };
814
815        const MOUSE_WHEEL_SPEED_MULTIPLIER: f32 = 50.0;
816        Self::Scroll(ScrollEventConent {
817            delta_x: delta_x * MOUSE_WHEEL_SPEED_MULTIPLIER,
818            delta_y: delta_y * MOUSE_WHEEL_SPEED_MULTIPLIER,
819        })
820    }
821}
822
823/// Represents the different types of cursor buttons or touch interactions.
824///
825/// `PressKeyEventType` identifies which button was pressed or released in
826/// a cursor event. This covers the three standard mouse buttons that are
827/// commonly supported across different platforms and input devices.
828///
829/// # Example
830///
831/// ```rust,ignore
832/// use tessera_ui::cursor::PressKeyEventType;
833///
834/// match button_type {
835///     PressKeyEventType::Left => println!("Primary button (usually left-click)"),
836///     PressKeyEventType::Right => println!("Secondary button (usually right-click)"),
837///     PressKeyEventType::Middle => println!("Middle button (usually scroll wheel click)"),
838/// }
839/// ```
840#[derive(Debug, Clone, PartialEq, Eq)]
841pub enum PressKeyEventType {
842    /// The primary mouse button (typically left button) or primary touch.
843    Left,
844    /// The secondary mouse button (typically right button).
845    Right,
846    /// The middle mouse button (typically scroll wheel click).
847    Middle,
848}