1use std::cmp::Reverse;
11
12use fret_core::{MouseButton, ViewportInputEvent, ViewportInputKind};
13use fret_viewport_tooling::{
14 ViewportTool, ViewportToolCx, ViewportToolId, ViewportToolInput, ViewportToolPriority,
15 ViewportToolResult,
16};
17
18#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
19pub enum ViewportToolCoordinateSpace {
20 #[default]
22 TargetPx,
23 ScreenPx,
25}
26
27#[derive(Debug, Clone, Copy)]
28pub struct ViewportToolArbitratorConfig {
29 pub primary_button: MouseButton,
30 pub coordinate_space: ViewportToolCoordinateSpace,
31}
32
33impl Default for ViewportToolArbitratorConfig {
34 fn default() -> Self {
35 Self {
36 primary_button: MouseButton::Left,
37 coordinate_space: ViewportToolCoordinateSpace::TargetPx,
38 }
39 }
40}
41
42#[derive(Default)]
43pub struct ViewportToolArbitrator {
44 pub config: ViewportToolArbitratorConfig,
45 tools: Vec<Box<dyn ViewportTool>>,
46 hot: Option<ViewportToolId>,
47 active: Option<ViewportToolId>,
48 active_button: Option<MouseButton>,
49 active_pointer_id: Option<fret_core::PointerId>,
50}
51
52#[derive(Debug, Default, Clone, Copy, PartialEq, Eq)]
53pub struct ViewportToolRouterState {
54 pub hot: Option<ViewportToolId>,
55 pub active: Option<ViewportToolId>,
56 pub active_button: Option<MouseButton>,
57 pub active_pointer_id: Option<fret_core::PointerId>,
58}
59
60pub struct ViewportToolEntry<T> {
61 pub id: ViewportToolId,
62 pub priority: ViewportToolPriority,
63 pub set_hot: Option<fn(&mut T, bool)>,
64 pub hit_test: fn(&mut T, ViewportToolCx<'_>) -> bool,
65 pub handle_event: fn(&mut T, ViewportToolCx<'_>, bool, bool) -> ViewportToolResult,
66 pub cancel: Option<fn(&mut T)>,
67}
68
69pub fn cancel_active_viewport_tools<T>(
74 state: &mut ViewportToolRouterState,
75 host: &mut T,
76 tools: &mut [ViewportToolEntry<T>],
77) -> bool {
78 let Some(active) = state.active else {
79 return false;
80 };
81
82 if let Some(entry) = tools.iter_mut().find(|t| t.id == active)
83 && let Some(cancel) = entry.cancel
84 {
85 cancel(host);
86 }
87
88 if let Some(hot) = state.hot
89 && let Some(entry) = tools.iter_mut().find(|t| t.id == hot)
90 {
91 call_set_hot(host, entry, false);
92 }
93
94 state.hot = None;
95 state.active = None;
96 state.active_button = None;
97 state.active_pointer_id = None;
98 true
99}
100
101pub fn route_viewport_tools<T>(
102 state: &mut ViewportToolRouterState,
103 config: ViewportToolArbitratorConfig,
104 host: &mut T,
105 event: &ViewportInputEvent,
106 tools: &mut [ViewportToolEntry<T>],
107) -> bool {
108 if tools.is_empty() {
109 state.hot = None;
110 state.active = None;
111 state.active_button = None;
112 state.active_pointer_id = None;
113 return false;
114 }
115
116 tools.sort_by_key(|t| Reverse(t.priority.0));
117
118 if let ViewportInputKind::PointerCancel { .. } = event.kind {
119 let active_pointer_matches = state
120 .active_pointer_id
121 .is_none_or(|p| p == event.pointer_id);
122 if state.active.is_some() && active_pointer_matches {
123 return cancel_active_viewport_tools(state, host, tools);
124 }
125 return false;
126 }
127
128 let input = derive_input(config, state.active_button, event);
129 let cx = ViewportToolCx { event, input };
130
131 if let Some(active) = state.active {
132 if let Some(active_pointer_id) = state.active_pointer_id
133 && active_pointer_id != event.pointer_id
134 {
135 return false;
136 }
137 if state.active_pointer_id.is_none() {
138 state.active_pointer_id = Some(event.pointer_id);
139 }
140
141 force_hot(state, host, tools, active);
142 let handled = if let Some(entry) = tools.iter_mut().find(|t| t.id == active) {
143 (entry.handle_event)(host, cx, true, true).handled
144 } else {
145 false
146 };
147
148 if !cx.input.dragging {
149 state.active = None;
150 state.active_button = None;
151 state.active_pointer_id = None;
152 }
153 return handled;
154 }
155
156 match event.kind {
157 ViewportInputKind::PointerMove { .. }
158 | ViewportInputKind::PointerDown { .. }
159 | ViewportInputKind::Wheel { .. } => update_hot(state, host, tools, cx),
160 ViewportInputKind::PointerUp { .. } => {}
161 ViewportInputKind::PointerCancel { .. } => {}
162 }
163
164 match event.kind {
165 ViewportInputKind::PointerDown { .. } => dispatch_pointer_down(state, host, tools, cx),
166 ViewportInputKind::PointerMove { .. } | ViewportInputKind::PointerUp { .. } => {
167 dispatch_hot_only(state, host, tools, cx)
168 }
169 ViewportInputKind::Wheel { .. } => dispatch_wheel(state, host, tools, cx),
170 ViewportInputKind::PointerCancel { .. } => false,
171 }
172}
173
174fn derive_input(
175 config: ViewportToolArbitratorConfig,
176 active_button: Option<MouseButton>,
177 event: &ViewportInputEvent,
178) -> ViewportToolInput {
179 let primary_button = active_button.unwrap_or(config.primary_button);
180 let mut input = match config.coordinate_space {
181 ViewportToolCoordinateSpace::TargetPx => {
182 ViewportToolInput::from_viewport_input_target_px(event, primary_button)
183 }
184 ViewportToolCoordinateSpace::ScreenPx => {
185 ViewportToolInput::from_viewport_input_screen_px(event, primary_button)
186 }
187 };
188
189 if active_button.is_some() && matches!(event.kind, ViewportInputKind::PointerMove { .. }) {
192 input.dragging = true;
193 }
194
195 input
196}
197
198fn call_set_hot<T>(host: &mut T, entry: &mut ViewportToolEntry<T>, hot: bool) {
199 if let Some(f) = entry.set_hot {
200 f(host, hot);
201 }
202}
203
204fn force_hot<T>(
205 state: &mut ViewportToolRouterState,
206 host: &mut T,
207 tools: &mut [ViewportToolEntry<T>],
208 id: ViewportToolId,
209) {
210 if state.hot == Some(id) {
211 return;
212 }
213
214 if let Some(old) = state.hot
215 && let Some(entry) = tools.iter_mut().find(|t| t.id == old)
216 {
217 call_set_hot(host, entry, false);
218 }
219
220 if let Some(entry) = tools.iter_mut().find(|t| t.id == id) {
221 call_set_hot(host, entry, true);
222 state.hot = Some(id);
223 } else {
224 state.hot = None;
225 }
226}
227
228fn update_hot<T>(
229 state: &mut ViewportToolRouterState,
230 host: &mut T,
231 tools: &mut [ViewportToolEntry<T>],
232 cx: ViewportToolCx<'_>,
233) {
234 let mut next_hot = None;
235 for tool in tools.iter_mut() {
236 if (tool.hit_test)(host, cx) {
237 next_hot = Some(tool.id);
238 break;
239 }
240 }
241
242 if next_hot == state.hot {
243 return;
244 }
245
246 if let Some(old) = state.hot
247 && let Some(entry) = tools.iter_mut().find(|t| t.id == old)
248 {
249 call_set_hot(host, entry, false);
250 }
251 if let Some(next) = next_hot
252 && let Some(entry) = tools.iter_mut().find(|t| t.id == next)
253 {
254 call_set_hot(host, entry, true);
255 state.hot = Some(next);
256 } else {
257 state.hot = None;
258 }
259}
260
261fn dispatch_pointer_down<T>(
262 state: &mut ViewportToolRouterState,
263 host: &mut T,
264 tools: &mut [ViewportToolEntry<T>],
265 cx: ViewportToolCx<'_>,
266) -> bool {
267 let down_button = match cx.event.kind {
268 ViewportInputKind::PointerDown { button, .. } => Some(button),
269 _ => None,
270 };
271 for tool in tools.iter_mut() {
272 let id = tool.id;
273 let hot = state.hot == Some(id);
274 let res = (tool.handle_event)(host, cx, hot, false);
275 if !res.handled {
276 continue;
277 }
278
279 if res.capture {
280 state.active = Some(id);
281 state.active_button = down_button;
282 state.active_pointer_id = Some(cx.event.pointer_id);
283 force_hot(state, host, tools, id);
284 }
285 return true;
286 }
287 false
288}
289
290fn dispatch_hot_only<T>(
291 state: &mut ViewportToolRouterState,
292 host: &mut T,
293 tools: &mut [ViewportToolEntry<T>],
294 cx: ViewportToolCx<'_>,
295) -> bool {
296 let Some(hot) = state.hot else {
297 return false;
298 };
299 let Some(entry) = tools.iter_mut().find(|t| t.id == hot) else {
300 state.hot = None;
301 return false;
302 };
303 (entry.handle_event)(host, cx, true, false).handled
304}
305
306fn dispatch_wheel<T>(
307 state: &mut ViewportToolRouterState,
308 host: &mut T,
309 tools: &mut [ViewportToolEntry<T>],
310 cx: ViewportToolCx<'_>,
311) -> bool {
312 if let Some(hot) = state.hot
313 && let Some(entry) = tools.iter_mut().find(|t| t.id == hot)
314 && (entry.handle_event)(host, cx, true, false).handled
315 {
316 return true;
317 }
318
319 for tool in tools.iter_mut() {
320 let id = tool.id;
321 if Some(id) == state.hot {
322 continue;
323 }
324 let res = (tool.handle_event)(host, cx, false, false);
325 if res.handled {
326 return true;
327 }
328 }
329 false
330}
331
332impl ViewportToolArbitrator {
333 pub fn new(config: ViewportToolArbitratorConfig) -> Self {
334 Self {
335 config,
336 tools: Vec::new(),
337 hot: None,
338 active: None,
339 active_button: None,
340 active_pointer_id: None,
341 }
342 }
343
344 pub fn tools_mut(&mut self) -> &mut [Box<dyn ViewportTool>] {
345 &mut self.tools
346 }
347
348 pub fn hot_tool(&self) -> Option<ViewportToolId> {
349 self.hot
350 }
351
352 pub fn active_tool(&self) -> Option<ViewportToolId> {
353 self.active
354 }
355
356 pub fn set_tools(&mut self, tools: impl IntoIterator<Item = Box<dyn ViewportTool>>) {
357 let mut tools: Vec<Box<dyn ViewportTool>> = tools.into_iter().collect();
358 tools.sort_by_key(|t| Reverse(t.priority().0));
359 self.tools = tools;
360 self.hot = None;
361 self.active = None;
362 self.active_button = None;
363 self.active_pointer_id = None;
364 }
365
366 pub fn clear_tools(&mut self) {
367 self.tools.clear();
368 self.hot = None;
369 self.active = None;
370 self.active_button = None;
371 self.active_pointer_id = None;
372 }
373
374 pub fn cancel_active(&mut self) {
375 if let Some(active) = self.active
376 && let Some(idx) = self.index_of(active)
377 {
378 self.tools[idx].cancel();
379 }
380 self.active = None;
381 self.active_button = None;
382 self.active_pointer_id = None;
383 }
384
385 pub fn cancel_active_and_clear_hot(&mut self) {
387 self.cancel_active();
388 if let Some(hot) = self.hot
389 && let Some(idx) = self.index_of(hot)
390 {
391 self.tools[idx].set_hot(false);
392 }
393 self.hot = None;
394 }
395
396 pub fn handle_event(&mut self, event: &ViewportInputEvent) -> bool {
397 if self.tools.is_empty() {
398 self.hot = None;
399 self.active = None;
400 self.active_button = None;
401 self.active_pointer_id = None;
402 return false;
403 }
404
405 if let ViewportInputKind::PointerCancel { .. } = event.kind {
406 if self
407 .active_pointer_id
408 .is_some_and(|p| p != event.pointer_id)
409 {
410 return false;
411 }
412 if self.active.is_some() {
413 self.cancel_active_and_clear_hot();
414 return true;
415 }
416 return false;
417 }
418
419 let input = self.derive_input(event);
420 let cx = ViewportToolCx { event, input };
421
422 if let Some(active) = self.active {
423 if let Some(active_pointer_id) = self.active_pointer_id
424 && active_pointer_id != event.pointer_id
425 {
426 return false;
427 }
428 if self.active_pointer_id.is_none() {
429 self.active_pointer_id = Some(event.pointer_id);
430 }
431
432 self.force_hot(active);
433 let handled = if let Some(idx) = self.index_of(active) {
434 self.tools[idx].handle_event(cx, true, true).handled
435 } else {
436 false
437 };
438
439 if !cx.input.dragging {
440 self.active = None;
441 self.active_button = None;
442 self.active_pointer_id = None;
443 }
444 return handled;
445 }
446
447 match event.kind {
448 ViewportInputKind::PointerMove { .. } | ViewportInputKind::PointerDown { .. } => {
449 self.update_hot(cx);
450 }
451 ViewportInputKind::PointerUp { .. } => {}
452 ViewportInputKind::Wheel { .. } => {
453 self.update_hot(cx);
454 }
455 ViewportInputKind::PointerCancel { .. } => {}
456 }
457
458 match event.kind {
459 ViewportInputKind::PointerDown { .. } => self.dispatch_pointer_down(cx),
460 ViewportInputKind::PointerMove { .. } => self.dispatch_hot_only(cx),
461 ViewportInputKind::PointerUp { .. } => self.dispatch_hot_only(cx),
462 ViewportInputKind::Wheel { .. } => self.dispatch_wheel(cx),
463 ViewportInputKind::PointerCancel { .. } => false,
464 }
465 }
466
467 fn derive_input(&self, event: &ViewportInputEvent) -> ViewportToolInput {
468 let primary_button = self.active_button.unwrap_or(self.config.primary_button);
469 let mut input = match self.config.coordinate_space {
470 ViewportToolCoordinateSpace::TargetPx => {
471 ViewportToolInput::from_viewport_input_target_px(event, primary_button)
472 }
473 ViewportToolCoordinateSpace::ScreenPx => {
474 ViewportToolInput::from_viewport_input_screen_px(event, primary_button)
475 }
476 };
477
478 if self.active_button.is_some()
481 && matches!(event.kind, ViewportInputKind::PointerMove { .. })
482 {
483 input.dragging = true;
484 }
485
486 input
487 }
488
489 fn index_of(&self, id: ViewportToolId) -> Option<usize> {
490 self.tools.iter().position(|t| t.id() == id)
491 }
492
493 fn force_hot(&mut self, id: ViewportToolId) {
494 if self.hot == Some(id) {
495 return;
496 }
497 if let Some(old) = self.hot
498 && let Some(idx) = self.index_of(old)
499 {
500 self.tools[idx].set_hot(false);
501 }
502 if let Some(idx) = self.index_of(id) {
503 self.tools[idx].set_hot(true);
504 self.hot = Some(id);
505 } else {
506 self.hot = None;
507 }
508 }
509
510 fn update_hot(&mut self, cx: ViewportToolCx<'_>) {
511 let mut next_hot = None;
512 for tool in &mut self.tools {
513 if tool.hit_test(cx) {
514 next_hot = Some(tool.id());
515 break;
516 }
517 }
518
519 if next_hot == self.hot {
520 return;
521 }
522
523 if let Some(old) = self.hot
524 && let Some(idx) = self.index_of(old)
525 {
526 self.tools[idx].set_hot(false);
527 }
528 if let Some(next) = next_hot
529 && let Some(idx) = self.index_of(next)
530 {
531 self.tools[idx].set_hot(true);
532 self.hot = Some(next);
533 } else {
534 self.hot = None;
535 }
536 }
537
538 fn dispatch_pointer_down(&mut self, cx: ViewportToolCx<'_>) -> bool {
539 let down_button = match cx.event.kind {
540 ViewportInputKind::PointerDown { button, .. } => Some(button),
541 _ => None,
542 };
543 for tool in &mut self.tools {
544 let id = tool.id();
545 let hot = self.hot == Some(id);
546 let res = tool.handle_event(cx, hot, false);
547 if !res.handled {
548 continue;
549 }
550
551 if res.capture {
552 self.active = Some(id);
553 self.active_button = down_button;
554 self.active_pointer_id = Some(cx.event.pointer_id);
555 self.force_hot(id);
556 }
557 return true;
558 }
559 false
560 }
561
562 fn dispatch_hot_only(&mut self, cx: ViewportToolCx<'_>) -> bool {
563 let Some(hot) = self.hot else {
564 return false;
565 };
566 let Some(idx) = self.index_of(hot) else {
567 self.hot = None;
568 return false;
569 };
570 self.tools[idx].handle_event(cx, true, false).handled
571 }
572
573 fn dispatch_wheel(&mut self, cx: ViewportToolCx<'_>) -> bool {
574 if let Some(hot) = self.hot
575 && let Some(idx) = self.index_of(hot)
576 && self.tools[idx].handle_event(cx, true, false).handled
577 {
578 return true;
579 }
580
581 for tool in &mut self.tools {
582 let id = tool.id();
583 if Some(id) == self.hot {
584 continue;
585 }
586 let res = tool.handle_event(cx, false, false);
587 if res.handled {
588 return true;
589 }
590 }
591 false
592 }
593}
594
595#[cfg(test)]
596mod tests {
597 use super::*;
598 use fret_core::geometry::{Px, Rect, Size};
599 use fret_core::{AppWindowId, Modifiers, RenderTargetId, ViewportFit, ViewportInputGeometry};
600 use fret_viewport_tooling::{ViewportToolPriority, ViewportToolResult};
601
602 fn dummy_event(kind: ViewportInputKind) -> ViewportInputEvent {
603 ViewportInputEvent {
604 window: AppWindowId::default(),
605 target: RenderTargetId::default(),
606 pointer_id: fret_core::PointerId(0),
607 pointer_type: fret_core::PointerType::Mouse,
608 geometry: ViewportInputGeometry {
609 content_rect_px: Rect::new(
610 fret_core::geometry::Point::new(Px(0.0), Px(0.0)),
611 Size::new(Px(100.0), Px(50.0)),
612 ),
613 draw_rect_px: Rect::new(
614 fret_core::geometry::Point::new(Px(0.0), Px(0.0)),
615 Size::new(Px(100.0), Px(50.0)),
616 ),
617 target_px_size: (1000, 500),
618 fit: ViewportFit::Stretch,
619 pixels_per_point: 2.0,
620 },
621 cursor_px: fret_core::geometry::Point::new(Px(10.0), Px(10.0)),
622 uv: (0.0, 0.0),
623 target_px: (0, 0),
624 kind,
625 }
626 }
627
628 struct TestTool {
629 id: ViewportToolId,
630 prio: i32,
631 hit: bool,
632 down_capture: bool,
633 down_handled: bool,
634 hot: bool,
635 cancelled: bool,
636 calls: Vec<&'static str>,
637 }
638
639 impl TestTool {
640 fn new(id: u64, prio: i32) -> Self {
641 Self {
642 id: ViewportToolId(id),
643 prio,
644 hit: false,
645 down_capture: false,
646 down_handled: false,
647 hot: false,
648 cancelled: false,
649 calls: Vec::new(),
650 }
651 }
652 }
653
654 impl ViewportTool for TestTool {
655 fn id(&self) -> ViewportToolId {
656 self.id
657 }
658
659 fn priority(&self) -> ViewportToolPriority {
660 ViewportToolPriority(self.prio)
661 }
662
663 fn set_hot(&mut self, hot: bool) {
664 self.hot = hot;
665 self.calls.push(if hot { "hot_on" } else { "hot_off" });
666 }
667
668 fn hit_test(&mut self, _cx: ViewportToolCx<'_>) -> bool {
669 self.calls.push("hit_test");
670 self.hit
671 }
672
673 fn handle_event(
674 &mut self,
675 cx: ViewportToolCx<'_>,
676 hot: bool,
677 active: bool,
678 ) -> ViewportToolResult {
679 match cx.event.kind {
680 ViewportInputKind::PointerDown { .. } => {
681 self.calls.push(if hot { "down_hot" } else { "down_cold" });
682 if self.down_handled {
683 if self.down_capture {
684 return ViewportToolResult::handled_and_capture();
685 }
686 return ViewportToolResult::handled();
687 }
688 }
689 ViewportInputKind::PointerMove { .. } => {
690 self.calls.push(if hot { "move_hot" } else { "move_cold" });
691 }
692 ViewportInputKind::PointerUp { .. } => {
693 self.calls
694 .push(if active { "up_active" } else { "up_inactive" });
695 }
696 ViewportInputKind::Wheel { .. } => {
697 self.calls
698 .push(if hot { "wheel_hot" } else { "wheel_cold" });
699 }
700 ViewportInputKind::PointerCancel { .. } => {
701 self.calls.push("pointer_cancel");
702 }
703 }
704 ViewportToolResult::unhandled()
705 }
706
707 fn cancel(&mut self) {
708 self.cancelled = true;
709 self.calls.push("cancel");
710 }
711 }
712
713 #[test]
714 fn picks_hot_by_priority_and_clears_previous() {
715 let mut a = TestTool::new(1, 10);
716 a.hit = false;
717 let mut b = TestTool::new(2, 0);
718 b.hit = true;
719
720 let mut arb = ViewportToolArbitrator::new(Default::default());
721 arb.set_tools(vec![
722 Box::new(a) as Box<dyn ViewportTool>,
723 Box::new(b) as Box<dyn ViewportTool>,
724 ]);
725
726 let handled = arb.handle_event(&dummy_event(ViewportInputKind::PointerMove {
727 buttons: Default::default(),
728 modifiers: Modifiers::default(),
729 }));
730 assert!(!handled);
731 assert_eq!(arb.hot_tool(), Some(ViewportToolId(2)));
732 }
733
734 #[test]
735 fn pointer_down_captures_and_routes_followup_to_active_only() {
736 let mut a = TestTool::new(1, 10);
737 a.hit = true;
738 a.down_handled = true;
739 a.down_capture = true;
740 let mut b = TestTool::new(2, 0);
741 b.hit = true;
742 b.down_handled = true;
743 b.down_capture = true;
744
745 let mut arb = ViewportToolArbitrator::new(Default::default());
746 arb.set_tools(vec![
747 Box::new(a) as Box<dyn ViewportTool>,
748 Box::new(b) as Box<dyn ViewportTool>,
749 ]);
750
751 assert!(
752 arb.handle_event(&dummy_event(ViewportInputKind::PointerDown {
753 button: MouseButton::Left,
754 modifiers: Modifiers::default(),
755 click_count: 1,
756 }))
757 );
758 assert_eq!(arb.active_tool(), Some(ViewportToolId(1)));
759
760 let _ = arb.handle_event(&dummy_event(ViewportInputKind::PointerUp {
761 button: MouseButton::Left,
762 modifiers: Modifiers::default(),
763 is_click: true,
764 click_count: 1,
765 }));
766 assert_eq!(arb.active_tool(), None);
767 }
768
769 #[test]
770 fn cancel_active_and_clear_hot_resets_state() {
771 let mut a = TestTool::new(1, 10);
772 a.hit = true;
773 a.down_handled = true;
774 a.down_capture = true;
775
776 let mut arb = ViewportToolArbitrator::new(Default::default());
777 arb.set_tools(vec![Box::new(a) as Box<dyn ViewportTool>]);
778
779 assert!(
780 arb.handle_event(&dummy_event(ViewportInputKind::PointerDown {
781 button: MouseButton::Left,
782 modifiers: Modifiers::default(),
783 click_count: 1,
784 }))
785 );
786 assert_eq!(arb.active_tool(), Some(ViewportToolId(1)));
787 assert_eq!(arb.hot_tool(), Some(ViewportToolId(1)));
788
789 arb.cancel_active_and_clear_hot();
790 assert_eq!(arb.active_tool(), None);
791 assert_eq!(arb.hot_tool(), None);
792 }
793
794 #[test]
795 fn callback_router_cancel_clears_active_and_hot() {
796 #[derive(Default)]
797 struct Host {
798 cancelled: bool,
799 hot: bool,
800 }
801
802 fn set_hot(host: &mut Host, hot: bool) {
803 host.hot = hot;
804 }
805
806 fn hit_test(_host: &mut Host, _cx: ViewportToolCx<'_>) -> bool {
807 false
808 }
809
810 fn handle_event(
811 _host: &mut Host,
812 _cx: ViewportToolCx<'_>,
813 _hot: bool,
814 _active: bool,
815 ) -> ViewportToolResult {
816 ViewportToolResult::unhandled()
817 }
818
819 fn cancel(host: &mut Host) {
820 host.cancelled = true;
821 }
822
823 let mut host = Host::default();
824 let mut state = ViewportToolRouterState {
825 hot: Some(ViewportToolId(1)),
826 active: Some(ViewportToolId(1)),
827 active_button: Some(MouseButton::Left),
828 active_pointer_id: Some(fret_core::PointerId(0)),
829 };
830 let mut tools = [ViewportToolEntry {
831 id: ViewportToolId(1),
832 priority: ViewportToolPriority(0),
833 set_hot: Some(set_hot),
834 hit_test,
835 handle_event,
836 cancel: Some(cancel),
837 }];
838
839 let cancelled = cancel_active_viewport_tools(&mut state, &mut host, &mut tools);
840 assert!(cancelled);
841 assert!(host.cancelled);
842 assert!(!host.hot);
843 assert_eq!(state.hot, None);
844 assert_eq!(state.active, None);
845 assert_eq!(state.active_button, None);
846 assert_eq!(state.active_pointer_id, None);
847 }
848
849 #[test]
850 fn callback_router_active_tool_is_pointer_local() {
851 #[derive(Default)]
852 struct Host {
853 moves_active: u32,
854 }
855
856 fn set_hot(_host: &mut Host, _hot: bool) {}
857 fn hit_test(_host: &mut Host, _cx: ViewportToolCx<'_>) -> bool {
858 true
859 }
860 fn handle_event(
861 host: &mut Host,
862 cx: ViewportToolCx<'_>,
863 _hot: bool,
864 active: bool,
865 ) -> ViewportToolResult {
866 match cx.event.kind {
867 ViewportInputKind::PointerDown { .. } => ViewportToolResult::handled_and_capture(),
868 ViewportInputKind::PointerMove { .. } if active => {
869 host.moves_active += 1;
870 ViewportToolResult::handled()
871 }
872 _ => ViewportToolResult::unhandled(),
873 }
874 }
875
876 let mut host = Host::default();
877 let mut state = ViewportToolRouterState::default();
878 let mut tools = [ViewportToolEntry {
879 id: ViewportToolId(1),
880 priority: ViewportToolPriority(0),
881 set_hot: Some(set_hot),
882 hit_test,
883 handle_event,
884 cancel: None,
885 }];
886
887 let mut down = dummy_event(ViewportInputKind::PointerDown {
888 button: MouseButton::Left,
889 modifiers: Modifiers::default(),
890 click_count: 1,
891 });
892 down.pointer_id = fret_core::PointerId(0);
893 assert!(route_viewport_tools(
894 &mut state,
895 Default::default(),
896 &mut host,
897 &down,
898 &mut tools
899 ));
900 assert_eq!(state.active, Some(ViewportToolId(1)));
901 assert_eq!(state.active_pointer_id, Some(fret_core::PointerId(0)));
902
903 let mut move_other = dummy_event(ViewportInputKind::PointerMove {
904 buttons: fret_core::MouseButtons::default(),
905 modifiers: Modifiers::default(),
906 });
907 move_other.pointer_id = fret_core::PointerId(1);
908 assert!(!route_viewport_tools(
909 &mut state,
910 Default::default(),
911 &mut host,
912 &move_other,
913 &mut tools
914 ));
915 assert_eq!(host.moves_active, 0);
916 assert_eq!(state.active, Some(ViewportToolId(1)));
917
918 let mut move_active = move_other;
919 move_active.pointer_id = fret_core::PointerId(0);
920 assert!(route_viewport_tools(
921 &mut state,
922 Default::default(),
923 &mut host,
924 &move_active,
925 &mut tools
926 ));
927 assert_eq!(host.moves_active, 1);
928 }
929
930 #[test]
931 fn arbitrator_active_tool_is_pointer_local() {
932 let mut a = TestTool::new(1, 0);
933 a.hit = true;
934 a.down_handled = true;
935 a.down_capture = true;
936
937 let mut arb = ViewportToolArbitrator::new(Default::default());
938 arb.set_tools(vec![Box::new(a) as Box<dyn ViewportTool>]);
939
940 let mut down = dummy_event(ViewportInputKind::PointerDown {
941 button: MouseButton::Left,
942 modifiers: Modifiers::default(),
943 click_count: 1,
944 });
945 down.pointer_id = fret_core::PointerId(0);
946 assert!(arb.handle_event(&down));
947 assert_eq!(arb.active_tool(), Some(ViewportToolId(1)));
948
949 let mut move_other = dummy_event(ViewportInputKind::PointerMove {
950 buttons: fret_core::MouseButtons::default(),
951 modifiers: Modifiers::default(),
952 });
953 move_other.pointer_id = fret_core::PointerId(1);
954 assert!(!arb.handle_event(&move_other));
955 assert_eq!(arb.active_tool(), Some(ViewportToolId(1)));
956 }
957}