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) if Self::does_hover_widget_inner(app, x, y, &unit.slot) => {
889 return true;
890 }
891 _ => {}
892 }
893 false
894 }
895}
896
897impl InteractionsEngine<DefaultInteractionsEngineResult, ()> for DefaultInteractionsEngine {
898 fn perform_interactions(
899 &mut self,
900 app: &mut Application,
901 ) -> Result<DefaultInteractionsEngineResult, ()> {
902 let mut to_resize = HashSet::new();
903 let mut to_relative_layout = HashSet::new();
904 let mut to_select = None;
905 let mut to_jump = HashMap::new();
906 let mut to_focus = None;
907 let mut to_send_axis = vec![];
908 let mut to_send_custom = vec![];
909 for (id, signal) in app.signals() {
910 if let Some(signal) = signal.as_any().downcast_ref() {
911 match signal {
912 ResizeListenerSignal::Register => {
913 if let Some(item) = app.layout_data().items.get(id) {
914 self.resize_listeners
915 .insert(id.to_owned(), item.local_space.size());
916 to_resize.insert(id.to_owned());
917 }
918 }
919 ResizeListenerSignal::Unregister => {
920 self.resize_listeners.remove(id);
921 }
922 _ => {}
923 }
924 } else if let Some(signal) = signal.as_any().downcast_ref() {
925 match signal {
926 RelativeLayoutListenerSignal::Register(relative_to) => {
927 if let (Some(item), Some(rect)) = (
928 app.layout_data().items.get(relative_to),
929 app.layout_data().rect_relative_to(id, relative_to),
930 ) {
931 self.relative_layout_listeners.insert(
932 id.to_owned(),
933 (relative_to.to_owned(), item.local_space.size(), rect),
934 );
935 to_relative_layout.insert(id.to_owned());
936 }
937 }
938 RelativeLayoutListenerSignal::Unregister => {
939 self.relative_layout_listeners.remove(id);
940 }
941 _ => {}
942 }
943 } else if let Some(signal) = signal.as_any().downcast_ref() {
944 match signal {
945 NavSignal::Register(t) => match t {
946 NavType::Container => {
947 self.containers.insert(id.to_owned(), Default::default());
948 }
949 NavType::Item => {
950 if let Some((key, items)) = self
951 .containers
952 .iter_mut()
953 .filter(|(k, _)| {
954 k.path() != id.path() && id.path().starts_with(k.path())
955 })
956 .max_by(|(a, _), (b, _)| a.depth().cmp(&b.depth()))
957 {
958 items.remove(id);
959 items.insert(id.to_owned());
960 self.items_owners.insert(id.to_owned(), key.to_owned());
961 }
962 }
963 NavType::Button => {
964 self.buttons.insert(id.to_owned());
965 }
966 NavType::TextInput => {
967 self.text_inputs.insert(id.to_owned());
968 }
969 NavType::ScrollView => {
970 self.scroll_views.insert(id.to_owned());
971 }
972 NavType::ScrollViewContent => {
973 self.scroll_view_contents.insert(id.to_owned());
974 }
975 NavType::Tracking(who) => {
976 if let Some(who) = who.read() {
977 self.tracking.insert(id.to_owned(), who);
978 }
979 }
980 },
981 NavSignal::Unregister(t) => match t {
982 NavType::Container => {
983 if let Some(items) = self.containers.remove(id) {
984 for id in items {
985 self.items_owners.remove(&id);
986 }
987 }
988 }
989 NavType::Item => {
990 if let Some(key) = self.items_owners.remove(id)
991 && let Some(items) = self.containers.get_mut(&key)
992 {
993 items.remove(&key);
994 }
995 if let Some(lid) = &self.locked_widget
996 && lid == id
997 {
998 self.locked_widget = None;
999 }
1000 }
1001 NavType::Button => {
1002 self.buttons.remove(id);
1003 }
1004 NavType::TextInput => {
1005 self.text_inputs.remove(id);
1006 if let Some(focused) = &self.focused_text_input
1007 && focused == id
1008 {
1009 self.focused_text_input = None;
1010 }
1011 }
1012 NavType::ScrollView => {
1013 self.scroll_views.remove(id);
1014 }
1015 NavType::ScrollViewContent => {
1016 self.scroll_view_contents.remove(id);
1017 }
1018 NavType::Tracking(_) => {
1019 self.tracking.remove(id);
1020 }
1021 },
1022 NavSignal::Select(idref) => to_select = Some(idref.to_owned()),
1023 NavSignal::Unselect => to_select = Some(().into()),
1024 NavSignal::Lock => {
1025 if self.locked_widget.is_none() {
1026 self.locked_widget = Some(id.to_owned());
1027 }
1028 }
1029 NavSignal::Unlock => {
1030 if let Some(lid) = &self.locked_widget
1031 && lid == id
1032 {
1033 self.locked_widget = None;
1034 }
1035 }
1036 NavSignal::Jump(data) => {
1037 to_jump.insert(id.to_owned(), data.to_owned());
1038 }
1039 NavSignal::FocusTextInput(idref) => to_focus = Some(idref.to_owned()),
1040 NavSignal::Axis(name, value) => to_send_axis.push((name.to_owned(), *value)),
1041 NavSignal::Custom(idref, data) => {
1042 to_send_custom.push((idref.to_owned(), data.to_owned()))
1043 }
1044 _ => {}
1045 }
1046 }
1047 }
1048
1049 for (k, v) in &mut self.resize_listeners {
1050 if let Some(item) = app.layout_data().items.get(k) {
1051 let size = item.local_space.size();
1052 if to_resize.contains(k)
1053 || (v.x - size.x).abs() >= 1.0e-6
1054 || (v.y - size.y).abs() >= 1.0e-6
1055 {
1056 app.send_message(k, ResizeListenerSignal::Change(size));
1057 *v = size;
1058 }
1059 }
1060 }
1061 for (k, (r, s, v)) in &mut self.relative_layout_listeners {
1062 if let (Some(item), Some(rect)) = (
1063 app.layout_data().items.get(r),
1064 app.layout_data().rect_relative_to(k, r),
1065 ) {
1066 let size = item.local_space.size();
1067 if to_relative_layout.contains(k)
1068 || (s.x - size.x).abs() >= 1.0e-6
1069 || (s.y - size.y).abs() >= 1.0e-6
1070 || (v.left - rect.left).abs() >= 1.0e-6
1071 || (v.right - rect.right).abs() >= 1.0e-6
1072 || (v.top - rect.top).abs() >= 1.0e-6
1073 || (v.bottom - rect.bottom).abs() >= 1.0e-6
1074 {
1075 app.send_message(k, RelativeLayoutListenerSignal::Change(size, rect));
1076 *s = size;
1077 *v = rect;
1078 }
1079 }
1080 }
1081 if !to_jump.is_empty() {
1082 self.cache_sorted_items_ids(app);
1083 }
1084 if let Some(idref) = to_select {
1085 self.select_item(app, idref.read());
1086 }
1087 for (id, data) in to_jump {
1088 self.jump(app, &id, data);
1089 }
1090 if let Some(idref) = to_focus {
1091 self.focus_text_input(app, idref.read());
1092 }
1093 for (name, value) in to_send_axis {
1094 self.send_to_selected_item(app, NavSignal::Axis(name, value));
1095 }
1096 for (idref, data) in to_send_custom {
1097 if let Some(id) = idref.read() {
1098 app.send_message(&id, NavSignal::Custom(().into(), data));
1099 } else {
1100 self.send_to_selected_item(app, NavSignal::Custom(().into(), data));
1101 }
1102 }
1103 let mut result = DefaultInteractionsEngineResult::default();
1104 while let Some(interaction) = self.interactions_queue.pop_front() {
1105 match interaction {
1106 Interaction::None => {}
1107 Interaction::Navigate(msg) => match msg {
1108 NavSignal::Select(idref) => {
1109 self.select_item(app, idref.read());
1110 }
1111 NavSignal::Unselect => {
1112 self.select_item(app, None);
1113 }
1114 NavSignal::Accept(_) | NavSignal::Context(_) | NavSignal::Cancel(_) => {
1115 self.send_to_selected_item(app, msg);
1116 }
1117 NavSignal::Up
1118 | NavSignal::Down
1119 | NavSignal::Left
1120 | NavSignal::Right
1121 | NavSignal::Prev
1122 | NavSignal::Next => {
1123 self.send_to_selected_container(app, msg);
1124 }
1125 NavSignal::FocusTextInput(idref) => {
1126 self.focus_text_input(app, idref.read());
1127 }
1128 NavSignal::TextChange(_) => {
1129 if self.send_to_focused_text_input(app, msg) {
1130 result.captured_text_change = true;
1131 }
1132 }
1133 NavSignal::Custom(idref, data) => {
1134 if let Some(id) = idref.read() {
1135 app.send_message(&id, NavSignal::Custom(().into(), data));
1136 } else {
1137 self.send_to_selected_item(app, NavSignal::Custom(().into(), data));
1138 }
1139 }
1140 NavSignal::Jump(jump) => match jump {
1141 NavJump::Scroll(NavScroll::Factor(_, _))
1142 | NavJump::Scroll(NavScroll::Units(_, _))
1143 | NavJump::Scroll(NavScroll::Widget(_, _)) => {
1144 if let Some(id) = self.selected_scroll_view().cloned() {
1145 self.jump(app, &id, jump);
1146 }
1147 }
1148 _ => {}
1149 },
1150 _ => {}
1151 },
1152 Interaction::PointerMove(Vec2 { x, y }) => {
1153 if self.locked_widget.is_some() {
1154 if self.selected_button().is_some() {
1155 result.captured_pointer_location = true;
1156 }
1157 } else if let Some((found, _)) = self.find_button(app, x, y) {
1158 result.captured_pointer_location = true;
1159 self.select_item(app, Some(found));
1160 } else {
1161 if self.deselect_when_no_button_found {
1162 self.select_item(app, None);
1163 }
1164 if self.does_hover_widget(app, x, y) {
1165 result.captured_pointer_location = true;
1166 }
1167 }
1168 for (id, who) in &self.tracking {
1169 if let Some(layout) = app.layout_data().items.get(who) {
1170 let rect = layout.ui_space;
1171 let size = rect.size();
1172 app.send_message(
1173 id,
1174 NavSignal::Axis(
1175 "pointer-x".to_owned(),
1176 if size.x > 0.0 {
1177 (x - rect.left) / size.x
1178 } else {
1179 0.0
1180 },
1181 ),
1182 );
1183 app.send_message(
1184 id,
1185 NavSignal::Axis(
1186 "pointer-y".to_owned(),
1187 if size.y > 0.0 {
1188 (y - rect.top) / size.y
1189 } else {
1190 0.0
1191 },
1192 ),
1193 );
1194 app.send_message(
1195 id,
1196 NavSignal::Axis("pointer-x-unscaled".to_owned(), x - rect.left),
1197 );
1198 app.send_message(
1199 id,
1200 NavSignal::Axis("pointer-y-unscaled".to_owned(), y - rect.top),
1201 );
1202 app.send_message(id, NavSignal::Axis("pointer-x-ui".to_owned(), x));
1203 app.send_message(id, NavSignal::Axis("pointer-y-ui".to_owned(), y));
1204 result.captured_pointer_location = true;
1205 result.captured_pointer_action = true;
1206 }
1207 }
1208 }
1209 Interaction::PointerDown(button, Vec2 { x, y }) => {
1210 if let Some((found, _)) = self.find_button(app, x, y) {
1211 self.select_item(app, Some(found));
1212 result.captured_pointer_location = true;
1213 let action = match button {
1214 PointerButton::Trigger => NavSignal::Accept(true),
1215 PointerButton::Context => NavSignal::Context(true),
1216 };
1217 if self.send_to_selected_button(app, action) {
1218 result.captured_pointer_action = true;
1219 }
1220 } else {
1221 if self.deselect_when_no_button_found {
1222 self.select_item(app, None);
1223 }
1224 if self.does_hover_widget(app, x, y) {
1225 result.captured_pointer_location = true;
1226 }
1227 }
1228 for (id, who) in &self.tracking {
1229 if let Some(layout) = app.layout_data().items.get(who) {
1230 let rect = layout.ui_space;
1231 let size = rect.size();
1232 app.send_message(
1233 id,
1234 NavSignal::Axis(
1235 "pointer-x".to_owned(),
1236 if size.x > 0.0 {
1237 (x - rect.left) / size.x
1238 } else {
1239 0.0
1240 },
1241 ),
1242 );
1243 app.send_message(
1244 id,
1245 NavSignal::Axis(
1246 "pointer-y".to_owned(),
1247 if size.y > 0.0 {
1248 (y - rect.top) / size.y
1249 } else {
1250 0.0
1251 },
1252 ),
1253 );
1254 app.send_message(
1255 id,
1256 NavSignal::Axis("pointer-x-unscaled".to_owned(), x - rect.left),
1257 );
1258 app.send_message(
1259 id,
1260 NavSignal::Axis("pointer-y-unscaled".to_owned(), y - rect.top),
1261 );
1262 app.send_message(id, NavSignal::Axis("pointer-x-ui".to_owned(), x));
1263 app.send_message(id, NavSignal::Axis("pointer-y-ui".to_owned(), y));
1264 result.captured_pointer_location = true;
1265 result.captured_pointer_action = true;
1266 }
1267 }
1268 }
1269 Interaction::PointerUp(button, Vec2 { x, y }) => {
1270 let action = match button {
1271 PointerButton::Trigger => NavSignal::Accept(false),
1272 PointerButton::Context => NavSignal::Context(false),
1273 };
1274 if self.send_to_selected_button(app, action) {
1275 result.captured_pointer_action = true;
1276 }
1277 for (id, who) in &self.tracking {
1278 if let Some(layout) = app.layout_data().items.get(who) {
1279 let rect = layout.ui_space;
1280 let size = rect.size();
1281 app.send_message(
1282 id,
1283 NavSignal::Axis(
1284 "pointer-x".to_owned(),
1285 if size.x > 0.0 {
1286 (x - rect.left) / size.x
1287 } else {
1288 0.0
1289 },
1290 ),
1291 );
1292 app.send_message(
1293 id,
1294 NavSignal::Axis(
1295 "pointer-y".to_owned(),
1296 if size.y > 0.0 {
1297 (y - rect.top) / size.y
1298 } else {
1299 0.0
1300 },
1301 ),
1302 );
1303 app.send_message(
1304 id,
1305 NavSignal::Axis("pointer-x-unscaled".to_owned(), x - rect.left),
1306 );
1307 app.send_message(
1308 id,
1309 NavSignal::Axis("pointer-y-unscaled".to_owned(), y - rect.top),
1310 );
1311 app.send_message(id, NavSignal::Axis("pointer-x-ui".to_owned(), x));
1312 app.send_message(id, NavSignal::Axis("pointer-y-ui".to_owned(), y));
1313 result.captured_pointer_location = true;
1314 result.captured_pointer_action = true;
1315 }
1316 }
1317 }
1318 }
1319 }
1320 Ok(result)
1321 }
1322}