1use super::interaction_tracker::InteractionTracker;
5use crate::input::{
6 IPointer, InputElement, KeyModifiers, PointerCaptureLostEventArgs, PointerEventArgs, PointerPoint,
7 PointerPressedEventArgs, PointerReleasedEventArgs, PointerWheelEventArgs,
8};
9use crate::interactivity::Interactive;
10use crate::numerics::Vector3;
11use crate::reactive::IDisposable;
12use crate::rendering::composition::{CompositionDrawListVisual, CompositionVisual, Compositor};
13use crate::{Point, Ref, WeakRef};
14use std::cell::{Cell, RefCell};
15use std::collections::VecDeque;
16use std::rc::{Rc, Weak};
17
18const WHEEL_PIXELS_PER_NOTCH: f64 = 50.0;
21
22const WHEEL_SCALE_PER_NOTCH: f32 = 1.1;
24
25const RAILS_DECISION_DISTANCE: f64 = 4.0;
27
28const RAILS_SLOPE: f64 = 0.414_213_57;
30
31const VELOCITY_WINDOW_MS: u64 = 100;
34
35#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
37pub enum InteractionSourceMode {
38 #[default]
40 Disabled,
41 EnabledWithInertia,
43 EnabledWithoutInertia,
45}
46
47impl InteractionSourceMode {
48 fn is_enabled(self) -> bool {
49 self != Self::Disabled
50 }
51}
52
53#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
56pub enum InteractionChainingMode {
57 #[default]
58 Auto,
59 Always,
60 Never,
61}
62
63#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
66pub enum VisualInteractionSourceRedirectionMode {
67 #[default]
68 Off,
69 CapableTouchpadOnly,
70 PointerWheelOnly,
71 CapableTouchpadAndPointerWheel,
72}
73
74impl VisualInteractionSourceRedirectionMode {
75 fn includes_pointer_wheel(self) -> bool {
76 matches!(self, Self::PointerWheelOnly | Self::CapableTouchpadAndPointerWheel)
77 }
78}
79
80#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Hash)]
83pub enum InteractionSourceRedirectionMode {
84 Disabled,
85 #[default]
86 Enabled,
87}
88
89#[derive(Default)]
93pub struct InteractionSourceConfiguration {
94 position_x_source_mode: Cell<InteractionSourceRedirectionMode>,
95 position_y_source_mode: Cell<InteractionSourceRedirectionMode>,
96 scale_source_mode: Cell<InteractionSourceRedirectionMode>,
97}
98
99impl InteractionSourceConfiguration {
100 pub fn position_x_source_mode(&self) -> InteractionSourceRedirectionMode {
101 self.position_x_source_mode.get()
102 }
103
104 pub fn set_position_x_source_mode(&self, value: InteractionSourceRedirectionMode) {
105 self.position_x_source_mode.set(value)
106 }
107
108 pub fn position_y_source_mode(&self) -> InteractionSourceRedirectionMode {
109 self.position_y_source_mode.get()
110 }
111
112 pub fn set_position_y_source_mode(&self, value: InteractionSourceRedirectionMode) {
113 self.position_y_source_mode.set(value)
114 }
115
116 pub fn scale_source_mode(&self) -> InteractionSourceRedirectionMode {
117 self.scale_source_mode.get()
118 }
119
120 pub fn set_scale_source_mode(&self, value: InteractionSourceRedirectionMode) {
121 self.scale_source_mode.set(value)
122 }
123}
124
125#[derive(Clone, Copy, Debug, PartialEq, Eq)]
127enum Rail {
128 X,
129 Y,
130}
131
132struct Manipulation {
134 pointer: Option<Rc<dyn IPointer>>,
135 subscriptions: Vec<Rc<dyn IDisposable>>,
136 trackers: Vec<Rc<InteractionTracker>>,
137 start: Point,
138 last: Point,
139 rails_decided: bool,
140 rail: Option<Rail>,
141 samples: VecDeque<(u64, Point)>,
143}
144
145pub struct VisualInteractionSource {
163 this: Weak<VisualInteractionSource>,
164 source: Rc<CompositionVisual>,
165 element: RefCell<Option<WeakRef<InputElement>>>,
166 position_x_source_mode: Cell<InteractionSourceMode>,
167 position_y_source_mode: Cell<InteractionSourceMode>,
168 scale_source_mode: Cell<InteractionSourceMode>,
169 position_x_chaining_mode: Cell<InteractionChainingMode>,
170 position_y_chaining_mode: Cell<InteractionChainingMode>,
171 scale_chaining_mode: Cell<InteractionChainingMode>,
172 manipulation_redirection_mode: Cell<VisualInteractionSourceRedirectionMode>,
173 is_position_x_rails_enabled: Cell<bool>,
174 is_position_y_rails_enabled: Cell<bool>,
175 pointer_wheel_config: Rc<InteractionSourceConfiguration>,
176 delta_position: Cell<Vector3>,
177 delta_scale: Cell<f32>,
178 position: Cell<Vector3>,
179 position_velocity: Cell<Vector3>,
180 scale: Cell<f32>,
181 scale_velocity: Cell<f32>,
182 trackers: RefCell<Vec<Weak<InteractionTracker>>>,
183 manipulation: RefCell<Option<Manipulation>>,
184 wheel_subscription: RefCell<Option<Rc<dyn IDisposable>>>,
185}
186
187impl VisualInteractionSource {
188 pub fn create(source: &Rc<CompositionVisual>) -> Rc<VisualInteractionSource> {
191 let element = Self::find_element(source);
192 Self::create_core(source, element)
193 }
194
195 pub fn create_for_element(source: &Rc<CompositionVisual>, element: &Ref<InputElement>) -> Rc<VisualInteractionSource> {
199 Self::create_core(source, Some(element.clone()))
200 }
201
202 fn create_core(source: &Rc<CompositionVisual>, element: Option<Ref<InputElement>>) -> Rc<VisualInteractionSource> {
203 Rc::new_cyclic(|this: &Weak<VisualInteractionSource>| VisualInteractionSource {
204 this: this.clone(),
205 source: source.clone(),
206 element: RefCell::new(element.map(|element| element.downgrade())),
207 position_x_source_mode: Cell::new(InteractionSourceMode::Disabled),
208 position_y_source_mode: Cell::new(InteractionSourceMode::Disabled),
209 scale_source_mode: Cell::new(InteractionSourceMode::Disabled),
210 position_x_chaining_mode: Cell::new(InteractionChainingMode::Auto),
211 position_y_chaining_mode: Cell::new(InteractionChainingMode::Auto),
212 scale_chaining_mode: Cell::new(InteractionChainingMode::Auto),
213 manipulation_redirection_mode: Cell::new(VisualInteractionSourceRedirectionMode::Off),
214 is_position_x_rails_enabled: Cell::new(true),
215 is_position_y_rails_enabled: Cell::new(true),
216 pointer_wheel_config: Rc::new(InteractionSourceConfiguration::default()),
217 delta_position: Cell::new(Vector3::ZERO),
218 delta_scale: Cell::new(1.0),
219 position: Cell::new(Vector3::ZERO),
220 position_velocity: Cell::new(Vector3::ZERO),
221 scale: Cell::new(1.0),
222 scale_velocity: Cell::new(0.0),
223 trackers: RefCell::new(Vec::new()),
224 manipulation: RefCell::new(None),
225 wheel_subscription: RefCell::new(None),
226 })
227 }
228
229 fn find_element(visual: &Rc<CompositionVisual>) -> Option<Ref<InputElement>> {
232 let mut current = Some(visual.clone());
233 while let Some(visual) = current {
234 let element = CompositionDrawListVisual::from_visual(&visual)
235 .and_then(|draw_list| draw_list.visual())
236 .and_then(|element| element.downcast::<InputElement>().ok());
237 if element.is_some() {
238 return element;
239 }
240 current = visual.parent();
241 }
242 None
243 }
244
245 pub fn element(&self) -> Option<Ref<InputElement>> {
248 if let Some(element) = self.element.borrow().as_ref().and_then(WeakRef::upgrade) {
249 return Some(element);
250 }
251 let element = Self::find_element(&self.source)?;
252 *self.element.borrow_mut() = Some(element.downgrade());
253 Some(element)
254 }
255
256 pub fn source(&self) -> &Rc<CompositionVisual> {
258 &self.source
259 }
260
261 pub fn compositor(&self) -> &Rc<Compositor> {
263 self.source.compositor()
264 }
265
266 pub fn position_x_source_mode(&self) -> InteractionSourceMode {
270 self.position_x_source_mode.get()
271 }
272
273 pub fn set_position_x_source_mode(&self, value: InteractionSourceMode) {
274 self.position_x_source_mode.set(value)
275 }
276
277 pub fn position_y_source_mode(&self) -> InteractionSourceMode {
279 self.position_y_source_mode.get()
280 }
281
282 pub fn set_position_y_source_mode(&self, value: InteractionSourceMode) {
283 self.position_y_source_mode.set(value)
284 }
285
286 pub fn scale_source_mode(&self) -> InteractionSourceMode {
289 self.scale_source_mode.get()
290 }
291
292 pub fn set_scale_source_mode(&self, value: InteractionSourceMode) {
293 self.scale_source_mode.set(value)
294 }
295
296 pub fn position_x_chaining_mode(&self) -> InteractionChainingMode {
301 self.position_x_chaining_mode.get()
302 }
303
304 pub fn set_position_x_chaining_mode(&self, value: InteractionChainingMode) {
305 self.position_x_chaining_mode.set(value)
306 }
307
308 pub fn position_y_chaining_mode(&self) -> InteractionChainingMode {
310 self.position_y_chaining_mode.get()
311 }
312
313 pub fn set_position_y_chaining_mode(&self, value: InteractionChainingMode) {
314 self.position_y_chaining_mode.set(value)
315 }
316
317 pub fn scale_chaining_mode(&self) -> InteractionChainingMode {
319 self.scale_chaining_mode.get()
320 }
321
322 pub fn set_scale_chaining_mode(&self, value: InteractionChainingMode) {
323 self.scale_chaining_mode.set(value)
324 }
325
326 pub fn manipulation_redirection_mode(&self) -> VisualInteractionSourceRedirectionMode {
331 self.manipulation_redirection_mode.get()
332 }
333
334 pub fn set_manipulation_redirection_mode(&self, value: VisualInteractionSourceRedirectionMode) {
335 self.manipulation_redirection_mode.set(value);
336 self.update_wheel_subscription();
337 }
338
339 pub fn is_position_x_rails_enabled(&self) -> bool {
342 self.is_position_x_rails_enabled.get()
343 }
344
345 pub fn set_is_position_x_rails_enabled(&self, value: bool) {
346 self.is_position_x_rails_enabled.set(value)
347 }
348
349 pub fn is_position_y_rails_enabled(&self) -> bool {
351 self.is_position_y_rails_enabled.get()
352 }
353
354 pub fn set_is_position_y_rails_enabled(&self, value: bool) {
355 self.is_position_y_rails_enabled.set(value)
356 }
357
358 pub fn pointer_wheel_config(&self) -> &Rc<InteractionSourceConfiguration> {
360 &self.pointer_wheel_config
361 }
362
363 pub fn delta_position(&self) -> Vector3 {
372 self.delta_position.get()
373 }
374
375 pub fn delta_scale(&self) -> f32 {
377 self.delta_scale.get()
378 }
379
380 pub fn position(&self) -> Vector3 {
382 self.position.get()
383 }
384
385 pub fn position_velocity(&self) -> Vector3 {
387 self.position_velocity.get()
388 }
389
390 pub fn scale(&self) -> f32 {
392 self.scale.get()
393 }
394
395 pub fn scale_velocity(&self) -> f32 {
397 self.scale_velocity.get()
398 }
399
400 pub(crate) fn attach_tracker(&self, tracker: &Weak<InteractionTracker>) {
403 let mut trackers = self.trackers.borrow_mut();
404 trackers.retain(|t| t.strong_count() > 0);
405 if !trackers.iter().any(|t| t.ptr_eq(tracker)) {
406 trackers.push(tracker.clone());
407 }
408 drop(trackers);
409 self.update_wheel_subscription();
410 }
411
412 pub(crate) fn detach_tracker(&self, tracker: &Weak<InteractionTracker>) {
413 self.trackers.borrow_mut().retain(|t| t.strong_count() > 0 && !t.ptr_eq(tracker));
414 }
415
416 fn live_trackers(&self) -> Vec<Rc<InteractionTracker>> {
417 self.trackers.borrow().iter().filter_map(Weak::upgrade).collect()
418 }
419
420 pub fn try_redirect_for_manipulation(&self, e: &PointerPressedEventArgs) -> bool {
431 let element = self
432 .element()
433 .or_else(|| e.source().and_then(|source| source.downcast::<InputElement>().ok()));
434 let Some(element) = element else { return false };
435 let point = e.get_current_point(None);
436 self.try_redirect_for_manipulation_point(&point, &element)
437 }
438
439 pub fn try_redirect_for_manipulation_point(&self, point: &PointerPoint, element: &Ref<InputElement>) -> bool {
444 if self.manipulation.borrow().is_some() {
445 return false;
446 }
447 if !self.position_x_source_mode.get().is_enabled() && !self.position_y_source_mode.get().is_enabled() {
448 return false;
449 }
450 let pointer = point.pointer.clone();
451 let pointer_id = pointer.id();
452 pointer.capture(Some(element));
453
454 let mut subscriptions = Vec::new();
455 let weak = self.this.clone();
456 subscriptions.push(element.add_disposable_handler_with(
457 InputElement::pointer_moved_event(),
458 move |_, e: &PointerEventArgs| {
459 if e.pointer().id() != pointer_id {
460 return;
461 }
462 if let Some(this) = weak.upgrade() {
463 this.manipulation_moved(e.get_current_point(None).position, e.timestamp());
464 }
465 },
466 Interactive::DEFAULT_ROUTES,
467 true,
468 ));
469 let weak = self.this.clone();
470 subscriptions.push(element.add_disposable_handler_with(
471 InputElement::pointer_released_event(),
472 move |_, e: &PointerReleasedEventArgs| {
473 if e.pointer().id() != pointer_id {
474 return;
475 }
476 if let Some(this) = weak.upgrade() {
477 this.manipulation_moved(e.get_current_point(None).position, e.timestamp());
478 this.manipulation_ended(Some(e.timestamp()));
479 }
480 },
481 Interactive::DEFAULT_ROUTES,
482 true,
483 ));
484 let weak = self.this.clone();
485 subscriptions.push(element.add_disposable_handler_with(
486 InputElement::pointer_capture_lost_event(),
487 move |_, e: &PointerCaptureLostEventArgs| {
488 if e.pointer().id() != pointer_id {
489 return;
490 }
491 if let Some(this) = weak.upgrade() {
492 this.manipulation_ended(None);
495 }
496 },
497 Interactive::DEFAULT_ROUTES,
498 true,
499 ));
500
501 self.begin_manipulation(point.position, Some(pointer), subscriptions);
502 true
503 }
504
505 pub(crate) fn begin_manipulation(
508 &self,
509 position: Point,
510 pointer: Option<Rc<dyn IPointer>>,
511 subscriptions: Vec<Rc<dyn IDisposable>>,
512 ) {
513 let trackers = self.live_trackers();
514 *self.manipulation.borrow_mut() = Some(Manipulation {
515 pointer,
516 subscriptions,
517 trackers: trackers.clone(),
518 start: position,
519 last: position,
520 rails_decided: false,
521 rail: None,
522 samples: VecDeque::new(),
523 });
524 self.position.set(Vector3::ZERO);
525 self.delta_position.set(Vector3::ZERO);
526 self.position_velocity.set(Vector3::ZERO);
527 for tracker in trackers {
528 tracker.begin_interaction();
529 }
530 }
531
532 pub(crate) fn manipulation_moved(&self, position: Point, timestamp: u64) {
534 let (delta, trackers) = {
535 let mut manipulation = self.manipulation.borrow_mut();
536 let Some(m) = manipulation.as_mut() else { return };
537 let total = position - m.start;
538 if !m.rails_decided && (total.x * total.x + total.y * total.y).sqrt() >= RAILS_DECISION_DISTANCE {
539 m.rails_decided = true;
540 if self.is_position_x_rails_enabled.get() && total.y.abs() <= total.x.abs() * RAILS_SLOPE {
541 m.rail = Some(Rail::X);
542 } else if self.is_position_y_rails_enabled.get() && total.x.abs() <= total.y.abs() * RAILS_SLOPE {
543 m.rail = Some(Rail::Y);
544 }
545 }
546 let mut step = position - m.last;
547 m.last = position;
548 match m.rail {
549 Some(Rail::X) => step = Point::new(step.x, 0.0),
550 Some(Rail::Y) => step = Point::new(0.0, step.y),
551 None => {}
552 }
553 m.samples.push_back((timestamp, position));
554 while m.samples.len() > 2 && m.samples.front().is_some_and(|(t, _)| timestamp.saturating_sub(*t) > VELOCITY_WINDOW_MS) {
555 m.samples.pop_front();
556 }
557 let delta = self.mask(Vector3::new(-step.x as f32, -step.y as f32, 0.0));
560 self.position.set(self.position.get() + delta);
561 (delta, m.trackers.clone())
562 };
563 self.delta_position.set(delta);
564 let velocity = self.release_velocity(timestamp, false);
565 self.position_velocity.set(velocity);
566 if delta == Vector3::ZERO {
567 return;
568 }
569 for tracker in trackers {
570 tracker.interaction_delta(delta, velocity);
571 }
572 }
573
574 pub(crate) fn manipulation_ended(&self, timestamp: Option<u64>) {
578 let velocity = match timestamp {
579 Some(timestamp) => self.release_velocity(timestamp, true),
580 None => Vector3::ZERO,
581 };
582 let Some(manipulation) = self.manipulation.borrow_mut().take() else { return };
583 for subscription in &manipulation.subscriptions {
584 subscription.dispose();
585 }
586 drop(manipulation.pointer);
587 self.position_velocity.set(Vector3::ZERO);
588 for tracker in manipulation.trackers {
589 tracker.end_interaction(velocity);
590 }
591 }
592
593 fn mask(&self, v: Vector3) -> Vector3 {
595 Vector3::new(
596 if self.position_x_source_mode.get().is_enabled() { v.x } else { 0.0 },
597 if self.position_y_source_mode.get().is_enabled() { v.y } else { 0.0 },
598 0.0,
599 )
600 }
601
602 fn release_velocity(&self, now: u64, release: bool) -> Vector3 {
607 let manipulation = self.manipulation.borrow();
608 let Some(m) = manipulation.as_ref() else { return Vector3::ZERO };
609 let (Some(&(t0, p0)), Some(&(t1, p1))) = (m.samples.front(), m.samples.back()) else { return Vector3::ZERO };
610 if t1 <= t0 || now.saturating_sub(t1) > VELOCITY_WINDOW_MS {
611 return Vector3::ZERO;
612 }
613 let seconds = (t1 - t0) as f64 / 1000.0;
614 let mut v = (p1 - p0) / seconds;
615 match m.rail {
616 Some(Rail::X) => v = Point::new(v.x, 0.0),
617 Some(Rail::Y) => v = Point::new(0.0, v.y),
618 None => {}
619 }
620 let v = self.mask(Vector3::new(-v.x as f32, -v.y as f32, 0.0));
621 if !release {
622 return v;
623 }
624 let inertia = |mode: InteractionSourceMode| mode == InteractionSourceMode::EnabledWithInertia;
625 Vector3::new(
626 if inertia(self.position_x_source_mode.get()) { v.x } else { 0.0 },
627 if inertia(self.position_y_source_mode.get()) { v.y } else { 0.0 },
628 0.0,
629 )
630 }
631
632 fn update_wheel_subscription(&self) {
635 let wanted = self.manipulation_redirection_mode.get().includes_pointer_wheel();
636 if !wanted {
637 if let Some(subscription) = self.wheel_subscription.borrow_mut().take() {
638 subscription.dispose();
639 }
640 return;
641 }
642 if self.wheel_subscription.borrow().is_some() {
643 return;
644 }
645 let Some(element) = self.element() else { return };
646 let weak = self.this.clone();
647 let subscription = element.add_disposable_handler_with(
648 InputElement::pointer_wheel_changed_event(),
649 move |_, e: &PointerWheelEventArgs| {
650 if e.handled() {
651 return;
652 }
653 if let Some(this) = weak.upgrade() {
654 if this.on_wheel(e) {
655 e.set_handled(true);
656 }
657 }
658 },
659 Interactive::DEFAULT_ROUTES,
660 false,
661 );
662 *self.wheel_subscription.borrow_mut() = Some(subscription);
663 }
664
665 fn on_wheel(&self, e: &PointerWheelEventArgs) -> bool {
666 if !self.manipulation_redirection_mode.get().includes_pointer_wheel() {
667 return false;
668 }
669 let delta = e.delta();
670 let config = &self.pointer_wheel_config;
671 if e.key_modifiers().contains(KeyModifiers::CONTROL) {
672 if !self.scale_source_mode.get().is_enabled()
673 || config.scale_source_mode() != InteractionSourceRedirectionMode::Enabled
674 {
675 return false;
676 }
677 let center = match self.element() {
678 Some(element) => e.get_position(Some(&element)),
679 None => Point::default(),
680 };
681 return self.wheel_scale(delta.y as f32, Vector3::new(center.x as f32, center.y as f32, 0.0));
682 }
683 let x = if self.position_x_source_mode.get().is_enabled()
684 && config.position_x_source_mode() == InteractionSourceRedirectionMode::Enabled
685 {
686 -delta.x * WHEEL_PIXELS_PER_NOTCH
687 } else {
688 0.0
689 };
690 let y = if self.position_y_source_mode.get().is_enabled()
691 && config.position_y_source_mode() == InteractionSourceRedirectionMode::Enabled
692 {
693 -delta.y * WHEEL_PIXELS_PER_NOTCH
694 } else {
695 0.0
696 };
697 self.wheel_position(Vector3::new(x as f32, y as f32, 0.0))
698 }
699
700 pub(crate) fn wheel_position(&self, delta: Vector3) -> bool {
703 if delta == Vector3::ZERO {
704 return false;
705 }
706 let trackers = self.live_trackers();
707 self.delta_position.set(delta);
708 for tracker in &trackers {
709 tracker.wheel_delta(delta);
710 }
711 !trackers.is_empty()
712 }
713
714 fn wheel_scale(&self, notches: f32, center: Vector3) -> bool {
716 if notches == 0.0 {
717 return false;
718 }
719 let factor = WHEEL_SCALE_PER_NOTCH.powf(notches);
720 let trackers = self.live_trackers();
721 self.delta_scale.set(factor);
722 self.scale.set(self.scale.get() * factor);
723 for tracker in &trackers {
724 tracker.wheel_scale(factor, center);
725 }
726 !trackers.is_empty()
727 }
728}
729
730impl Drop for VisualInteractionSource {
731 fn drop(&mut self) {
732 if let Some(subscription) = self.wheel_subscription.get_mut().take() {
733 subscription.dispose();
734 }
735 if let Some(manipulation) = self.manipulation.get_mut().take() {
736 for subscription in &manipulation.subscriptions {
737 subscription.dispose();
738 }
739 }
740 }
741}