1use crate::geometry::PropertyValue;
24use crate::interaction::{
25 InteractionButton, InteractionEvent, InteractionEventKind, InteractionModifiers,
26 InteractionTarget, PointerKind, ScreenPoint,
27};
28use crate::picking::{PickHit, PickOptions, PickQuery, PickResult};
29use crate::query::FeatureStateId;
30use crate::MapState;
31
32#[derive(Debug, Clone)]
42pub struct InteractionConfig {
43 pub drag_threshold_px: f64,
46
47 pub double_click_window_secs: f64,
50
51 pub auto_hover_state: bool,
55
56 pub auto_select_state: bool,
60
61 pub interactive_layers: Vec<String>,
65
66 pub tolerance_meters: f64,
69}
70
71impl Default for InteractionConfig {
72 fn default() -> Self {
73 Self {
74 drag_threshold_px: 5.0,
75 double_click_window_secs: 0.3,
76 auto_hover_state: true,
77 auto_select_state: false,
78 interactive_layers: Vec::new(),
79 tolerance_meters: 0.0,
80 }
81 }
82}
83
84#[derive(Debug, Clone)]
90struct HitSnapshot {
91 hit: PickHit,
93 target: InteractionTarget,
95}
96
97#[derive(Debug)]
115pub struct InteractionManager {
116 config: InteractionConfig,
118
119 cursor: ScreenPoint,
121
122 prev_hit: Option<HitSnapshot>,
124
125 hovered: Option<FeatureStateId>,
127
128 selected: Option<FeatureStateId>,
130
131 pointer_down_pos: Option<ScreenPoint>,
133
134 pointer_down_time: Option<f64>,
136
137 dragging: bool,
139
140 last_click_time: Option<f64>,
143
144 current_time: f64,
147
148 events: Vec<InteractionEvent>,
150}
151
152impl InteractionManager {
153 pub fn new() -> Self {
155 Self::with_config(InteractionConfig::default())
156 }
157
158 pub fn with_config(config: InteractionConfig) -> Self {
160 Self {
161 config,
162 cursor: ScreenPoint::default(),
163 prev_hit: None,
164 hovered: None,
165 selected: None,
166 pointer_down_pos: None,
167 pointer_down_time: None,
168 dragging: false,
169 last_click_time: None,
170 current_time: 0.0,
171 events: Vec::new(),
172 }
173 }
174
175 pub fn config(&self) -> &InteractionConfig {
177 &self.config
178 }
179
180 pub fn config_mut(&mut self) -> &mut InteractionConfig {
182 &mut self.config
183 }
184
185 pub fn hovered(&self) -> Option<&FeatureStateId> {
187 self.hovered.as_ref()
188 }
189
190 pub fn selected(&self) -> Option<&FeatureStateId> {
192 self.selected.as_ref()
193 }
194
195 pub fn cursor(&self) -> ScreenPoint {
197 self.cursor
198 }
199
200 pub fn is_dragging(&self) -> bool {
202 self.dragging
203 }
204
205 pub fn update_pointer_move(
217 &mut self,
218 map: &mut MapState,
219 x: f64,
220 y: f64,
221 time: f64,
222 pointer_kind: PointerKind,
223 modifiers: InteractionModifiers,
224 ) {
225 self.current_time = time;
226 self.cursor = ScreenPoint::new(x, y);
227
228 if let Some(down_pos) = self.pointer_down_pos {
230 let dx = x - down_pos.x;
231 let dy = y - down_pos.y;
232 if (dx * dx + dy * dy).sqrt() > self.config.drag_threshold_px {
233 self.dragging = true;
234 }
235 }
236
237 let result = self.pick_at(map, x, y);
239 let current = top_hit_snapshot(&result);
240
241 let prev_target = self.prev_hit.as_ref().map(|s| &s.target);
243 let curr_target = current.as_ref().map(|s| &s.target);
244
245 if prev_target != curr_target {
246 if let Some(prev) = &self.prev_hit {
248 let mut event = self.base_event(
249 InteractionEventKind::MouseLeave,
250 pointer_kind,
251 modifiers,
252 );
253 event.target = Some(prev.target.clone());
254 event.related_target = curr_target.cloned();
256 self.events.push(event);
257 }
258
259 if self.config.auto_hover_state {
261 if let Some(prev_id) = self.hovered.take() {
262 map.set_feature_state_property(
263 &prev_id.source_id,
264 &prev_id.feature_id,
265 "hover",
266 PropertyValue::Bool(false),
267 );
268 }
269 }
270
271 if let Some(curr) = ¤t {
273 let mut event = self.base_event(
274 InteractionEventKind::MouseEnter,
275 pointer_kind,
276 modifiers,
277 );
278 event.target = Some(curr.target.clone());
279 event.hit = Some(curr.hit.clone());
280 event.related_target = prev_target.cloned();
282 self.events.push(event);
283
284 if self.config.auto_hover_state {
286 if let Some(id) = feature_state_id_from_target(&curr.target) {
287 map.set_feature_state_property(
288 &id.source_id,
289 &id.feature_id,
290 "hover",
291 PropertyValue::Bool(true),
292 );
293 self.hovered = Some(id);
294 }
295 }
296 }
297 }
298
299 let mut move_event =
301 self.base_event(InteractionEventKind::MouseMove, pointer_kind, modifiers);
302 if let Some(curr) = ¤t {
303 move_event.target = Some(curr.target.clone());
304 move_event.hit = Some(curr.hit.clone());
305 }
306 self.events.push(move_event);
307
308 self.prev_hit = current;
310 }
311
312 pub fn update_pointer_down(
319 &mut self,
320 map: &MapState,
321 x: f64,
322 y: f64,
323 time: f64,
324 button: InteractionButton,
325 pointer_kind: PointerKind,
326 modifiers: InteractionModifiers,
327 ) {
328 let _ = map; self.current_time = time;
330 self.pointer_down_pos = Some(ScreenPoint::new(x, y));
331 self.pointer_down_time = Some(time);
332 self.dragging = false;
333
334 let mut event = self.base_event(
335 InteractionEventKind::MouseDown,
336 pointer_kind,
337 modifiers,
338 );
339 event.button = Some(button);
340 if let Some(snapshot) = &self.prev_hit {
342 event.target = Some(snapshot.target.clone());
343 event.hit = Some(snapshot.hit.clone());
344 }
345 self.events.push(event);
346 }
347
348 pub fn update_pointer_up(
359 &mut self,
360 map: &mut MapState,
361 x: f64,
362 y: f64,
363 time: f64,
364 button: InteractionButton,
365 pointer_kind: PointerKind,
366 modifiers: InteractionModifiers,
367 ) {
368 self.current_time = time;
369 self.cursor = ScreenPoint::new(x, y);
370
371 let mut up_event = self.base_event(
373 InteractionEventKind::MouseUp,
374 pointer_kind,
375 modifiers,
376 );
377 up_event.button = Some(button);
378 if let Some(snapshot) = &self.prev_hit {
379 up_event.target = Some(snapshot.target.clone());
380 up_event.hit = Some(snapshot.hit.clone());
381 }
382 self.events.push(up_event);
383
384 let is_click = !self.dragging;
386
387 if is_click {
388 let mut click_event = self.base_event(
390 InteractionEventKind::Click,
391 pointer_kind,
392 modifiers,
393 );
394 click_event.button = Some(button);
395 if let Some(snapshot) = &self.prev_hit {
396 click_event.target = Some(snapshot.target.clone());
397 click_event.hit = Some(snapshot.hit.clone());
398 }
399 self.events.push(click_event);
400
401 if self.config.auto_select_state {
403 self.update_selection(map);
404 }
405
406 if let Some(prev_click) = self.last_click_time {
408 if (time - prev_click) <= self.config.double_click_window_secs {
409 let mut dbl_event = self.base_event(
410 InteractionEventKind::DoubleClick,
411 pointer_kind,
412 modifiers,
413 );
414 dbl_event.button = Some(button);
415 if let Some(snapshot) = &self.prev_hit {
416 dbl_event.target = Some(snapshot.target.clone());
417 dbl_event.hit = Some(snapshot.hit.clone());
418 }
419 self.events.push(dbl_event);
420 self.last_click_time = None;
422 } else {
423 self.last_click_time = Some(time);
424 }
425 } else {
426 self.last_click_time = Some(time);
427 }
428 }
429
430 self.pointer_down_pos = None;
432 self.pointer_down_time = None;
433 self.dragging = false;
434 }
435
436 pub fn update_pointer_leave(
441 &mut self,
442 map: &mut MapState,
443 pointer_kind: PointerKind,
444 modifiers: InteractionModifiers,
445 ) {
446 if let Some(prev) = self.prev_hit.take() {
448 let mut event = self.base_event(
449 InteractionEventKind::MouseLeave,
450 pointer_kind,
451 modifiers,
452 );
453 event.target = Some(prev.target);
454 self.events.push(event);
455 }
456
457 if self.config.auto_hover_state {
459 if let Some(prev_id) = self.hovered.take() {
460 map.set_feature_state_property(
461 &prev_id.source_id,
462 &prev_id.feature_id,
463 "hover",
464 PropertyValue::Bool(false),
465 );
466 }
467 }
468
469 self.pointer_down_pos = None;
470 self.pointer_down_time = None;
471 self.dragging = false;
472 }
473
474 pub fn drain_events(&mut self) -> Vec<InteractionEvent> {
484 std::mem::take(&mut self.events)
485 }
486
487 pub fn pending_event_count(&self) -> usize {
489 self.events.len()
490 }
491
492 fn pick_at(&self, map: &MapState, x: f64, y: f64) -> PickResult {
498 let mut options = PickOptions::new();
499 options.tolerance_meters = self.config.tolerance_meters;
500 options.limit = 1;
501 map.pick(PickQuery::screen(x, y), options)
502 }
503
504 fn base_event(
506 &self,
507 kind: InteractionEventKind,
508 pointer_kind: PointerKind,
509 modifiers: InteractionModifiers,
510 ) -> InteractionEvent {
511 InteractionEvent::new(kind, pointer_kind, self.cursor)
512 .with_modifiers(modifiers)
513 }
514
515 fn update_selection(&mut self, map: &mut MapState) {
517 if let Some(prev_id) = self.selected.take() {
519 map.set_feature_state_property(
520 &prev_id.source_id,
521 &prev_id.feature_id,
522 "selected",
523 PropertyValue::Bool(false),
524 );
525 }
526
527 if let Some(snapshot) = &self.prev_hit {
529 if let Some(id) = feature_state_id_from_target(&snapshot.target) {
530 map.set_feature_state_property(
531 &id.source_id,
532 &id.feature_id,
533 "selected",
534 PropertyValue::Bool(true),
535 );
536 self.selected = Some(id);
537 }
538 }
539 }
540}
541
542impl Default for InteractionManager {
543 fn default() -> Self {
544 Self::new()
545 }
546}
547
548fn top_hit_snapshot(result: &PickResult) -> Option<HitSnapshot> {
554 result.first().map(|hit| HitSnapshot {
555 target: InteractionTarget::from_pick_hit(hit),
556 hit: hit.clone(),
557 })
558}
559
560fn feature_state_id_from_target(target: &InteractionTarget) -> Option<FeatureStateId> {
563 match (&target.source_id, &target.feature_id) {
564 (Some(source_id), Some(feature_id)) => {
565 Some(FeatureStateId::new(source_id.clone(), feature_id.clone()))
566 }
567 _ => None,
568 }
569}
570
571#[cfg(test)]
576mod tests {
577 use super::*;
578 use crate::geometry::{Feature, FeatureCollection, Geometry, Point};
579 use crate::layers::{VectorLayer, VectorStyle};
580 use rustial_math::GeoCoord;
581
582 fn map_with_point_at_center() -> MapState {
585 let mut state = MapState::new();
586 state.set_viewport(800, 600);
587 let target = GeoCoord::from_lat_lon(0.0, 0.0);
588 state.set_camera_target(target);
589 state.set_camera_distance(500.0);
590
591 let fc = FeatureCollection {
592 features: vec![Feature {
593 geometry: Geometry::Point(Point { coord: target }),
594 properties: Default::default(),
595 }],
596 };
597 let vl = VectorLayer::new("points", fc, VectorStyle::default());
598 state.push_layer(Box::new(vl));
599 state.update();
600 state
601 }
602
603 #[test]
606 fn pointer_move_over_feature_emits_enter_then_move() {
607 let mut map = map_with_point_at_center();
608 let mut mgr = InteractionManager::new();
609
610 mgr.update_pointer_move(
612 &mut map,
613 400.0,
614 300.0,
615 0.0,
616 PointerKind::Mouse,
617 InteractionModifiers::default(),
618 );
619
620 let events = mgr.drain_events();
621 assert!(events.len() >= 2, "expected at least enter + move, got {}", events.len());
623 assert_eq!(events[0].kind, InteractionEventKind::MouseEnter);
624 assert_eq!(events[1].kind, InteractionEventKind::MouseMove);
625 assert!(events[0].target.is_some(), "enter event should have a target");
626 }
627
628 #[test]
629 fn pointer_move_away_emits_leave() {
630 let mut map = map_with_point_at_center();
631 let mut mgr = InteractionManager::new();
632
633 mgr.update_pointer_move(
635 &mut map, 400.0, 300.0, 0.0,
636 PointerKind::Mouse, InteractionModifiers::default(),
637 );
638 mgr.drain_events(); mgr.update_pointer_move(
642 &mut map, 10.0, 10.0, 0.1,
643 PointerKind::Mouse, InteractionModifiers::default(),
644 );
645
646 let events = mgr.drain_events();
647 let kinds: Vec<_> = events.iter().map(|e| e.kind).collect();
649 assert!(
650 kinds.contains(&InteractionEventKind::MouseLeave),
651 "expected MouseLeave, got {:?}",
652 kinds
653 );
654 }
655
656 #[test]
659 fn click_within_threshold_emits_click() {
660 let mut map = map_with_point_at_center();
661 let mut mgr = InteractionManager::new();
662
663 mgr.update_pointer_move(
665 &mut map, 400.0, 300.0, 0.0,
666 PointerKind::Mouse, InteractionModifiers::default(),
667 );
668 mgr.drain_events();
669
670 mgr.update_pointer_down(
672 &map, 400.0, 300.0, 1.0,
673 InteractionButton::Primary,
674 PointerKind::Mouse, InteractionModifiers::default(),
675 );
676 mgr.drain_events();
677
678 mgr.update_pointer_up(
680 &mut map, 401.0, 300.0, 1.1,
681 InteractionButton::Primary,
682 PointerKind::Mouse, InteractionModifiers::default(),
683 );
684
685 let events = mgr.drain_events();
686 let kinds: Vec<_> = events.iter().map(|e| e.kind).collect();
687 assert!(kinds.contains(&InteractionEventKind::MouseUp));
688 assert!(
689 kinds.contains(&InteractionEventKind::Click),
690 "expected Click, got {:?}",
691 kinds
692 );
693 }
694
695 #[test]
696 fn drag_beyond_threshold_suppresses_click() {
697 let mut map = map_with_point_at_center();
698 let mut mgr = InteractionManager::new();
699
700 mgr.update_pointer_move(
701 &mut map, 400.0, 300.0, 0.0,
702 PointerKind::Mouse, InteractionModifiers::default(),
703 );
704 mgr.drain_events();
705
706 mgr.update_pointer_down(
708 &map, 400.0, 300.0, 1.0,
709 InteractionButton::Primary,
710 PointerKind::Mouse, InteractionModifiers::default(),
711 );
712 mgr.drain_events();
713
714 mgr.update_pointer_move(
716 &mut map, 420.0, 320.0, 1.05,
717 PointerKind::Mouse, InteractionModifiers::default(),
718 );
719 mgr.drain_events();
720
721 mgr.update_pointer_up(
723 &mut map, 420.0, 320.0, 1.1,
724 InteractionButton::Primary,
725 PointerKind::Mouse, InteractionModifiers::default(),
726 );
727
728 let events = mgr.drain_events();
729 let kinds: Vec<_> = events.iter().map(|e| e.kind).collect();
730 assert!(kinds.contains(&InteractionEventKind::MouseUp));
731 assert!(
732 !kinds.contains(&InteractionEventKind::Click),
733 "Click should be suppressed after drag, got {:?}",
734 kinds
735 );
736 }
737
738 #[test]
741 fn two_clicks_within_window_emit_double_click() {
742 let mut map = map_with_point_at_center();
743 let mut mgr = InteractionManager::new();
744
745 mgr.update_pointer_move(
746 &mut map, 400.0, 300.0, 0.0,
747 PointerKind::Mouse, InteractionModifiers::default(),
748 );
749 mgr.drain_events();
750
751 mgr.update_pointer_down(
753 &map, 400.0, 300.0, 1.0,
754 InteractionButton::Primary,
755 PointerKind::Mouse, InteractionModifiers::default(),
756 );
757 mgr.update_pointer_up(
758 &mut map, 400.0, 300.0, 1.05,
759 InteractionButton::Primary,
760 PointerKind::Mouse, InteractionModifiers::default(),
761 );
762 mgr.drain_events(); mgr.update_pointer_down(
766 &map, 400.0, 300.0, 1.2,
767 InteractionButton::Primary,
768 PointerKind::Mouse, InteractionModifiers::default(),
769 );
770 mgr.update_pointer_up(
771 &mut map, 400.0, 300.0, 1.25,
772 InteractionButton::Primary,
773 PointerKind::Mouse, InteractionModifiers::default(),
774 );
775
776 let events = mgr.drain_events();
777 let kinds: Vec<_> = events.iter().map(|e| e.kind).collect();
778 assert!(
779 kinds.contains(&InteractionEventKind::DoubleClick),
780 "expected DoubleClick, got {:?}",
781 kinds
782 );
783 }
784
785 #[test]
786 fn two_clicks_outside_window_do_not_emit_double_click() {
787 let mut map = map_with_point_at_center();
788 let mut mgr = InteractionManager::new();
789
790 mgr.update_pointer_move(
791 &mut map, 400.0, 300.0, 0.0,
792 PointerKind::Mouse, InteractionModifiers::default(),
793 );
794 mgr.drain_events();
795
796 mgr.update_pointer_down(
798 &map, 400.0, 300.0, 1.0,
799 InteractionButton::Primary,
800 PointerKind::Mouse, InteractionModifiers::default(),
801 );
802 mgr.update_pointer_up(
803 &mut map, 400.0, 300.0, 1.05,
804 InteractionButton::Primary,
805 PointerKind::Mouse, InteractionModifiers::default(),
806 );
807 mgr.drain_events();
808
809 mgr.update_pointer_down(
811 &map, 400.0, 300.0, 2.0,
812 InteractionButton::Primary,
813 PointerKind::Mouse, InteractionModifiers::default(),
814 );
815 mgr.update_pointer_up(
816 &mut map, 400.0, 300.0, 2.05,
817 InteractionButton::Primary,
818 PointerKind::Mouse, InteractionModifiers::default(),
819 );
820
821 let events = mgr.drain_events();
822 let kinds: Vec<_> = events.iter().map(|e| e.kind).collect();
823 assert!(
824 !kinds.contains(&InteractionEventKind::DoubleClick),
825 "DoubleClick should NOT fire outside timing window, got {:?}",
826 kinds
827 );
828 }
829
830 #[test]
833 fn drain_events_clears_pending_queue() {
834 let mut map = map_with_point_at_center();
835 let mut mgr = InteractionManager::new();
836
837 mgr.update_pointer_move(
838 &mut map, 400.0, 300.0, 0.0,
839 PointerKind::Mouse, InteractionModifiers::default(),
840 );
841 assert!(mgr.pending_event_count() > 0);
842
843 let events = mgr.drain_events();
844 assert!(!events.is_empty());
845 assert_eq!(mgr.pending_event_count(), 0);
846 }
847
848 #[test]
851 fn pointer_leave_emits_leave_and_clears_hover() {
852 let mut map = map_with_point_at_center();
853 let mut mgr = InteractionManager::new();
854
855 mgr.update_pointer_move(
857 &mut map, 400.0, 300.0, 0.0,
858 PointerKind::Mouse, InteractionModifiers::default(),
859 );
860 mgr.drain_events();
861 assert!(mgr.hovered().is_some() || mgr.prev_hit.is_some());
862
863 mgr.update_pointer_leave(
865 &mut map,
866 PointerKind::Mouse, InteractionModifiers::default(),
867 );
868
869 let events = mgr.drain_events();
870 let kinds: Vec<_> = events.iter().map(|e| e.kind).collect();
871 assert!(
872 kinds.contains(&InteractionEventKind::MouseLeave),
873 "expected MouseLeave on pointer leave, got {:?}",
874 kinds
875 );
876 assert!(mgr.prev_hit.is_none());
877 }
878
879 #[test]
882 fn auto_hover_state_sets_and_clears_feature_state() {
883 let mut map = map_with_point_at_center();
884 let mut mgr = InteractionManager::new();
885
886 mgr.update_pointer_move(
888 &mut map, 400.0, 300.0, 0.0,
889 PointerKind::Mouse, InteractionModifiers::default(),
890 );
891 mgr.drain_events();
892
893 if let Some(id) = mgr.hovered() {
894 let state = map.feature_state(&id.source_id, &id.feature_id);
895 let hover_val = state
896 .and_then(|s| s.get("hover"))
897 .and_then(|v| v.as_bool());
898 assert_eq!(hover_val, Some(true));
899 }
900
901 mgr.update_pointer_move(
903 &mut map, 10.0, 10.0, 0.1,
904 PointerKind::Mouse, InteractionModifiers::default(),
905 );
906 mgr.drain_events();
907
908 assert!(mgr.hovered().is_none());
910 }
911}