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), Some(c)) = (z, child) {
179 if w == c {
180 return RelativeHitZ::Front;
181 }
182 }
183 z
184 }
185
186 pub fn update_transform(&mut self, value: FrameValueUpdate<PxTransform>) {
187 for item in &mut self.items {
188 if let HitTestItem::Transform(FrameValue::Bind { id, value: t, .. }) = item {
189 if *id == value.id {
190 *t = value.value;
191 break;
192 }
193 }
194 }
195 }
196
197 pub fn clip_child(&self, child: HitChildIndex, inner_transform: &PxTransform, window_point: PxPoint) -> bool {
199 let mut transform_stack = vec![];
200 let mut current_transform = inner_transform;
201 let mut local_point = match inv_transform_point(current_transform, window_point) {
202 Some(p) => p,
203 None => return false,
204 };
205
206 let child = child.1 + self.segments.offset(child.0);
207
208 let mut items = self.items[..child].iter();
209 let mut clip = false;
210
211 'clip: while let Some(item) = items.next() {
212 match item {
213 HitTestItem::Clip(prim, clip_out) => {
214 clip = match clip_out {
215 true => prim.contains(local_point),
216 false => !prim.contains(local_point),
217 };
218 if clip {
219 let mut clip_depth = 0;
220 'close_clip: for item in items.by_ref() {
221 match item {
222 HitTestItem::Clip(_, _) => clip_depth += 1,
223 HitTestItem::PopClip => {
224 if clip_depth == 0 {
225 clip = false; continue 'clip;
227 }
228 clip_depth -= 1;
229 }
230 _ => continue 'close_clip,
231 }
232 }
233 }
234 }
235 HitTestItem::PopClip => continue 'clip,
236 HitTestItem::Transform(t) => {
237 let t = t.value();
238 match inv_transform_point(t, local_point) {
239 Some(p) => {
240 transform_stack.push((current_transform, local_point));
242 current_transform = t;
243 local_point = p;
244 }
245 None => {
246 let mut transform_depth = 0;
248 'skip_transformed: for item in items.by_ref() {
249 match item {
250 HitTestItem::Transform(_) => {
251 transform_depth += 1;
252 }
253 HitTestItem::PopTransform => {
254 if transform_depth == 0 {
255 continue 'clip;
256 }
257 transform_depth -= 1;
258 }
259 _ => continue 'skip_transformed,
260 }
261 }
262 }
263 }
264 }
265 HitTestItem::PopTransform => {
266 (current_transform, local_point) = transform_stack.pop().unwrap();
267 }
268 _ => continue 'clip,
269 }
270 }
271
272 clip
273 }
274
275 pub(crate) fn parallel_split(&self) -> Self {
276 Self {
277 items: vec![],
278 segments: self.segments.parallel_split(),
279 }
280 }
281
282 pub(crate) fn parallel_fold(&mut self, mut split: Self) {
283 self.segments.parallel_fold(split.segments, self.items.len());
284
285 if self.items.is_empty() {
286 self.items = split.items;
287 } else {
288 self.items.append(&mut split.items)
289 }
290 }
291}
292
293#[derive(Debug, Clone, Copy, PartialEq, Eq)]
295pub enum RelativeHitZ {
296 NoHit,
298 Back,
300 Over(WidgetId),
302 Front,
304}
305
306#[derive(Debug)]
307enum HitTestPrimitive {
308 Rect(PxBox),
309 RoundedRect(PxBox, PxCornerRadius),
310 Ellipse(PxPoint, PxSize),
311}
312impl HitTestPrimitive {
313 fn contains(&self, point: PxPoint) -> bool {
314 match self {
315 HitTestPrimitive::Rect(r) => r.contains(point),
316 HitTestPrimitive::RoundedRect(rect, radii) => rounded_rect_contains(rect, radii, point),
317 HitTestPrimitive::Ellipse(center, radii) => ellipse_contains(*radii, *center, point),
318 }
319 }
320}
321#[derive(Debug)]
322enum HitTestItem {
323 Hit(HitTestPrimitive),
324
325 Clip(HitTestPrimitive, bool),
326 PopClip,
327
328 Transform(FrameValue<PxTransform>),
329 PopTransform,
330
331 Child(WidgetId),
332}
333
334fn rounded_rect_contains(rect: &PxBox, radii: &PxCornerRadius, point: PxPoint) -> bool {
335 if !rect.contains(point) {
336 return false;
337 }
338
339 let top_left_center = rect.min + radii.top_left.to_vector();
340 if top_left_center.x > point.x && top_left_center.y > point.y && !ellipse_contains(radii.top_left, top_left_center, point) {
341 return false;
342 }
343
344 let bottom_right_center = rect.max - radii.bottom_right.to_vector();
345 if bottom_right_center.x < point.x
346 && bottom_right_center.y < point.y
347 && !ellipse_contains(radii.bottom_right, bottom_right_center, point)
348 {
349 return false;
350 }
351
352 let top_right = PxPoint::new(rect.max.x, rect.min.y);
353 let top_right_center = top_right + PxVector::new(-radii.top_right.width, radii.top_right.height);
354 if top_right_center.x < point.x && top_right_center.y > point.y && !ellipse_contains(radii.top_right, top_right_center, point) {
355 return false;
356 }
357
358 let bottom_left = PxPoint::new(rect.min.x, rect.max.y);
359 let bottom_left_center = bottom_left + PxVector::new(radii.bottom_left.width, -radii.bottom_left.height);
360 if bottom_left_center.x > point.x && bottom_left_center.y < point.y && !ellipse_contains(radii.bottom_left, bottom_left_center, point) {
361 return false;
362 }
363
364 true
365}
366
367fn ellipse_contains(radii: PxSize, center: PxPoint, point: PxPoint) -> bool {
368 let h = center.x.0 as f64;
369 let k = center.y.0 as f64;
370
371 let a = radii.width.0 as f64;
372 let b = radii.height.0 as f64;
373
374 let x = point.x.0 as f64;
375 let y = point.y.0 as f64;
376
377 let p = ((x - h).powi(2) / a.powi(2)) + ((y - k).powi(2) / b.powi(2));
378
379 p <= 1.0
380}
381
382pub fn ensure_no_corner_overlap(radii: &mut PxCornerRadius, size: PxSize) {
385 let mut ratio = 1.0;
386 let top_left_radius = &mut radii.top_left;
387 let top_right_radius = &mut radii.top_right;
388 let bottom_right_radius = &mut radii.bottom_right;
389 let bottom_left_radius = &mut radii.bottom_left;
390
391 let sum = top_left_radius.width + top_right_radius.width;
392 if size.width < sum {
393 ratio = f32::min(ratio, size.width.0 as f32 / sum.0 as f32);
394 }
395
396 let sum = bottom_left_radius.width + bottom_right_radius.width;
397 if size.width < sum {
398 ratio = f32::min(ratio, size.width.0 as f32 / sum.0 as f32);
399 }
400
401 let sum = top_left_radius.height + bottom_left_radius.height;
402 if size.height < sum {
403 ratio = f32::min(ratio, size.height.0 as f32 / sum.0 as f32);
404 }
405
406 let sum = top_right_radius.height + bottom_right_radius.height;
407 if size.height < sum {
408 ratio = f32::min(ratio, size.height.0 as f32 / sum.0 as f32);
409 }
410
411 if ratio < 1. {
412 top_left_radius.width *= ratio;
413 top_left_radius.height *= ratio;
414
415 top_right_radius.width *= ratio;
416 top_right_radius.height *= ratio;
417
418 bottom_left_radius.width *= ratio;
419 bottom_left_radius.height *= ratio;
420
421 bottom_right_radius.width *= ratio;
422 bottom_right_radius.height *= ratio;
423 }
424}
425
426fn inv_transform_point(t: &PxTransform, point: PxPoint) -> Option<PxPoint> {
427 t.inverse()?.project_point(point)
428}
429
430#[derive(Debug, Clone, Copy)]
431pub(crate) struct HitChildIndex(ParallelSegmentId, usize);
432impl Default for HitChildIndex {
433 fn default() -> Self {
434 Self(ParallelSegmentId::MAX, Default::default())
435 }
436}
437
438pub(crate) type ParallelSegmentId = usize;
440
441#[derive(Debug, Clone)]
443pub(crate) struct ParallelSegmentOffsets {
444 id: ParallelSegmentId,
445 id_gen: Arc<AtomicUsize>,
446 used: bool,
447 segments: Vec<(ParallelSegmentId, usize)>,
448}
449impl Default for ParallelSegmentOffsets {
450 fn default() -> Self {
451 Self {
452 id: 0,
453 used: false,
454 id_gen: Arc::new(AtomicUsize::new(1)),
455 segments: vec![],
456 }
457 }
458}
459impl ParallelSegmentOffsets {
460 pub fn id(&mut self) -> ParallelSegmentId {
462 self.used = true;
463 self.id
464 }
465
466 pub fn offset(&self, id: ParallelSegmentId) -> usize {
468 self.segments
469 .iter()
470 .find_map(|&(i, o)| if i == id { Some(o) } else { None })
471 .unwrap_or_else(|| {
472 if id != 0 {
473 tracing::error!(target: "parallel", "segment offset for `{id}` not found");
474 }
475 0
476 })
477 }
478
479 pub fn parallel_split(&self) -> Self {
481 Self {
482 used: false,
483 id: self.id_gen.fetch_add(1, atomic::Ordering::Relaxed),
484 id_gen: self.id_gen.clone(),
485 segments: vec![],
486 }
487 }
488
489 pub fn parallel_fold(&mut self, mut split: Self, offset: usize) {
491 if !Arc::ptr_eq(&self.id_gen, &split.id_gen) {
492 tracing::error!(target: "parallel", "cannot parallel fold segments not split from the same root");
493 return;
494 }
495
496 if offset > 0 {
497 for (_, o) in &mut split.segments {
498 *o += offset;
499 }
500 }
501
502 if self.segments.is_empty() {
503 self.segments = split.segments;
504 } else {
505 self.segments.append(&mut split.segments);
506 }
507 if split.used {
508 self.segments.push((split.id, offset));
509 }
510 }
511}