raui_core/interactive/
default_interactions_engine.rs

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/// Single pointer + Keyboard + Gamepad
65#[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    // TODO: refactor this shit! my eyes are bleeding, like really dude ffs..
482    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}