vizia_core/systems/
hover.rs

1use std::{cmp::Ordering, collections::BinaryHeap};
2
3use crate::prelude::*;
4use log::debug;
5use skia_safe::Matrix;
6use vizia_storage::{DrawChildIterator, LayoutParentIterator};
7
8// Determines the hovered entity based on the mouse cursor position.
9pub fn hover_system(cx: &mut Context, window_entity: Entity) {
10    cx.current = window_entity;
11
12    if let Some(pseudo_classes) = cx.style.pseudo_classes.get(window_entity) {
13        if !pseudo_classes.contains(PseudoClassFlags::OVER) {
14            return;
15        }
16    }
17
18    let mut queue = BinaryHeap::new();
19    let pointer_events: bool =
20        cx.style.pointer_events.get(window_entity).copied().unwrap_or_default().into();
21    queue.push(ZEntity { index: 0, pointer_events, entity: window_entity });
22    let mut hovered = window_entity;
23    let transform = Matrix::new_identity();
24    // let clip_bounds = cx.cache.get_bounds(window_entity);
25    let clip_bounds: BoundingBox =
26        BoundingBox { x: -f32::MAX / 2.0, y: -f32::MAX / 2.0, w: f32::MAX, h: f32::MAX };
27    while !queue.is_empty() {
28        let zentity = queue.pop().unwrap();
29        cx.with_current(zentity.entity, |cx| {
30            hover_entity(
31                &mut EventContext::new(cx),
32                zentity.index,
33                zentity.pointer_events,
34                &mut queue,
35                &mut hovered,
36                transform,
37                &clip_bounds,
38            );
39        });
40    }
41
42    // Set hover state for hovered view and ancestors
43    let parent_iter = LayoutParentIterator::new(&cx.tree, hovered);
44    for ancestor in parent_iter {
45        if let Some(pseudo_classes) = cx.style.pseudo_classes.get_mut(ancestor) {
46            if pseudo_classes.contains(PseudoClassFlags::OVER)
47                && !pseudo_classes.contains(PseudoClassFlags::HOVER)
48            {
49                pseudo_classes.set(PseudoClassFlags::HOVER, true);
50            }
51        }
52    }
53
54    if hovered != cx.hovered {
55        // Useful for debugging
56        debug!(
57            "Hover changed to {:?} parent: {:?}, view: {}, posx: {}, posy: {} width: {} height: {}",
58            hovered,
59            cx.tree.get_layout_parent(hovered),
60            cx.views.get(&hovered).map_or("<None>", |view| view.element().unwrap_or("<Unnamed>")),
61            cx.cache.get_posx(hovered),
62            cx.cache.get_posy(hovered),
63            cx.cache.get_width(hovered),
64            cx.cache.get_height(hovered),
65        );
66
67        let cursor = cx.style.cursor.get(hovered).cloned().unwrap_or_default();
68
69        if !cx.cursor_icon_locked {
70            cx.emit(WindowEvent::SetCursor(cursor));
71        }
72
73        // Send mouse enter/leave events directly to entity.
74        cx.event_queue.push_back(Event::new(WindowEvent::MouseEnter).direct(hovered));
75        cx.event_queue.push_back(Event::new(WindowEvent::MouseLeave).direct(cx.hovered));
76
77        // Send mouse over/out events to entity and ancestors.
78        cx.event_queue.push_back(Event::new(WindowEvent::MouseOver).target(hovered));
79        cx.event_queue.push_back(Event::new(WindowEvent::MouseOut).target(cx.hovered));
80
81        cx.style.needs_restyle(cx.hovered);
82        cx.style.needs_restyle(hovered);
83
84        cx.hovered = hovered;
85    }
86}
87
88fn hover_entity(
89    cx: &mut EventContext,
90    current_z: i32,
91    parent_pointer_events: bool,
92    queue: &mut BinaryHeap<ZEntity>,
93    hovered: &mut Entity,
94    parent_transform: Matrix,
95    clip_bounds: &BoundingBox,
96) {
97    // Skip if non-hoverable (will skip any descendants)
98    let hoverable = cx
99        .style
100        .abilities
101        .get(cx.current)
102        .map(|abilitites| abilitites.contains(Abilities::HOVERABLE))
103        .unwrap_or(true);
104
105    if !hoverable {
106        return;
107    }
108
109    // Skip if not displayed.
110    // TODO: Should this skip descendants? Probably not...?
111    if cx.style.display.get(cx.current).copied().unwrap_or_default() == Display::None
112        && !cx.style.text_span.get(cx.current).copied().unwrap_or_default()
113    {
114        return;
115    }
116
117    let pointer_events = cx
118        .style
119        .pointer_events
120        .get(cx.current)
121        .copied()
122        .map(|pointer_events| match pointer_events {
123            PointerEvents::Auto => true,
124            PointerEvents::None => false,
125        })
126        .unwrap_or(parent_pointer_events);
127
128    // Push to queue if the z-index is higher than the current z-index.
129    let z_index = cx.style.z_index.get(cx.current).copied().unwrap_or_default();
130    if z_index > current_z {
131        queue.push(ZEntity { index: z_index, entity: cx.current, pointer_events });
132        return;
133    }
134
135    let bounds = cx.bounds();
136
137    let cursor_x = cx.mouse.cursor_x;
138    let cursor_y = cx.mouse.cursor_y;
139
140    if cursor_x < 0.0 || cursor_y < 0.0 {
141        return;
142    }
143
144    let mut transform = parent_transform;
145
146    transform = cx.transform() * transform;
147
148    let t = transform.invert().unwrap();
149    let t = t.map_point((cursor_x, cursor_y));
150    let tx = t.x;
151    let ty = t.y;
152    let clipping = clip_bounds.intersection(&cx.clip_region());
153
154    let b = bounds.intersection(&clipping);
155    // let b = bounds;
156
157    if let Some(pseudo_classes) = cx.style.pseudo_classes.get_mut(cx.current) {
158        pseudo_classes.set(PseudoClassFlags::HOVER, false);
159    }
160
161    if pointer_events {
162        if tx >= b.left() && tx < b.right() && ty >= b.top() && ty < b.bottom() {
163            *hovered = cx.current;
164
165            if !cx
166                .style
167                .pseudo_classes
168                .get(cx.current)
169                .copied()
170                .unwrap_or_default()
171                .contains(PseudoClassFlags::OVER)
172            {
173                if let Some(pseudo_class) = cx.style.pseudo_classes.get_mut(cx.current) {
174                    pseudo_class.set(PseudoClassFlags::OVER, true);
175
176                    cx.needs_restyle();
177                }
178            }
179        } else if cx
180            .style
181            .pseudo_classes
182            .get(cx.current)
183            .copied()
184            .unwrap_or_default()
185            .contains(PseudoClassFlags::OVER)
186        {
187            if let Some(pseudo_class) = cx.style.pseudo_classes.get_mut(cx.current) {
188                pseudo_class.set(PseudoClassFlags::OVER, false);
189
190                cx.needs_restyle();
191            }
192        }
193    }
194
195    let child_iter = DrawChildIterator::new(cx.tree, cx.current);
196    for child in child_iter {
197        cx.current = child;
198        hover_entity(cx, current_z, pointer_events, queue, hovered, transform, &clipping);
199    }
200}
201
202struct ZEntity {
203    pub index: i32,
204    pub pointer_events: bool,
205    pub entity: Entity,
206}
207
208impl Ord for ZEntity {
209    fn cmp(&self, other: &Self) -> Ordering {
210        other.index.cmp(&self.index)
211    }
212}
213impl PartialOrd for ZEntity {
214    fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
215        Some(self.cmp(other))
216    }
217}
218impl PartialEq for ZEntity {
219    fn eq(&self, other: &Self) -> bool {
220        self.index == other.index
221    }
222}
223
224impl Eq for ZEntity {}