1use std::sync::{Arc, atomic::AtomicUsize};
2
3use zng_layout::unit::{PxBox, PxCornerRadius, PxPoint, PxSize, PxTransform, PxVector};
4use zng_view_api::display_list::{FrameValue, FrameValueUpdate};
5
6use crate::widget::WidgetId;
7
8#[derive(Debug, Default)]
10pub(crate) struct HitTestClips {
11 items: Vec<HitTestItem>,
12 segments: ParallelSegmentOffsets,
13}
14impl HitTestClips {
15 pub fn is_hit_testable(&self) -> bool {
17 !self.items.is_empty()
18 }
19
20 pub fn push_rect(&mut self, rect: PxBox) {
21 self.items.push(HitTestItem::Hit(HitTestPrimitive::Rect(rect)));
22 }
23
24 pub fn push_clip_rect(&mut self, clip_rect: PxBox, clip_out: bool) {
25 self.items.push(HitTestItem::Clip(HitTestPrimitive::Rect(clip_rect), clip_out));
26 }
27
28 pub fn push_rounded_rect(&mut self, rect: PxBox, mut radii: PxCornerRadius) {
29 if radii == PxCornerRadius::zero() {
30 self.push_rect(rect);
31 } else {
32 ensure_no_corner_overlap(&mut radii, rect.size());
33 self.items.push(HitTestItem::Hit(HitTestPrimitive::RoundedRect(rect, radii)));
34 }
35 }
36
37 pub fn push_clip_rounded_rect(&mut self, clip_rect: PxBox, mut radii: PxCornerRadius, clip_out: bool) {
38 if radii == PxCornerRadius::zero() {
39 self.push_clip_rect(clip_rect, clip_out);
40 } else {
41 ensure_no_corner_overlap(&mut radii, clip_rect.size());
42 self.items
43 .push(HitTestItem::Clip(HitTestPrimitive::RoundedRect(clip_rect, radii), clip_out));
44 }
45 }
46
47 pub fn push_ellipse(&mut self, center: PxPoint, radii: PxSize) {
48 self.items.push(HitTestItem::Hit(HitTestPrimitive::Ellipse(center, radii)));
49 }
50
51 pub fn push_clip_ellipse(&mut self, center: PxPoint, radii: PxSize, clip_out: bool) {
52 self.items
53 .push(HitTestItem::Clip(HitTestPrimitive::Ellipse(center, radii), clip_out));
54 }
55
56 pub fn pop_clip(&mut self) {
57 self.items.push(HitTestItem::PopClip);
58 }
59
60 pub fn push_transform(&mut self, transform: FrameValue<PxTransform>) {
61 self.items.push(HitTestItem::Transform(transform))
62 }
63
64 pub fn pop_transform(&mut self) {
65 self.items.push(HitTestItem::PopTransform);
66 }
67
68 #[must_use]
69 pub fn push_child(&mut self, widget: WidgetId) -> HitChildIndex {
70 if let Some(HitTestItem::Child(c)) = self.items.last_mut() {
71 *c = widget;
72 } else {
73 self.items.push(HitTestItem::Child(widget));
74 }
75 HitChildIndex(self.segments.id(), self.items.len() - 1)
76 }
77
78 pub fn hit_test_z(&self, inner_transform: &PxTransform, window_point: PxPoint) -> RelativeHitZ {
80 let mut z = RelativeHitZ::NoHit;
81 let mut child = None;
82
83 let mut transform_stack = vec![];
84 let mut current_transform = inner_transform;
85 let mut local_point = match inv_transform_point(current_transform, window_point) {
86 Some(p) => p,
87 None => return RelativeHitZ::NoHit,
88 };
89
90 let mut items = self.items.iter();
91
92 'hit_test: while let Some(item) = items.next() {
93 match item {
94 HitTestItem::Hit(prim) => {
95 if prim.contains(local_point) {
96 z = if let Some(inner) = child {
97 RelativeHitZ::Over(inner)
98 } else {
99 RelativeHitZ::Back
100 };
101 }
102 }
103
104 HitTestItem::Clip(prim, clip_out) => {
105 let skip = match clip_out {
106 true => prim.contains(local_point),
107 false => !prim.contains(local_point),
108 };
109
110 if skip {
111 let mut clip_depth = 0;
113 'skip_clipped: for item in items.by_ref() {
114 match item {
115 HitTestItem::Clip(_, _) => {
116 clip_depth += 1;
117 }
118 HitTestItem::PopClip => {
119 if clip_depth == 0 {
120 continue 'hit_test;
121 }
122 clip_depth -= 1;
123 }
124 HitTestItem::Child(w) => {
125 child = Some(*w);
126 continue 'skip_clipped;
127 }
128 _ => continue 'skip_clipped,
129 }
130 }
131 }
132 }
133 HitTestItem::PopClip => continue 'hit_test,
134
135 HitTestItem::Transform(t) => {
136 let t = t.value();
137 match inv_transform_point(t, local_point) {
138 Some(p) => {
139 transform_stack.push((current_transform, local_point));
141 current_transform = t;
142 local_point = p;
143 }
144 None => {
145 let mut transform_depth = 0;
147 'skip_transformed: for item in items.by_ref() {
148 match item {
149 HitTestItem::Transform(_) => {
150 transform_depth += 1;
151 }
152 HitTestItem::PopTransform => {
153 if transform_depth == 0 {
154 continue 'hit_test;
155 }
156 transform_depth -= 1;
157 }
158 HitTestItem::Child(w) => {
159 child = Some(*w);
160 continue 'skip_transformed;
161 }
162 _ => continue 'skip_transformed,
163 }
164 }
165 }
166 }
167 }
168 HitTestItem::PopTransform => {
169 (current_transform, local_point) = transform_stack.pop().unwrap();
170 }
171
172 HitTestItem::Child(w) => {
173 child = Some(*w);
174 }
175 }
176 }
177
178 if let RelativeHitZ::Over(w) = z
179 && let Some(c) = child
180 && w == c
181 {
182 return RelativeHitZ::Front;
183 }
184 z
185 }
186
187 pub fn update_transform(&mut self, value: FrameValueUpdate<PxTransform>) {
188 for item in &mut self.items {
189 if let HitTestItem::Transform(FrameValue::Bind { id, value: t, .. }) = item
190 && *id == value.id
191 {
192 *t = value.value;
193 break;
194 }
195 }
196 }
197
198 pub fn clip_child(&self, child: HitChildIndex, inner_transform: &PxTransform, window_point: PxPoint) -> bool {
200 let mut transform_stack = vec![];
201 let mut current_transform = inner_transform;
202 let mut local_point = match inv_transform_point(current_transform, window_point) {
203 Some(p) => p,
204 None => return false,
205 };
206
207 let child = child.1 + self.segments.offset(child.0);
208
209 let mut items = self.items[..child].iter();
210 let mut clip = false;
211
212 'clip: while let Some(item) = items.next() {
213 match item {
214 HitTestItem::Clip(prim, clip_out) => {
215 clip = match clip_out {
216 true => prim.contains(local_point),
217 false => !prim.contains(local_point),
218 };
219 if clip {
220 let mut clip_depth = 0;
221 'close_clip: for item in items.by_ref() {
222 match item {
223 HitTestItem::Clip(_, _) => clip_depth += 1,
224 HitTestItem::PopClip => {
225 if clip_depth == 0 {
226 clip = false; continue 'clip;
228 }
229 clip_depth -= 1;
230 }
231 _ => continue 'close_clip,
232 }
233 }
234 }
235 }
236 HitTestItem::PopClip => continue 'clip,
237 HitTestItem::Transform(t) => {
238 let t = t.value();
239 match inv_transform_point(t, local_point) {
240 Some(p) => {
241 transform_stack.push((current_transform, local_point));
243 current_transform = t;
244 local_point = p;
245 }
246 None => {
247 let mut transform_depth = 0;
249 'skip_transformed: for item in items.by_ref() {
250 match item {
251 HitTestItem::Transform(_) => {
252 transform_depth += 1;
253 }
254 HitTestItem::PopTransform => {
255 if transform_depth == 0 {
256 continue 'clip;
257 }
258 transform_depth -= 1;
259 }
260 _ => continue 'skip_transformed,
261 }
262 }
263 }
264 }
265 }
266 HitTestItem::PopTransform => {
267 (current_transform, local_point) = transform_stack.pop().unwrap();
268 }
269 _ => continue 'clip,
270 }
271 }
272
273 clip
274 }
275
276 pub(crate) fn parallel_split(&self) -> Self {
277 Self {
278 items: vec![],
279 segments: self.segments.parallel_split(),
280 }
281 }
282
283 pub(crate) fn parallel_fold(&mut self, mut split: Self) {
284 self.segments.parallel_fold(split.segments, self.items.len());
285
286 if self.items.is_empty() {
287 self.items = split.items;
288 } else {
289 self.items.append(&mut split.items)
290 }
291 }
292}
293
294#[derive(Debug, Clone, Copy, PartialEq, Eq)]
296pub enum RelativeHitZ {
297 NoHit,
299 Back,
301 Over(WidgetId),
303 Front,
305}
306
307#[derive(Debug)]
308enum HitTestPrimitive {
309 Rect(PxBox),
310 RoundedRect(PxBox, PxCornerRadius),
311 Ellipse(PxPoint, PxSize),
312}
313impl HitTestPrimitive {
314 fn contains(&self, point: PxPoint) -> bool {
315 match self {
316 HitTestPrimitive::Rect(r) => r.contains(point),
317 HitTestPrimitive::RoundedRect(rect, radii) => rounded_rect_contains(rect, radii, point),
318 HitTestPrimitive::Ellipse(center, radii) => ellipse_contains(*radii, *center, point),
319 }
320 }
321}
322#[derive(Debug)]
323enum HitTestItem {
324 Hit(HitTestPrimitive),
325
326 Clip(HitTestPrimitive, bool),
327 PopClip,
328
329 Transform(FrameValue<PxTransform>),
330 PopTransform,
331
332 Child(WidgetId),
333}
334
335fn rounded_rect_contains(rect: &PxBox, radii: &PxCornerRadius, point: PxPoint) -> bool {
336 if !rect.contains(point) {
337 return false;
338 }
339
340 let top_left_center = rect.min + radii.top_left.to_vector();
341 if top_left_center.x > point.x && top_left_center.y > point.y && !ellipse_contains(radii.top_left, top_left_center, point) {
342 return false;
343 }
344
345 let bottom_right_center = rect.max - radii.bottom_right.to_vector();
346 if bottom_right_center.x < point.x
347 && bottom_right_center.y < point.y
348 && !ellipse_contains(radii.bottom_right, bottom_right_center, point)
349 {
350 return false;
351 }
352
353 let top_right = PxPoint::new(rect.max.x, rect.min.y);
354 let top_right_center = top_right + PxVector::new(-radii.top_right.width, radii.top_right.height);
355 if top_right_center.x < point.x && top_right_center.y > point.y && !ellipse_contains(radii.top_right, top_right_center, point) {
356 return false;
357 }
358
359 let bottom_left = PxPoint::new(rect.min.x, rect.max.y);
360 let bottom_left_center = bottom_left + PxVector::new(radii.bottom_left.width, -radii.bottom_left.height);
361 if bottom_left_center.x > point.x && bottom_left_center.y < point.y && !ellipse_contains(radii.bottom_left, bottom_left_center, point) {
362 return false;
363 }
364
365 true
366}
367
368fn ellipse_contains(radii: PxSize, center: PxPoint, point: PxPoint) -> bool {
369 let h = center.x.0 as f64;
370 let k = center.y.0 as f64;
371
372 let a = radii.width.0 as f64;
373 let b = radii.height.0 as f64;
374
375 let x = point.x.0 as f64;
376 let y = point.y.0 as f64;
377
378 let p = ((x - h).powi(2) / a.powi(2)) + ((y - k).powi(2) / b.powi(2));
379
380 p <= 1.0
381}
382
383pub fn ensure_no_corner_overlap(radii: &mut PxCornerRadius, size: PxSize) {
386 let mut ratio = 1.0;
387 let top_left_radius = &mut radii.top_left;
388 let top_right_radius = &mut radii.top_right;
389 let bottom_right_radius = &mut radii.bottom_right;
390 let bottom_left_radius = &mut radii.bottom_left;
391
392 let sum = top_left_radius.width + top_right_radius.width;
393 if size.width < sum {
394 ratio = f32::min(ratio, size.width.0 as f32 / sum.0 as f32);
395 }
396
397 let sum = bottom_left_radius.width + bottom_right_radius.width;
398 if size.width < sum {
399 ratio = f32::min(ratio, size.width.0 as f32 / sum.0 as f32);
400 }
401
402 let sum = top_left_radius.height + bottom_left_radius.height;
403 if size.height < sum {
404 ratio = f32::min(ratio, size.height.0 as f32 / sum.0 as f32);
405 }
406
407 let sum = top_right_radius.height + bottom_right_radius.height;
408 if size.height < sum {
409 ratio = f32::min(ratio, size.height.0 as f32 / sum.0 as f32);
410 }
411
412 if ratio < 1. {
413 top_left_radius.width *= ratio;
414 top_left_radius.height *= ratio;
415
416 top_right_radius.width *= ratio;
417 top_right_radius.height *= ratio;
418
419 bottom_left_radius.width *= ratio;
420 bottom_left_radius.height *= ratio;
421
422 bottom_right_radius.width *= ratio;
423 bottom_right_radius.height *= ratio;
424 }
425}
426
427fn inv_transform_point(t: &PxTransform, point: PxPoint) -> Option<PxPoint> {
428 t.inverse()?.project_point(point)
429}
430
431#[derive(Debug, Clone, Copy)]
432pub(crate) struct HitChildIndex(ParallelSegmentId, usize);
433impl Default for HitChildIndex {
434 fn default() -> Self {
435 Self(ParallelSegmentId::MAX, Default::default())
436 }
437}
438
439pub(crate) type ParallelSegmentId = usize;
441
442#[derive(Debug, Clone)]
444pub(crate) struct ParallelSegmentOffsets {
445 id: ParallelSegmentId,
446 id_gen: Arc<AtomicUsize>,
447 used: bool,
448 segments: Vec<(ParallelSegmentId, usize)>,
449}
450impl Default for ParallelSegmentOffsets {
451 fn default() -> Self {
452 Self {
453 id: 0,
454 used: false,
455 id_gen: Arc::new(AtomicUsize::new(1)),
456 segments: vec![],
457 }
458 }
459}
460impl ParallelSegmentOffsets {
461 pub fn id(&mut self) -> ParallelSegmentId {
463 self.used = true;
464 self.id
465 }
466
467 pub fn offset(&self, id: ParallelSegmentId) -> usize {
469 self.segments
470 .iter()
471 .find_map(|&(i, o)| if i == id { Some(o) } else { None })
472 .unwrap_or_else(|| {
473 if id != 0 {
474 tracing::error!(target: "parallel", "segment offset for `{id}` not found");
475 }
476 0
477 })
478 }
479
480 pub fn parallel_split(&self) -> Self {
482 Self {
483 used: false,
484 id: self.id_gen.fetch_add(1, atomic::Ordering::Relaxed),
485 id_gen: self.id_gen.clone(),
486 segments: vec![],
487 }
488 }
489
490 pub fn parallel_fold(&mut self, mut split: Self, offset: usize) {
492 if !Arc::ptr_eq(&self.id_gen, &split.id_gen) {
493 tracing::error!(target: "parallel", "cannot parallel fold segments not split from the same root");
494 return;
495 }
496
497 if offset > 0 {
498 for (_, o) in &mut split.segments {
499 *o += offset;
500 }
501 }
502
503 if self.segments.is_empty() {
504 self.segments = split.segments;
505 } else {
506 self.segments.append(&mut split.segments);
507 }
508 if split.used {
509 self.segments.push((split.id, offset));
510 }
511 }
512}