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
8pub 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: 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 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 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 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 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 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 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 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 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 {}