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}