1use crate::{
2 Scalar,
3 application::Application,
4 interactive::InteractionsEngine,
5 messenger::MessageData,
6 widget::{
7 WidgetId,
8 component::{
9 RelativeLayoutListenerSignal, ResizeListenerSignal,
10 interactive::navigation::{NavDirection, NavJump, NavScroll, NavSignal, NavType},
11 },
12 unit::WidgetUnit,
13 utils::{Rect, Vec2, lerp},
14 },
15};
16use std::collections::{HashMap, HashSet, VecDeque};
17
18#[derive(Debug, Copy, Clone, PartialEq, Eq)]
19pub enum PointerButton {
20 Trigger,
21 Context,
22}
23
24#[derive(Debug, Default, Clone)]
25pub enum Interaction {
26 #[default]
27 None,
28 Navigate(NavSignal),
29 PointerDown(PointerButton, Vec2),
30 PointerUp(PointerButton, Vec2),
31 PointerMove(Vec2),
32}
33
34impl Interaction {
35 pub fn is_none(&self) -> bool {
36 matches!(self, Self::None)
37 }
38
39 #[inline]
40 pub fn is_some(&self) -> bool {
41 !self.is_none()
42 }
43}
44
45#[derive(Debug, Default, Copy, Clone)]
46pub struct DefaultInteractionsEngineResult {
47 pub captured_pointer_location: bool,
48 pub captured_pointer_action: bool,
49 pub captured_text_change: bool,
50}
51
52impl DefaultInteractionsEngineResult {
53 #[inline]
54 pub fn is_any(&self) -> bool {
55 self.captured_pointer_action || self.captured_pointer_location || self.captured_text_change
56 }
57
58 #[inline]
59 pub fn is_none(&self) -> bool {
60 !self.is_any()
61 }
62}
63
64#[derive(Debug, Default)]
66pub struct DefaultInteractionsEngine {
67 pub deselect_when_no_button_found: bool,
68 pub unfocus_when_selection_change: bool,
69 resize_listeners: HashMap<WidgetId, Vec2>,
70 relative_layout_listeners: HashMap<WidgetId, (WidgetId, Vec2, Rect)>,
71 interactions_queue: VecDeque<Interaction>,
72 containers: HashMap<WidgetId, HashSet<WidgetId>>,
73 items_owners: HashMap<WidgetId, WidgetId>,
74 buttons: HashSet<WidgetId>,
75 text_inputs: HashSet<WidgetId>,
76 scroll_views: HashSet<WidgetId>,
77 scroll_view_contents: HashSet<WidgetId>,
78 tracking: HashMap<WidgetId, WidgetId>,
79 selected_chain: Vec<WidgetId>,
80 locked_widget: Option<WidgetId>,
81 focused_text_input: Option<WidgetId>,
82 sorted_items_ids: Vec<WidgetId>,
83}
84
85impl DefaultInteractionsEngine {
86 #[allow(clippy::too_many_arguments)]
87 pub fn with_capacity(
88 resize_listeners: usize,
89 relative_layout_listeners: usize,
90 interactions_queue: usize,
91 containers: usize,
92 buttons: usize,
93 text_inputs: usize,
94 scroll_views: usize,
95 tracking: usize,
96 selected_chain: usize,
97 ) -> Self {
98 Self {
99 deselect_when_no_button_found: false,
100 unfocus_when_selection_change: true,
101 resize_listeners: HashMap::with_capacity(resize_listeners),
102 relative_layout_listeners: HashMap::with_capacity(relative_layout_listeners),
103 interactions_queue: VecDeque::with_capacity(interactions_queue),
104 containers: HashMap::with_capacity(containers),
105 items_owners: Default::default(),
106 buttons: HashSet::with_capacity(buttons),
107 text_inputs: HashSet::with_capacity(text_inputs),
108 scroll_views: HashSet::with_capacity(scroll_views),
109 scroll_view_contents: HashSet::with_capacity(scroll_views),
110 tracking: HashMap::with_capacity(tracking),
111 selected_chain: Vec::with_capacity(selected_chain),
112 locked_widget: None,
113 focused_text_input: None,
114 sorted_items_ids: vec![],
115 }
116 }
117
118 pub fn locked_widget(&self) -> Option<&WidgetId> {
119 self.locked_widget.as_ref()
120 }
121
122 pub fn selected_chain(&self) -> &[WidgetId] {
123 &self.selected_chain
124 }
125
126 pub fn selected_item(&self) -> Option<&WidgetId> {
127 self.selected_chain.last()
128 }
129
130 pub fn selected_container(&self) -> Option<&WidgetId> {
131 self.selected_chain
132 .iter()
133 .rev()
134 .find(|id| self.containers.contains_key(id))
135 }
136
137 pub fn selected_button(&self) -> Option<&WidgetId> {
138 self.selected_chain
139 .iter()
140 .rev()
141 .find(|id| self.buttons.contains(id))
142 }
143
144 pub fn selected_scroll_view(&self) -> Option<&WidgetId> {
145 self.selected_chain
146 .iter()
147 .rev()
148 .find(|id| self.scroll_views.contains(id))
149 }
150
151 pub fn selected_scroll_view_content(&self) -> Option<&WidgetId> {
152 self.selected_chain
153 .iter()
154 .rev()
155 .find(|id| self.scroll_view_contents.contains(id))
156 }
157
158 pub fn focused_text_input(&self) -> Option<&WidgetId> {
159 self.focused_text_input.as_ref()
160 }
161
162 pub fn interact(&mut self, interaction: Interaction) {
163 if interaction.is_some() {
164 self.interactions_queue.push_back(interaction);
165 }
166 }
167
168 pub fn clear_queue(&mut self, put_unselect: bool) {
169 self.interactions_queue.clear();
170 if put_unselect {
171 self.interactions_queue
172 .push_back(Interaction::Navigate(NavSignal::Unselect));
173 }
174 }
175
176 fn cache_sorted_items_ids(&mut self, app: &Application) {
177 self.sorted_items_ids = Vec::with_capacity(self.items_owners.len());
178 self.cache_sorted_items_ids_inner(app.rendered_tree());
179 }
180
181 fn cache_sorted_items_ids_inner(&mut self, unit: &WidgetUnit) {
182 if let Some(data) = unit.as_data() {
183 self.sorted_items_ids.push(data.id().to_owned());
184 }
185 match unit {
186 WidgetUnit::AreaBox(unit) => {
187 self.cache_sorted_items_ids_inner(&unit.slot);
188 }
189 WidgetUnit::ContentBox(unit) => {
190 for item in &unit.items {
191 self.cache_sorted_items_ids_inner(&item.slot);
192 }
193 }
194 WidgetUnit::FlexBox(unit) => {
195 if unit.direction.is_order_ascending() {
196 for item in &unit.items {
197 self.cache_sorted_items_ids_inner(&item.slot);
198 }
199 } else {
200 for item in unit.items.iter().rev() {
201 self.cache_sorted_items_ids_inner(&item.slot);
202 }
203 }
204 }
205 WidgetUnit::GridBox(unit) => {
206 for item in &unit.items {
207 self.cache_sorted_items_ids_inner(&item.slot);
208 }
209 }
210 WidgetUnit::SizeBox(unit) => {
211 self.cache_sorted_items_ids_inner(&unit.slot);
212 }
213 _ => {}
214 }
215 }
216
217 pub fn select_item(&mut self, app: &mut Application, id: Option<WidgetId>) -> bool {
218 if self.locked_widget.is_some() || self.selected_chain.last() == id.as_ref() {
219 return false;
220 }
221 if let Some(id) = &id
222 && self.containers.contains_key(id)
223 {
224 app.send_message(id, NavSignal::Select(id.to_owned().into()));
225 }
226 match (self.selected_chain.is_empty(), id) {
227 (false, None) => {
228 for id in std::mem::take(&mut self.selected_chain).iter().rev() {
229 app.send_message(id, NavSignal::Unselect);
230 }
231 }
232 (false, Some(mut id)) => {
233 if self.unfocus_when_selection_change {
234 self.focus_text_input(app, None);
235 }
236 let mut chain = Vec::with_capacity(self.selected_chain.len());
237 while let Some(owner) = self.items_owners.get(&id) {
238 if !chain.contains(&id) {
239 chain.push(id.to_owned());
240 }
241 if !chain.contains(owner) {
242 chain.push(owner.to_owned());
243 }
244 id = owner.to_owned();
245 }
246 chain.reverse();
247 let mut index = 0;
248 for (a, b) in self.selected_chain.iter().zip(chain.iter()) {
249 if a != b {
250 break;
251 }
252 index += 1;
253 }
254 for id in &self.selected_chain[index..] {
255 app.send_message(id, NavSignal::Unselect);
256 }
257 for id in &chain[index..] {
258 app.send_message(id, NavSignal::Select(().into()));
259 }
260 self.selected_chain = chain;
261 }
262 (true, Some(mut id)) => {
263 if self.unfocus_when_selection_change {
264 self.focus_text_input(app, None);
265 }
266 self.selected_chain.clear();
267 while let Some(owner) = self.items_owners.get(&id) {
268 if !self.selected_chain.contains(&id) {
269 self.selected_chain.push(id.to_owned());
270 }
271 if !self.selected_chain.contains(owner) {
272 self.selected_chain.push(owner.to_owned());
273 }
274 id = owner.to_owned();
275 }
276 self.selected_chain.reverse();
277 for id in &self.selected_chain {
278 app.send_message(id, NavSignal::Select(().into()));
279 }
280 }
281 (true, None) => {}
282 }
283 true
284 }
285
286 pub fn focus_text_input(&mut self, app: &mut Application, id: Option<WidgetId>) {
287 if self.focused_text_input == id {
288 return;
289 }
290 if let Some(focused) = &self.focused_text_input {
291 app.send_message(focused, NavSignal::FocusTextInput(().into()));
292 }
293 self.focused_text_input = None;
294 if let Some(id) = id
295 && self.text_inputs.contains(&id)
296 {
297 app.send_message(&id, NavSignal::FocusTextInput(id.to_owned().into()));
298 self.focused_text_input = Some(id);
299 }
300 }
301
302 pub fn send_to_selected_item<T>(&self, app: &mut Application, data: T) -> bool
303 where
304 T: 'static + MessageData,
305 {
306 if let Some(id) = self.selected_item() {
307 app.send_message(id, data);
308 return true;
309 }
310 false
311 }
312
313 pub fn send_to_selected_container<T>(&self, app: &mut Application, data: T) -> bool
314 where
315 T: 'static + MessageData,
316 {
317 if let Some(id) = self.selected_container() {
318 app.send_message(id, data);
319 return true;
320 }
321 false
322 }
323
324 pub fn send_to_selected_button<T>(&self, app: &mut Application, data: T) -> bool
325 where
326 T: 'static + MessageData,
327 {
328 if let Some(id) = self.selected_button() {
329 app.send_message(id, data);
330 return true;
331 }
332 false
333 }
334
335 pub fn send_to_focused_text_input<T>(&self, app: &mut Application, data: T) -> bool
336 where
337 T: 'static + MessageData,
338 {
339 if let Some(id) = self.focused_text_input() {
340 app.send_message(id, data);
341 return true;
342 }
343 false
344 }
345
346 fn find_scroll_view_content(&self, id: &WidgetId) -> Option<WidgetId> {
347 if self.scroll_views.contains(id)
348 && let Some(items) = self.containers.get(id)
349 {
350 for item in items {
351 if self.scroll_view_contents.contains(item) {
352 return Some(item.to_owned());
353 }
354 }
355 }
356 None
357 }
358
359 fn get_item_point(app: &Application, id: &WidgetId) -> Option<Vec2> {
360 if let Some(layout) = app.layout_data().items.get(id) {
361 let x = (layout.ui_space.left + layout.ui_space.right) * 0.5;
362 let y = (layout.ui_space.top + layout.ui_space.bottom) * 0.5;
363 Some(Vec2 { x, y })
364 } else {
365 None
366 }
367 }
368
369 fn get_selected_item_point(&self, app: &Application) -> Option<Vec2> {
370 Self::get_item_point(app, self.selected_item()?)
371 }
372
373 fn get_closest_item_point(app: &Application, id: &WidgetId, mut point: Vec2) -> Option<Vec2> {
374 if let Some(layout) = app.layout_data().items.get(id) {
375 point.x = point.x.max(layout.ui_space.left).min(layout.ui_space.right);
376 point.y = point.y.max(layout.ui_space.top).min(layout.ui_space.bottom);
377 Some(point)
378 } else {
379 None
380 }
381 }
382
383 fn find_item_closest_to_point(
384 app: &Application,
385 point: Vec2,
386 items: &HashSet<WidgetId>,
387 ) -> Option<WidgetId> {
388 items
389 .iter()
390 .filter_map(|id| {
391 Self::get_closest_item_point(app, id, point).map(|p| {
392 let dx = p.x - point.x;
393 let dy = p.y - point.y;
394 (id, dx * dx + dy * dy)
395 })
396 })
397 .min_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
398 .map(|m| m.0.to_owned())
399 }
400
401 fn find_item_closest_to_direction(
402 app: &Application,
403 point: Vec2,
404 direction: NavDirection,
405 items: &HashSet<WidgetId>,
406 ) -> Option<WidgetId> {
407 let dir = match direction {
408 NavDirection::Up => Vec2 { x: 0.0, y: -1.0 },
409 NavDirection::Down => Vec2 { x: 0.0, y: 1.0 },
410 NavDirection::Left => Vec2 { x: -1.0, y: 0.0 },
411 NavDirection::Right => Vec2 { x: 1.0, y: 0.0 },
412 _ => return None,
413 };
414 items
415 .iter()
416 .filter_map(|id| {
417 Self::get_closest_item_point(app, id, point).map(|p| {
418 let dx = p.x - point.x;
419 let dy = p.y - point.y;
420 let len = (dx * dx + dy * dy).sqrt();
421 let dot = dx / len * dir.x + dy / len * dir.y;
422 let f = if len > 0.0 { dot / len } else { 0.0 };
423 (id, f)
424 })
425 })
426 .filter(|m| m.1 > 1.0e-6)
427 .max_by(|a, b| a.1.partial_cmp(&b.1).unwrap())
428 .map(|m| m.0.to_owned())
429 }
430
431 fn find_first_item(&self, items: &HashSet<WidgetId>) -> Option<WidgetId> {
432 self.sorted_items_ids
433 .iter()
434 .find(|id| items.contains(id))
435 .cloned()
436 }
437
438 fn find_last_item(&self, items: &HashSet<WidgetId>) -> Option<WidgetId> {
439 self.sorted_items_ids
440 .iter()
441 .rev()
442 .find(|id| items.contains(id))
443 .cloned()
444 }
445
446 fn find_prev_item(&self, id: &WidgetId, items: &HashSet<WidgetId>) -> Option<WidgetId> {
447 let mut found = false;
448 self.sorted_items_ids
449 .iter()
450 .rev()
451 .find(|i| {
452 if found {
453 if items.contains(i) {
454 return true;
455 }
456 } else if i == &id {
457 found = true;
458 }
459 false
460 })
461 .cloned()
462 }
463
464 fn find_next_item(&self, id: &WidgetId, items: &HashSet<WidgetId>) -> Option<WidgetId> {
465 let mut found = false;
466 self.sorted_items_ids
467 .iter()
468 .find(|i| {
469 if found {
470 if items.contains(i) {
471 return true;
472 }
473 } else if i == &id {
474 found = true;
475 }
476 false
477 })
478 .cloned()
479 }
480
481 fn jump(&mut self, app: &mut Application, id: &WidgetId, data: NavJump) {
483 if let Some(items) = self.containers.get(id) {
484 match data {
485 NavJump::First => {
486 if let Some(id) = self.find_first_item(items) {
487 self.select_item(app, Some(id));
488 }
489 }
490 NavJump::Last => {
491 if let Some(id) = self.find_last_item(items) {
492 self.select_item(app, Some(id));
493 }
494 }
495 NavJump::TopLeft => {
496 if let Some(layout) = app.layout_data().items.get(id) {
497 let point = Vec2 {
498 x: layout.ui_space.left,
499 y: layout.ui_space.top,
500 };
501 if let Some(id) = Self::find_item_closest_to_point(app, point, items) {
502 self.select_item(app, Some(id));
503 }
504 }
505 }
506 NavJump::TopRight => {
507 if let Some(layout) = app.layout_data().items.get(id) {
508 let point = Vec2 {
509 x: layout.ui_space.right,
510 y: layout.ui_space.top,
511 };
512 if let Some(id) = Self::find_item_closest_to_point(app, point, items) {
513 self.select_item(app, Some(id));
514 }
515 }
516 }
517 NavJump::BottomLeft => {
518 if let Some(layout) = app.layout_data().items.get(id) {
519 let point = Vec2 {
520 x: layout.ui_space.left,
521 y: layout.ui_space.bottom,
522 };
523 if let Some(id) = Self::find_item_closest_to_point(app, point, items) {
524 self.select_item(app, Some(id));
525 }
526 }
527 }
528 NavJump::BottomRight => {
529 if let Some(layout) = app.layout_data().items.get(id) {
530 let point = Vec2 {
531 x: layout.ui_space.right,
532 y: layout.ui_space.bottom,
533 };
534 if let Some(id) = Self::find_item_closest_to_point(app, point, items) {
535 self.select_item(app, Some(id));
536 }
537 }
538 }
539 NavJump::MiddleCenter => {
540 if let Some(layout) = app.layout_data().items.get(id) {
541 let point = Vec2 {
542 x: (layout.ui_space.left + layout.ui_space.right) * 0.5,
543 y: (layout.ui_space.top + layout.ui_space.bottom) * 0.5,
544 };
545 if let Some(id) = Self::find_item_closest_to_point(app, point, items) {
546 self.select_item(app, Some(id));
547 }
548 }
549 }
550 NavJump::Loop(direction) => match direction {
551 NavDirection::Up
552 | NavDirection::Down
553 | NavDirection::Left
554 | NavDirection::Right => {
555 if let Some(point) = self.get_selected_item_point(app) {
556 if let Some(id) =
557 Self::find_item_closest_to_direction(app, point, direction, items)
558 {
559 self.select_item(app, Some(id));
560 } else if let Some(id) = self.items_owners.get(id) {
561 match direction {
562 NavDirection::Up => app.send_message(id, NavSignal::Up),
563 NavDirection::Down => app.send_message(id, NavSignal::Down),
564 NavDirection::Left => app.send_message(id, NavSignal::Left),
565 NavDirection::Right => app.send_message(id, NavSignal::Right),
566 _ => {}
567 }
568 }
569 }
570 }
571 NavDirection::Prev => {
572 if let Some(id) = self.selected_chain.last() {
573 if let Some(id) = self.find_prev_item(id, items) {
574 self.select_item(app, Some(id));
575 } else if let Some(id) = self.find_last_item(items) {
576 self.select_item(app, Some(id));
577 } else if let Some(id) = self.items_owners.get(id) {
578 app.send_message(id, NavSignal::Prev);
579 }
580 }
581 }
582 NavDirection::Next => {
583 if let Some(id) = self.selected_chain.last() {
584 if let Some(id) = self.find_next_item(id, items) {
585 self.select_item(app, Some(id));
586 } else if let Some(id) = self.find_first_item(items) {
587 self.select_item(app, Some(id));
588 } else if let Some(id) = self.items_owners.get(id) {
589 app.send_message(id, NavSignal::Next);
590 }
591 }
592 }
593 _ => {}
594 },
595 NavJump::Escape(direction, idref) => match direction {
596 NavDirection::Up
597 | NavDirection::Down
598 | NavDirection::Left
599 | NavDirection::Right => {
600 if let Some(point) = self.get_selected_item_point(app) {
601 if let Some(id) =
602 Self::find_item_closest_to_direction(app, point, direction, items)
603 {
604 self.select_item(app, Some(id));
605 } else if let Some(id) = idref.read() {
606 self.select_item(app, Some(id));
607 } else if let Some(id) = self.items_owners.get(id) {
608 match direction {
609 NavDirection::Up => app.send_message(id, NavSignal::Up),
610 NavDirection::Down => app.send_message(id, NavSignal::Down),
611 NavDirection::Left => app.send_message(id, NavSignal::Left),
612 NavDirection::Right => app.send_message(id, NavSignal::Right),
613 _ => {}
614 }
615 }
616 }
617 }
618 NavDirection::Prev => {
619 if let Some(id) = self.selected_chain.last() {
620 if let Some(id) = self.find_prev_item(id, items) {
621 self.select_item(app, Some(id));
622 } else if let Some(id) = idref.read() {
623 self.select_item(app, Some(id));
624 } else if let Some(id) = self.items_owners.get(id) {
625 app.send_message(id, NavSignal::Prev);
626 }
627 }
628 }
629 NavDirection::Next => {
630 if let Some(id) = self.selected_chain.last() {
631 if let Some(id) = self.find_next_item(id, items) {
632 self.select_item(app, Some(id));
633 } else if let Some(id) = idref.read() {
634 self.select_item(app, Some(id));
635 } else if let Some(id) = self.items_owners.get(id) {
636 app.send_message(id, NavSignal::Next);
637 }
638 }
639 }
640 _ => {}
641 },
642 NavJump::Scroll(scroll) => {
643 fn factor(
644 this: &DefaultInteractionsEngine,
645 app: &mut Application,
646 id: &WidgetId,
647 v: Vec2,
648 relative: bool,
649 ) {
650 if let Some(oid) = this.find_scroll_view_content(id) {
651 let a = app.layout_data().find_or_ui_space(oid.path());
652 let b = app.layout_data().find_or_ui_space(id.path());
653 let asize = a.local_space.size();
654 let bsize = b.local_space.size();
655 let f = Vec2 {
656 x: if bsize.x > 0.0 {
657 asize.x / bsize.x
658 } else {
659 0.0
660 },
661 y: if bsize.y > 0.0 {
662 asize.y / bsize.y
663 } else {
664 0.0
665 },
666 };
667 app.send_message(
668 id,
669 NavSignal::Jump(NavJump::Scroll(NavScroll::Change(v, f, relative))),
670 );
671 }
672 }
673
674 fn units(
675 this: &DefaultInteractionsEngine,
676 app: &mut Application,
677 id: &WidgetId,
678 v: Vec2,
679 relative: bool,
680 ) {
681 if let Some(oid) = this.find_scroll_view_content(id) {
682 let a = app.layout_data().find_or_ui_space(oid.path());
683 let b = app.layout_data().find_or_ui_space(id.path());
684 let asize = a.local_space.size();
685 let bsize = b.local_space.size();
686 let dsize = Vec2 {
687 x: asize.x - bsize.x,
688 y: asize.y - bsize.y,
689 };
690 let v = Vec2 {
691 x: if dsize.x > 0.0 { v.x / dsize.x } else { 0.0 },
692 y: if dsize.y > 0.0 { v.y / dsize.y } else { 0.0 },
693 };
694 let f = Vec2 {
695 x: if bsize.x > 0.0 {
696 asize.x / bsize.x
697 } else {
698 0.0
699 },
700 y: if bsize.y > 0.0 {
701 asize.y / bsize.y
702 } else {
703 0.0
704 },
705 };
706 app.send_message(
707 id,
708 NavSignal::Jump(NavJump::Scroll(NavScroll::Change(v, f, relative))),
709 );
710 }
711 }
712
713 match scroll {
714 NavScroll::Factor(v, relative) => factor(self, app, id, v, relative),
715 NavScroll::DirectFactor(idref, v, relative) => {
716 if let Some(id) = idref.read() {
717 factor(self, app, &id, v, relative);
718 }
719 }
720 NavScroll::Units(v, relative) => units(self, app, id, v, relative),
721 NavScroll::DirectUnits(idref, v, relative) => {
722 if let Some(id) = idref.read() {
723 units(self, app, &id, v, relative);
724 }
725 }
726 NavScroll::Widget(idref, anchor) => {
727 if let (Some(wid), Some(oid)) =
728 (idref.read(), self.find_scroll_view_content(id))
729 && let Some(rect) = app.layout_data().rect_relative_to(&wid, &oid)
730 {
731 let aitem = app.layout_data().find_or_ui_space(oid.path());
732 let bitem = app.layout_data().find_or_ui_space(id.path());
733 let x = lerp(rect.left, rect.right, anchor.x);
734 let y = lerp(rect.top, rect.bottom, anchor.y);
735 let asize = aitem.local_space.size();
736 let bsize = bitem.local_space.size();
737 let v = Vec2 {
738 x: if asize.x > 0.0 { x / asize.x } else { 0.0 },
739 y: if asize.y > 0.0 { y / asize.y } else { 0.0 },
740 };
741 let f = Vec2 {
742 x: if bsize.x > 0.0 {
743 asize.x / bsize.x
744 } else {
745 0.0
746 },
747 y: if bsize.y > 0.0 {
748 asize.y / bsize.y
749 } else {
750 0.0
751 },
752 };
753 app.send_message(
754 id,
755 NavSignal::Jump(NavJump::Scroll(NavScroll::Change(
756 v, f, false,
757 ))),
758 );
759 }
760 }
761 _ => {}
762 }
763 }
764 }
765 }
766 }
767
768 pub fn find_button(&self, app: &Application, x: Scalar, y: Scalar) -> Option<(WidgetId, Vec2)> {
769 self.find_button_inner(app, x, y, app.rendered_tree(), app.layout_data().ui_space)
770 }
771
772 fn find_button_inner(
773 &self,
774 app: &Application,
775 x: Scalar,
776 y: Scalar,
777 unit: &WidgetUnit,
778 mut clip: Rect,
779 ) -> Option<(WidgetId, Vec2)> {
780 if x < clip.left || x > clip.right || y < clip.top || y > clip.bottom {
781 return None;
782 }
783 let mut result = None;
784 if let Some(data) = unit.as_data()
785 && self.buttons.contains(data.id())
786 && let Some(layout) = app.layout_data().items.get(data.id())
787 {
788 let rect = layout.ui_space;
789 if x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom {
790 let size = rect.size();
791 let pos = Vec2 {
792 x: if size.x > 0.0 {
793 (x - rect.left) / size.x
794 } else {
795 0.0
796 },
797 y: if size.y > 0.0 {
798 (y - rect.top) / size.y
799 } else {
800 0.0
801 },
802 };
803 result = Some((data.id().to_owned(), pos));
804 }
805 }
806 match unit {
807 WidgetUnit::AreaBox(unit) => {
808 if let Some(id) = self.find_button_inner(app, x, y, &unit.slot, clip) {
809 result = Some(id);
810 }
811 }
812 WidgetUnit::ContentBox(unit) => {
813 if unit.clipping
814 && let Some(item) = app.layout_data().items.get(&unit.id)
815 {
816 clip = item.ui_space;
817 }
818 for item in &unit.items {
819 if let Some(id) = self.find_button_inner(app, x, y, &item.slot, clip) {
820 result = Some(id);
821 }
822 }
823 }
824 WidgetUnit::FlexBox(unit) => {
825 for item in &unit.items {
826 if let Some(id) = self.find_button_inner(app, x, y, &item.slot, clip) {
827 result = Some(id);
828 }
829 }
830 }
831 WidgetUnit::GridBox(unit) => {
832 for item in &unit.items {
833 if let Some(id) = self.find_button_inner(app, x, y, &item.slot, clip) {
834 result = Some(id);
835 }
836 }
837 }
838 WidgetUnit::SizeBox(unit) => {
839 if let Some(id) = self.find_button_inner(app, x, y, &unit.slot, clip) {
840 result = Some(id);
841 }
842 }
843 _ => {}
844 }
845 result
846 }
847
848 pub fn does_hover_widget(&self, app: &Application, x: Scalar, y: Scalar) -> bool {
849 Self::does_hover_widget_inner(app, x, y, app.rendered_tree())
850 }
851
852 fn does_hover_widget_inner(app: &Application, x: Scalar, y: Scalar, unit: &WidgetUnit) -> bool {
853 if let Some(data) = unit.as_data()
854 && let Some(layout) = app.layout_data().items.get(data.id())
855 {
856 let rect = layout.ui_space;
857 if x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom {
858 return true;
859 }
860 }
861 match unit {
862 WidgetUnit::AreaBox(unit) => {
863 if Self::does_hover_widget_inner(app, x, y, &unit.slot) {
864 return true;
865 }
866 }
867 WidgetUnit::ContentBox(unit) => {
868 for item in &unit.items {
869 if Self::does_hover_widget_inner(app, x, y, &item.slot) {
870 return true;
871 }
872 }
873 }
874 WidgetUnit::FlexBox(unit) => {
875 for item in &unit.items {
876 if Self::does_hover_widget_inner(app, x, y, &item.slot) {
877 return true;
878 }
879 }
880 }
881 WidgetUnit::GridBox(unit) => {
882 for item in &unit.items {
883 if Self::does_hover_widget_inner(app, x, y, &item.slot) {
884 return true;
885 }
886 }
887 }
888 WidgetUnit::SizeBox(unit) => {
889 if Self::does_hover_widget_inner(app, x, y, &unit.slot) {
890 return true;
891 }
892 }
893 _ => {}
894 }
895 false
896 }
897}
898
899impl InteractionsEngine<DefaultInteractionsEngineResult, ()> for DefaultInteractionsEngine {
900 fn perform_interactions(
901 &mut self,
902 app: &mut Application,
903 ) -> Result<DefaultInteractionsEngineResult, ()> {
904 let mut to_resize = HashSet::new();
905 let mut to_relative_layout = HashSet::new();
906 let mut to_select = None;
907 let mut to_jump = HashMap::new();
908 let mut to_focus = None;
909 let mut to_send_axis = vec![];
910 let mut to_send_custom = vec![];
911 for (id, signal) in app.signals() {
912 if let Some(signal) = signal.as_any().downcast_ref() {
913 match signal {
914 ResizeListenerSignal::Register => {
915 if let Some(item) = app.layout_data().items.get(id) {
916 self.resize_listeners
917 .insert(id.to_owned(), item.local_space.size());
918 to_resize.insert(id.to_owned());
919 }
920 }
921 ResizeListenerSignal::Unregister => {
922 self.resize_listeners.remove(id);
923 }
924 _ => {}
925 }
926 } else if let Some(signal) = signal.as_any().downcast_ref() {
927 match signal {
928 RelativeLayoutListenerSignal::Register(relative_to) => {
929 if let (Some(item), Some(rect)) = (
930 app.layout_data().items.get(relative_to),
931 app.layout_data().rect_relative_to(id, relative_to),
932 ) {
933 self.relative_layout_listeners.insert(
934 id.to_owned(),
935 (relative_to.to_owned(), item.local_space.size(), rect),
936 );
937 to_relative_layout.insert(id.to_owned());
938 }
939 }
940 RelativeLayoutListenerSignal::Unregister => {
941 self.relative_layout_listeners.remove(id);
942 }
943 _ => {}
944 }
945 } else if let Some(signal) = signal.as_any().downcast_ref() {
946 match signal {
947 NavSignal::Register(t) => match t {
948 NavType::Container => {
949 self.containers.insert(id.to_owned(), Default::default());
950 }
951 NavType::Item => {
952 if let Some((key, items)) = self
953 .containers
954 .iter_mut()
955 .filter(|(k, _)| {
956 k.path() != id.path() && id.path().starts_with(k.path())
957 })
958 .max_by(|(a, _), (b, _)| a.depth().cmp(&b.depth()))
959 {
960 items.remove(id);
961 items.insert(id.to_owned());
962 self.items_owners.insert(id.to_owned(), key.to_owned());
963 }
964 }
965 NavType::Button => {
966 self.buttons.insert(id.to_owned());
967 }
968 NavType::TextInput => {
969 self.text_inputs.insert(id.to_owned());
970 }
971 NavType::ScrollView => {
972 self.scroll_views.insert(id.to_owned());
973 }
974 NavType::ScrollViewContent => {
975 self.scroll_view_contents.insert(id.to_owned());
976 }
977 NavType::Tracking(who) => {
978 if let Some(who) = who.read() {
979 self.tracking.insert(id.to_owned(), who);
980 }
981 }
982 },
983 NavSignal::Unregister(t) => match t {
984 NavType::Container => {
985 if let Some(items) = self.containers.remove(id) {
986 for id in items {
987 self.items_owners.remove(&id);
988 }
989 }
990 }
991 NavType::Item => {
992 if let Some(key) = self.items_owners.remove(id)
993 && let Some(items) = self.containers.get_mut(&key)
994 {
995 items.remove(&key);
996 }
997 if let Some(lid) = &self.locked_widget
998 && lid == id
999 {
1000 self.locked_widget = None;
1001 }
1002 }
1003 NavType::Button => {
1004 self.buttons.remove(id);
1005 }
1006 NavType::TextInput => {
1007 self.text_inputs.remove(id);
1008 if let Some(focused) = &self.focused_text_input
1009 && focused == id
1010 {
1011 self.focused_text_input = None;
1012 }
1013 }
1014 NavType::ScrollView => {
1015 self.scroll_views.remove(id);
1016 }
1017 NavType::ScrollViewContent => {
1018 self.scroll_view_contents.remove(id);
1019 }
1020 NavType::Tracking(_) => {
1021 self.tracking.remove(id);
1022 }
1023 },
1024 NavSignal::Select(idref) => to_select = Some(idref.to_owned()),
1025 NavSignal::Unselect => to_select = Some(().into()),
1026 NavSignal::Lock => {
1027 if self.locked_widget.is_none() {
1028 self.locked_widget = Some(id.to_owned());
1029 }
1030 }
1031 NavSignal::Unlock => {
1032 if let Some(lid) = &self.locked_widget
1033 && lid == id
1034 {
1035 self.locked_widget = None;
1036 }
1037 }
1038 NavSignal::Jump(data) => {
1039 to_jump.insert(id.to_owned(), data.to_owned());
1040 }
1041 NavSignal::FocusTextInput(idref) => to_focus = Some(idref.to_owned()),
1042 NavSignal::Axis(name, value) => to_send_axis.push((name.to_owned(), *value)),
1043 NavSignal::Custom(idref, data) => {
1044 to_send_custom.push((idref.to_owned(), data.to_owned()))
1045 }
1046 _ => {}
1047 }
1048 }
1049 }
1050
1051 for (k, v) in &mut self.resize_listeners {
1052 if let Some(item) = app.layout_data().items.get(k) {
1053 let size = item.local_space.size();
1054 if to_resize.contains(k)
1055 || (v.x - size.x).abs() >= 1.0e-6
1056 || (v.y - size.y).abs() >= 1.0e-6
1057 {
1058 app.send_message(k, ResizeListenerSignal::Change(size));
1059 *v = size;
1060 }
1061 }
1062 }
1063 for (k, (r, s, v)) in &mut self.relative_layout_listeners {
1064 if let (Some(item), Some(rect)) = (
1065 app.layout_data().items.get(r),
1066 app.layout_data().rect_relative_to(k, r),
1067 ) {
1068 let size = item.local_space.size();
1069 if to_relative_layout.contains(k)
1070 || (s.x - size.x).abs() >= 1.0e-6
1071 || (s.y - size.y).abs() >= 1.0e-6
1072 || (v.left - rect.left).abs() >= 1.0e-6
1073 || (v.right - rect.right).abs() >= 1.0e-6
1074 || (v.top - rect.top).abs() >= 1.0e-6
1075 || (v.bottom - rect.bottom).abs() >= 1.0e-6
1076 {
1077 app.send_message(k, RelativeLayoutListenerSignal::Change(size, rect));
1078 *s = size;
1079 *v = rect;
1080 }
1081 }
1082 }
1083 if !to_jump.is_empty() {
1084 self.cache_sorted_items_ids(app);
1085 }
1086 if let Some(idref) = to_select {
1087 self.select_item(app, idref.read());
1088 }
1089 for (id, data) in to_jump {
1090 self.jump(app, &id, data);
1091 }
1092 if let Some(idref) = to_focus {
1093 self.focus_text_input(app, idref.read());
1094 }
1095 for (name, value) in to_send_axis {
1096 self.send_to_selected_item(app, NavSignal::Axis(name, value));
1097 }
1098 for (idref, data) in to_send_custom {
1099 if let Some(id) = idref.read() {
1100 app.send_message(&id, NavSignal::Custom(().into(), data));
1101 } else {
1102 self.send_to_selected_item(app, NavSignal::Custom(().into(), data));
1103 }
1104 }
1105 let mut result = DefaultInteractionsEngineResult::default();
1106 while let Some(interaction) = self.interactions_queue.pop_front() {
1107 match interaction {
1108 Interaction::None => {}
1109 Interaction::Navigate(msg) => match msg {
1110 NavSignal::Select(idref) => {
1111 self.select_item(app, idref.read());
1112 }
1113 NavSignal::Unselect => {
1114 self.select_item(app, None);
1115 }
1116 NavSignal::Accept(_) | NavSignal::Context(_) | NavSignal::Cancel(_) => {
1117 self.send_to_selected_item(app, msg);
1118 }
1119 NavSignal::Up
1120 | NavSignal::Down
1121 | NavSignal::Left
1122 | NavSignal::Right
1123 | NavSignal::Prev
1124 | NavSignal::Next => {
1125 self.send_to_selected_container(app, msg);
1126 }
1127 NavSignal::FocusTextInput(idref) => {
1128 self.focus_text_input(app, idref.read());
1129 }
1130 NavSignal::TextChange(_) => {
1131 if self.send_to_focused_text_input(app, msg) {
1132 result.captured_text_change = true;
1133 }
1134 }
1135 NavSignal::Custom(idref, data) => {
1136 if let Some(id) = idref.read() {
1137 app.send_message(&id, NavSignal::Custom(().into(), data));
1138 } else {
1139 self.send_to_selected_item(app, NavSignal::Custom(().into(), data));
1140 }
1141 }
1142 NavSignal::Jump(jump) => match jump {
1143 NavJump::Scroll(NavScroll::Factor(_, _))
1144 | NavJump::Scroll(NavScroll::Units(_, _))
1145 | NavJump::Scroll(NavScroll::Widget(_, _)) => {
1146 if let Some(id) = self.selected_scroll_view().cloned() {
1147 self.jump(app, &id, jump);
1148 }
1149 }
1150 _ => {}
1151 },
1152 _ => {}
1153 },
1154 Interaction::PointerMove(Vec2 { x, y }) => {
1155 if self.locked_widget.is_some() {
1156 if self.selected_button().is_some() {
1157 result.captured_pointer_location = true;
1158 }
1159 } else if let Some((found, _)) = self.find_button(app, x, y) {
1160 result.captured_pointer_location = true;
1161 self.select_item(app, Some(found));
1162 } else {
1163 if self.deselect_when_no_button_found {
1164 self.select_item(app, None);
1165 }
1166 if self.does_hover_widget(app, x, y) {
1167 result.captured_pointer_location = true;
1168 }
1169 }
1170 for (id, who) in &self.tracking {
1171 if let Some(layout) = app.layout_data().items.get(who) {
1172 let rect = layout.ui_space;
1173 let size = rect.size();
1174 app.send_message(
1175 id,
1176 NavSignal::Axis(
1177 "pointer-x".to_owned(),
1178 if size.x > 0.0 {
1179 (x - rect.left) / size.x
1180 } else {
1181 0.0
1182 },
1183 ),
1184 );
1185 app.send_message(
1186 id,
1187 NavSignal::Axis(
1188 "pointer-y".to_owned(),
1189 if size.y > 0.0 {
1190 (y - rect.top) / size.y
1191 } else {
1192 0.0
1193 },
1194 ),
1195 );
1196 app.send_message(
1197 id,
1198 NavSignal::Axis("pointer-x-unscaled".to_owned(), x - rect.left),
1199 );
1200 app.send_message(
1201 id,
1202 NavSignal::Axis("pointer-y-unscaled".to_owned(), y - rect.top),
1203 );
1204 app.send_message(id, NavSignal::Axis("pointer-x-ui".to_owned(), x));
1205 app.send_message(id, NavSignal::Axis("pointer-y-ui".to_owned(), y));
1206 result.captured_pointer_location = true;
1207 result.captured_pointer_action = true;
1208 }
1209 }
1210 }
1211 Interaction::PointerDown(button, Vec2 { x, y }) => {
1212 if let Some((found, _)) = self.find_button(app, x, y) {
1213 self.select_item(app, Some(found));
1214 result.captured_pointer_location = true;
1215 let action = match button {
1216 PointerButton::Trigger => NavSignal::Accept(true),
1217 PointerButton::Context => NavSignal::Context(true),
1218 };
1219 if self.send_to_selected_button(app, action) {
1220 result.captured_pointer_action = true;
1221 }
1222 } else {
1223 if self.deselect_when_no_button_found {
1224 self.select_item(app, None);
1225 }
1226 if self.does_hover_widget(app, x, y) {
1227 result.captured_pointer_location = true;
1228 }
1229 }
1230 for (id, who) in &self.tracking {
1231 if let Some(layout) = app.layout_data().items.get(who) {
1232 let rect = layout.ui_space;
1233 let size = rect.size();
1234 app.send_message(
1235 id,
1236 NavSignal::Axis(
1237 "pointer-x".to_owned(),
1238 if size.x > 0.0 {
1239 (x - rect.left) / size.x
1240 } else {
1241 0.0
1242 },
1243 ),
1244 );
1245 app.send_message(
1246 id,
1247 NavSignal::Axis(
1248 "pointer-y".to_owned(),
1249 if size.y > 0.0 {
1250 (y - rect.top) / size.y
1251 } else {
1252 0.0
1253 },
1254 ),
1255 );
1256 app.send_message(
1257 id,
1258 NavSignal::Axis("pointer-x-unscaled".to_owned(), x - rect.left),
1259 );
1260 app.send_message(
1261 id,
1262 NavSignal::Axis("pointer-y-unscaled".to_owned(), y - rect.top),
1263 );
1264 app.send_message(id, NavSignal::Axis("pointer-x-ui".to_owned(), x));
1265 app.send_message(id, NavSignal::Axis("pointer-y-ui".to_owned(), y));
1266 result.captured_pointer_location = true;
1267 result.captured_pointer_action = true;
1268 }
1269 }
1270 }
1271 Interaction::PointerUp(button, Vec2 { x, y }) => {
1272 let action = match button {
1273 PointerButton::Trigger => NavSignal::Accept(false),
1274 PointerButton::Context => NavSignal::Context(false),
1275 };
1276 if self.send_to_selected_button(app, action) {
1277 result.captured_pointer_action = true;
1278 }
1279 for (id, who) in &self.tracking {
1280 if let Some(layout) = app.layout_data().items.get(who) {
1281 let rect = layout.ui_space;
1282 let size = rect.size();
1283 app.send_message(
1284 id,
1285 NavSignal::Axis(
1286 "pointer-x".to_owned(),
1287 if size.x > 0.0 {
1288 (x - rect.left) / size.x
1289 } else {
1290 0.0
1291 },
1292 ),
1293 );
1294 app.send_message(
1295 id,
1296 NavSignal::Axis(
1297 "pointer-y".to_owned(),
1298 if size.y > 0.0 {
1299 (y - rect.top) / size.y
1300 } else {
1301 0.0
1302 },
1303 ),
1304 );
1305 app.send_message(
1306 id,
1307 NavSignal::Axis("pointer-x-unscaled".to_owned(), x - rect.left),
1308 );
1309 app.send_message(
1310 id,
1311 NavSignal::Axis("pointer-y-unscaled".to_owned(), y - rect.top),
1312 );
1313 app.send_message(id, NavSignal::Axis("pointer-x-ui".to_owned(), x));
1314 app.send_message(id, NavSignal::Axis("pointer-y-ui".to_owned(), y));
1315 result.captured_pointer_location = true;
1316 result.captured_pointer_action = true;
1317 }
1318 }
1319 }
1320 }
1321 }
1322 Ok(result)
1323 }
1324}