1use crate::{
2 application::Application,
3 interactive::InteractionsEngine,
4 messenger::MessageData,
5 widget::{
6 component::{
7 interactive::navigation::{NavDirection, NavJump, NavScroll, NavSignal, NavType},
8 RelativeLayoutListenerSignal, ResizeListenerSignal,
9 },
10 unit::WidgetUnit,
11 utils::{lerp, Rect, Vec2},
12 WidgetId,
13 },
14 Scalar,
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 if self.containers.contains_key(id) {
223 app.send_message(id, NavSignal::Select(id.to_owned().into()));
224 }
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 if self.text_inputs.contains(&id) {
296 app.send_message(&id, NavSignal::FocusTextInput(id.to_owned().into()));
297 self.focused_text_input = Some(id);
298 }
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 if let Some(items) = self.containers.get(id) {
349 for item in items {
350 if self.scroll_view_contents.contains(item) {
351 return Some(item.to_owned());
352 }
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 {
730 if let Some(rect) = app.layout_data().rect_relative_to(&wid, &oid) {
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
769 pub fn find_button(&self, app: &Application, x: Scalar, y: Scalar) -> Option<(WidgetId, Vec2)> {
770 self.find_button_inner(app, x, y, app.rendered_tree(), app.layout_data().ui_space)
771 }
772
773 fn find_button_inner(
774 &self,
775 app: &Application,
776 x: Scalar,
777 y: Scalar,
778 unit: &WidgetUnit,
779 mut clip: Rect,
780 ) -> Option<(WidgetId, Vec2)> {
781 if x < clip.left || x > clip.right || y < clip.top || y > clip.bottom {
782 return None;
783 }
784 let mut result = None;
785 if let Some(data) = unit.as_data() {
786 if self.buttons.contains(data.id()) {
787 if let Some(layout) = app.layout_data().items.get(data.id()) {
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 }
807 }
808 match unit {
809 WidgetUnit::AreaBox(unit) => {
810 if let Some(id) = self.find_button_inner(app, x, y, &unit.slot, clip) {
811 result = Some(id);
812 }
813 }
814 WidgetUnit::ContentBox(unit) => {
815 if unit.clipping {
816 if let Some(item) = app.layout_data().items.get(&unit.id) {
817 clip = item.ui_space;
818 }
819 }
820 for item in &unit.items {
821 if let Some(id) = self.find_button_inner(app, x, y, &item.slot, clip) {
822 result = Some(id);
823 }
824 }
825 }
826 WidgetUnit::FlexBox(unit) => {
827 for item in &unit.items {
828 if let Some(id) = self.find_button_inner(app, x, y, &item.slot, clip) {
829 result = Some(id);
830 }
831 }
832 }
833 WidgetUnit::GridBox(unit) => {
834 for item in &unit.items {
835 if let Some(id) = self.find_button_inner(app, x, y, &item.slot, clip) {
836 result = Some(id);
837 }
838 }
839 }
840 WidgetUnit::SizeBox(unit) => {
841 if let Some(id) = self.find_button_inner(app, x, y, &unit.slot, clip) {
842 result = Some(id);
843 }
844 }
845 _ => {}
846 }
847 result
848 }
849
850 pub fn does_hover_widget(&self, app: &Application, x: Scalar, y: Scalar) -> bool {
851 Self::does_hover_widget_inner(app, x, y, app.rendered_tree())
852 }
853
854 fn does_hover_widget_inner(app: &Application, x: Scalar, y: Scalar, unit: &WidgetUnit) -> bool {
855 if let Some(data) = unit.as_data() {
856 if let Some(layout) = app.layout_data().items.get(data.id()) {
857 let rect = layout.ui_space;
858 if x >= rect.left && x <= rect.right && y >= rect.top && y <= rect.bottom {
859 return true;
860 }
861 }
862 }
863 match unit {
864 WidgetUnit::AreaBox(unit) => {
865 if Self::does_hover_widget_inner(app, x, y, &unit.slot) {
866 return true;
867 }
868 }
869 WidgetUnit::ContentBox(unit) => {
870 for item in &unit.items {
871 if Self::does_hover_widget_inner(app, x, y, &item.slot) {
872 return true;
873 }
874 }
875 }
876 WidgetUnit::FlexBox(unit) => {
877 for item in &unit.items {
878 if Self::does_hover_widget_inner(app, x, y, &item.slot) {
879 return true;
880 }
881 }
882 }
883 WidgetUnit::GridBox(unit) => {
884 for item in &unit.items {
885 if Self::does_hover_widget_inner(app, x, y, &item.slot) {
886 return true;
887 }
888 }
889 }
890 WidgetUnit::SizeBox(unit) => {
891 if Self::does_hover_widget_inner(app, x, y, &unit.slot) {
892 return true;
893 }
894 }
895 _ => {}
896 }
897 false
898 }
899}
900
901impl InteractionsEngine<DefaultInteractionsEngineResult, ()> for DefaultInteractionsEngine {
902 fn perform_interactions(
903 &mut self,
904 app: &mut Application,
905 ) -> Result<DefaultInteractionsEngineResult, ()> {
906 let mut to_resize = HashSet::new();
907 let mut to_relative_layout = HashSet::new();
908 let mut to_select = None;
909 let mut to_jump = HashMap::new();
910 let mut to_focus = None;
911 let mut to_send_axis = vec![];
912 let mut to_send_custom = vec![];
913 for (id, signal) in app.signals() {
914 if let Some(signal) = signal.as_any().downcast_ref() {
915 match signal {
916 ResizeListenerSignal::Register => {
917 if let Some(item) = app.layout_data().items.get(id) {
918 self.resize_listeners
919 .insert(id.to_owned(), item.local_space.size());
920 to_resize.insert(id.to_owned());
921 }
922 }
923 ResizeListenerSignal::Unregister => {
924 self.resize_listeners.remove(id);
925 }
926 _ => {}
927 }
928 } else if let Some(signal) = signal.as_any().downcast_ref() {
929 match signal {
930 RelativeLayoutListenerSignal::Register(relative_to) => {
931 if let (Some(item), Some(rect)) = (
932 app.layout_data().items.get(relative_to),
933 app.layout_data().rect_relative_to(id, relative_to),
934 ) {
935 self.relative_layout_listeners.insert(
936 id.to_owned(),
937 (relative_to.to_owned(), item.local_space.size(), rect),
938 );
939 to_relative_layout.insert(id.to_owned());
940 }
941 }
942 RelativeLayoutListenerSignal::Unregister => {
943 self.relative_layout_listeners.remove(id);
944 }
945 _ => {}
946 }
947 } else if let Some(signal) = signal.as_any().downcast_ref() {
948 match signal {
949 NavSignal::Register(t) => match t {
950 NavType::Container => {
951 self.containers.insert(id.to_owned(), Default::default());
952 }
953 NavType::Item => {
954 if let Some((key, items)) = self
955 .containers
956 .iter_mut()
957 .filter(|(k, _)| {
958 k.path() != id.path() && id.path().starts_with(k.path())
959 })
960 .max_by(|(a, _), (b, _)| a.depth().cmp(&b.depth()))
961 {
962 items.remove(id);
963 items.insert(id.to_owned());
964 self.items_owners.insert(id.to_owned(), key.to_owned());
965 }
966 }
967 NavType::Button => {
968 self.buttons.insert(id.to_owned());
969 }
970 NavType::TextInput => {
971 self.text_inputs.insert(id.to_owned());
972 }
973 NavType::ScrollView => {
974 self.scroll_views.insert(id.to_owned());
975 }
976 NavType::ScrollViewContent => {
977 self.scroll_view_contents.insert(id.to_owned());
978 }
979 NavType::Tracking(who) => {
980 if let Some(who) = who.read() {
981 self.tracking.insert(id.to_owned(), who);
982 }
983 }
984 },
985 NavSignal::Unregister(t) => match t {
986 NavType::Container => {
987 if let Some(items) = self.containers.remove(id) {
988 for id in items {
989 self.items_owners.remove(&id);
990 }
991 }
992 }
993 NavType::Item => {
994 if let Some(key) = self.items_owners.remove(id) {
995 if let Some(items) = self.containers.get_mut(&key) {
996 items.remove(&key);
997 }
998 }
999 if let Some(lid) = &self.locked_widget {
1000 if lid == id {
1001 self.locked_widget = None;
1002 }
1003 }
1004 }
1005 NavType::Button => {
1006 self.buttons.remove(id);
1007 }
1008 NavType::TextInput => {
1009 self.text_inputs.remove(id);
1010 if let Some(focused) = &self.focused_text_input {
1011 if focused == id {
1012 self.focused_text_input = None;
1013 }
1014 }
1015 }
1016 NavType::ScrollView => {
1017 self.scroll_views.remove(id);
1018 }
1019 NavType::ScrollViewContent => {
1020 self.scroll_view_contents.remove(id);
1021 }
1022 NavType::Tracking(_) => {
1023 self.tracking.remove(id);
1024 }
1025 },
1026 NavSignal::Select(idref) => to_select = Some(idref.to_owned()),
1027 NavSignal::Unselect => to_select = Some(().into()),
1028 NavSignal::Lock => {
1029 if self.locked_widget.is_none() {
1030 self.locked_widget = Some(id.to_owned());
1031 }
1032 }
1033 NavSignal::Unlock => {
1034 if let Some(lid) = &self.locked_widget {
1035 if lid == id {
1036 self.locked_widget = None;
1037 }
1038 }
1039 }
1040 NavSignal::Jump(data) => {
1041 to_jump.insert(id.to_owned(), data.to_owned());
1042 }
1043 NavSignal::FocusTextInput(idref) => to_focus = Some(idref.to_owned()),
1044 NavSignal::Axis(name, value) => to_send_axis.push((name.to_owned(), *value)),
1045 NavSignal::Custom(idref, data) => {
1046 to_send_custom.push((idref.to_owned(), data.to_owned()))
1047 }
1048 _ => {}
1049 }
1050 }
1051 }
1052
1053 for (k, v) in &mut self.resize_listeners {
1054 if let Some(item) = app.layout_data().items.get(k) {
1055 let size = item.local_space.size();
1056 if to_resize.contains(k)
1057 || (v.x - size.x).abs() >= 1.0e-6
1058 || (v.y - size.y).abs() >= 1.0e-6
1059 {
1060 app.send_message(k, ResizeListenerSignal::Change(size));
1061 *v = size;
1062 }
1063 }
1064 }
1065 for (k, (r, s, v)) in &mut self.relative_layout_listeners {
1066 if let (Some(item), Some(rect)) = (
1067 app.layout_data().items.get(r),
1068 app.layout_data().rect_relative_to(k, r),
1069 ) {
1070 let size = item.local_space.size();
1071 if to_relative_layout.contains(k)
1072 || (s.x - size.x).abs() >= 1.0e-6
1073 || (s.y - size.y).abs() >= 1.0e-6
1074 || (v.left - rect.left).abs() >= 1.0e-6
1075 || (v.right - rect.right).abs() >= 1.0e-6
1076 || (v.top - rect.top).abs() >= 1.0e-6
1077 || (v.bottom - rect.bottom).abs() >= 1.0e-6
1078 {
1079 app.send_message(k, RelativeLayoutListenerSignal::Change(size, rect));
1080 *s = size;
1081 *v = rect;
1082 }
1083 }
1084 }
1085 if !to_jump.is_empty() {
1086 self.cache_sorted_items_ids(app);
1087 }
1088 if let Some(idref) = to_select {
1089 self.select_item(app, idref.read());
1090 }
1091 for (id, data) in to_jump {
1092 self.jump(app, &id, data);
1093 }
1094 if let Some(idref) = to_focus {
1095 self.focus_text_input(app, idref.read());
1096 }
1097 for (name, value) in to_send_axis {
1098 self.send_to_selected_item(app, NavSignal::Axis(name, value));
1099 }
1100 for (idref, data) in to_send_custom {
1101 if let Some(id) = idref.read() {
1102 app.send_message(&id, NavSignal::Custom(().into(), data));
1103 } else {
1104 self.send_to_selected_item(app, NavSignal::Custom(().into(), data));
1105 }
1106 }
1107 let mut result = DefaultInteractionsEngineResult::default();
1108 while let Some(interaction) = self.interactions_queue.pop_front() {
1109 match interaction {
1110 Interaction::None => {}
1111 Interaction::Navigate(msg) => match msg {
1112 NavSignal::Select(idref) => {
1113 self.select_item(app, idref.read());
1114 }
1115 NavSignal::Unselect => {
1116 self.select_item(app, None);
1117 }
1118 NavSignal::Accept(_) | NavSignal::Context(_) | NavSignal::Cancel(_) => {
1119 self.send_to_selected_item(app, msg);
1120 }
1121 NavSignal::Up
1122 | NavSignal::Down
1123 | NavSignal::Left
1124 | NavSignal::Right
1125 | NavSignal::Prev
1126 | NavSignal::Next => {
1127 self.send_to_selected_container(app, msg);
1128 }
1129 NavSignal::FocusTextInput(idref) => {
1130 self.focus_text_input(app, idref.read());
1131 }
1132 NavSignal::TextChange(_) => {
1133 if self.send_to_focused_text_input(app, msg) {
1134 result.captured_text_change = true;
1135 }
1136 }
1137 NavSignal::Custom(idref, data) => {
1138 if let Some(id) = idref.read() {
1139 app.send_message(&id, NavSignal::Custom(().into(), data));
1140 } else {
1141 self.send_to_selected_item(app, NavSignal::Custom(().into(), data));
1142 }
1143 }
1144 NavSignal::Jump(jump) => match jump {
1145 NavJump::Scroll(NavScroll::Factor(_, _))
1146 | NavJump::Scroll(NavScroll::Units(_, _))
1147 | NavJump::Scroll(NavScroll::Widget(_, _)) => {
1148 if let Some(id) = self.selected_scroll_view().cloned() {
1149 self.jump(app, &id, jump);
1150 }
1151 }
1152 _ => {}
1153 },
1154 _ => {}
1155 },
1156 Interaction::PointerMove(Vec2 { x, y }) => {
1157 if self.locked_widget.is_some() {
1158 if self.selected_button().is_some() {
1159 result.captured_pointer_location = true;
1160 }
1161 } else if let Some((found, _)) = self.find_button(app, x, y) {
1162 result.captured_pointer_location = true;
1163 self.select_item(app, Some(found));
1164 } else {
1165 if self.deselect_when_no_button_found {
1166 self.select_item(app, None);
1167 }
1168 if self.does_hover_widget(app, x, y) {
1169 result.captured_pointer_location = true;
1170 }
1171 }
1172 for (id, who) in &self.tracking {
1173 if let Some(layout) = app.layout_data().items.get(who) {
1174 let rect = layout.ui_space;
1175 let size = rect.size();
1176 let x = if size.x > 0.0 {
1177 (x - rect.left) / size.x
1178 } else {
1179 0.0
1180 };
1181 let y = if size.y > 0.0 {
1182 (y - rect.top) / size.y
1183 } else {
1184 0.0
1185 };
1186 app.send_message(id, NavSignal::Axis("pointer-x".to_owned(), x));
1187 app.send_message(id, NavSignal::Axis("pointer-y".to_owned(), y));
1188 result.captured_pointer_location = true;
1189 result.captured_pointer_action = true;
1190 }
1191 }
1192 }
1193 Interaction::PointerDown(button, Vec2 { x, y }) => {
1194 if let Some((found, _)) = self.find_button(app, x, y) {
1195 self.select_item(app, Some(found));
1196 result.captured_pointer_location = true;
1197 let action = match button {
1198 PointerButton::Trigger => NavSignal::Accept(true),
1199 PointerButton::Context => NavSignal::Context(true),
1200 };
1201 if self.send_to_selected_button(app, action) {
1202 result.captured_pointer_action = true;
1203 }
1204 } else {
1205 if self.deselect_when_no_button_found {
1206 self.select_item(app, None);
1207 }
1208 if self.does_hover_widget(app, x, y) {
1209 result.captured_pointer_location = true;
1210 }
1211 }
1212 for (id, who) in &self.tracking {
1213 if let Some(layout) = app.layout_data().items.get(who) {
1214 let rect = layout.ui_space;
1215 let size = rect.size();
1216 let x = if size.x > 0.0 {
1217 (x - rect.left) / size.x
1218 } else {
1219 0.0
1220 };
1221 let y = if size.y > 0.0 {
1222 (y - rect.top) / size.y
1223 } else {
1224 0.0
1225 };
1226 app.send_message(id, NavSignal::Axis("pointer-x".to_owned(), x));
1227 app.send_message(id, NavSignal::Axis("pointer-y".to_owned(), y));
1228 result.captured_pointer_location = true;
1229 result.captured_pointer_action = true;
1230 }
1231 }
1232 }
1233 Interaction::PointerUp(button, Vec2 { x, y }) => {
1234 let action = match button {
1235 PointerButton::Trigger => NavSignal::Accept(false),
1236 PointerButton::Context => NavSignal::Context(false),
1237 };
1238 if self.send_to_selected_button(app, action) {
1239 result.captured_pointer_action = true;
1240 }
1241 for (id, who) in &self.tracking {
1242 if let Some(layout) = app.layout_data().items.get(who) {
1243 let rect = layout.ui_space;
1244 let size = rect.size();
1245 let x = if size.x > 0.0 {
1246 (x - rect.left) / size.x
1247 } else {
1248 0.0
1249 };
1250 let y = if size.y > 0.0 {
1251 (y - rect.top) / size.y
1252 } else {
1253 0.0
1254 };
1255 app.send_message(id, NavSignal::Axis("pointer-x".to_owned(), x));
1256 app.send_message(id, NavSignal::Axis("pointer-y".to_owned(), y));
1257 result.captured_pointer_location = true;
1258 result.captured_pointer_action = true;
1259 }
1260 }
1261 }
1262 }
1263 }
1264 Ok(result)
1265 }
1266}