1use std::{
4 ops,
5 sync::{Arc, atomic::AtomicUsize},
6};
7
8use serde::{Deserialize, Serialize};
9
10use crate::{
11 AlphaType, BorderSide, ExtendMode, GradientStop, ImageRendering, LineOrientation, LineStyle, MixBlendMode, ReferenceFrameId,
12 RepeatMode, TransformStyle,
13 api_extension::{ApiExtensionId, ApiExtensionPayload},
14 font::{FontId, GlyphInstance, GlyphOptions},
15 image::ImageTextureId,
16 window::FrameId,
17};
18use zng_unit::*;
19
20#[derive(Debug)]
22pub struct DisplayListBuilder {
23 frame_id: FrameId,
24 list: Vec<DisplayItem>,
25
26 clip_len: usize,
27 mask_len: usize,
28 space_len: usize,
29 stacking_len: usize,
30
31 seg_id: SegmentId,
32 seg_id_gen: Arc<AtomicUsize>,
33 segments: Vec<(SegmentId, usize)>,
34 has_reuse_ranges: bool,
35}
36impl DisplayListBuilder {
37 pub fn new(frame_id: FrameId) -> Self {
39 Self::with_capacity(frame_id, 100)
40 }
41
42 pub fn with_capacity(frame_id: FrameId, capacity: usize) -> Self {
44 Self {
45 frame_id,
46 list: Vec::with_capacity(capacity),
47
48 clip_len: 1,
49 mask_len: 1,
50 space_len: 1,
51 stacking_len: 1,
52 seg_id: 0,
53 seg_id_gen: Arc::new(AtomicUsize::new(1)),
54 segments: vec![(0, 0)],
55 has_reuse_ranges: false,
56 }
57 }
58
59 pub fn frame_id(&self) -> FrameId {
61 self.frame_id
62 }
63
64 pub fn start_reuse_range(&mut self) -> ReuseStart {
70 ReuseStart {
71 frame_id: self.frame_id,
72 seg_id: self.seg_id,
73 start: self.list.len(),
74 clip_len: self.clip_len,
75 mask_len: self.mask_len,
76 space_len: self.space_len,
77 stacking_len: self.stacking_len,
78 }
79 }
80
81 pub fn finish_reuse_range(&mut self, start: ReuseStart) -> ReuseRange {
92 assert_eq!(self.frame_id, start.frame_id, "reuse range not started by the same builder");
93 assert_eq!(self.seg_id, start.seg_id, "reuse range not started by the same builder");
94 assert_eq!(
95 self.clip_len, start.clip_len,
96 "reuse range cannot finish before all clips pushed inside it are popped"
97 );
98 assert_eq!(
99 self.mask_len, start.mask_len,
100 "reuse range cannot finish before all masks pushed inside it are popped"
101 );
102 assert_eq!(
103 self.space_len, start.space_len,
104 "reuse range cannot finish before all reference frames pushed inside it are popped"
105 );
106 assert_eq!(
107 self.stacking_len, start.stacking_len,
108 "reuse range cannot finish before all stacking contexts pushed inside it are popped"
109 );
110 debug_assert!(start.start <= self.list.len());
111
112 self.has_reuse_ranges = true;
113
114 ReuseRange {
115 frame_id: self.frame_id,
116 seg_id: self.seg_id,
117 start: start.start,
118 end: self.list.len(),
119 }
120 }
121
122 pub fn push_reuse_range(&mut self, range: &ReuseRange) {
126 if !range.is_empty() {
127 self.list.push(DisplayItem::Reuse {
128 frame_id: range.frame_id,
129 seg_id: range.seg_id,
130 start: range.start,
131 end: range.end,
132 });
133 }
134 }
135
136 pub fn push_reference_frame(
143 &mut self,
144 key: ReferenceFrameId,
145 transform: FrameValue<PxTransform>,
146 transform_style: TransformStyle,
147 is_2d_scale_translation: bool,
148 ) {
149 debug_assert!(key.is_app_generated());
150
151 self.space_len += 1;
152 self.list.push(DisplayItem::PushReferenceFrame {
153 id: key,
154 transform,
155 transform_style,
156 is_2d_scale_translation,
157 });
158 }
159
160 pub fn pop_reference_frame(&mut self) {
164 debug_assert!(self.space_len > 1);
165 self.space_len -= 1;
166 self.list.push(DisplayItem::PopReferenceFrame);
167 }
168
169 pub fn push_stacking_context(&mut self, blend_mode: MixBlendMode, transform_style: TransformStyle, filters: &[FilterOp]) {
175 self.stacking_len += 1;
176 self.list.push(DisplayItem::PushStackingContext {
177 blend_mode,
178 transform_style,
179 filters: filters.to_vec().into_boxed_slice(),
180 })
181 }
182
183 pub fn pop_stacking_context(&mut self) {
187 debug_assert!(self.stacking_len > 1);
188 self.stacking_len -= 1;
189 self.list.push(DisplayItem::PopStackingContext);
190 }
191
192 pub fn push_clip_rect(&mut self, clip_rect: PxRect, clip_out: bool) {
196 self.clip_len += 1;
197 self.list.push(DisplayItem::PushClipRect { clip_rect, clip_out });
198 }
199
200 pub fn push_clip_rounded_rect(&mut self, clip_rect: PxRect, corners: PxCornerRadius, clip_out: bool) {
206 self.clip_len += 1;
207 self.list.push(DisplayItem::PushClipRoundedRect {
208 clip_rect,
209 corners,
210 clip_out,
211 });
212 }
213
214 pub fn pop_clip(&mut self) {
219 debug_assert!(self.clip_len > 1);
220 self.clip_len -= 1;
221 self.list.push(DisplayItem::PopClip);
222 }
223
224 pub fn push_mask(&mut self, image_id: ImageTextureId, rect: PxRect) {
228 self.mask_len += 1;
229 self.list.push(DisplayItem::PushMask { image_id, rect })
230 }
231
232 pub fn pop_mask(&mut self) {
237 debug_assert!(self.mask_len > 1);
238 self.mask_len -= 1;
239 self.list.push(DisplayItem::PopMask);
240 }
241
242 #[expect(clippy::too_many_arguments)]
244 pub fn push_border(
245 &mut self,
246 bounds: PxRect,
247 widths: PxSideOffsets,
248 top: BorderSide,
249 right: BorderSide,
250 bottom: BorderSide,
251 left: BorderSide,
252 radius: PxCornerRadius,
253 ) {
254 self.list.push(DisplayItem::Border {
255 bounds,
256 widths,
257 sides: [top, right, bottom, left],
258 radius,
259 })
260 }
261
262 #[expect(clippy::too_many_arguments)]
264 pub fn push_nine_patch_border(
265 &mut self,
266 bounds: PxRect,
267 source: NinePatchSource,
268 widths: PxSideOffsets,
269 img_size: PxSize,
270 slice: PxSideOffsets,
271 fill: bool,
272 repeat_horizontal: RepeatMode,
273 repeat_vertical: RepeatMode,
274 ) {
275 self.list.push(DisplayItem::NinePatchBorder {
276 bounds,
277 source,
278 widths,
279 img_size,
280 slice,
281 fill,
282 repeat_horizontal,
283 repeat_vertical,
284 })
285 }
286
287 pub fn push_text(
289 &mut self,
290 clip_rect: PxRect,
291 font_id: FontId,
292 glyphs: &[GlyphInstance],
293 color: FrameValue<Rgba>,
294 options: GlyphOptions,
295 ) {
296 self.list.push(DisplayItem::Text {
297 clip_rect,
298 font_id,
299 glyphs: glyphs.to_vec().into_boxed_slice(),
300 color,
301 options,
302 });
303 }
304
305 #[expect(clippy::too_many_arguments)]
307 pub fn push_image(
308 &mut self,
309 clip_rect: PxRect,
310 image_id: ImageTextureId,
311 image_size: PxSize,
312 tile_size: PxSize,
313 tile_spacing: PxSize,
314 rendering: ImageRendering,
315 alpha_type: AlphaType,
316 ) {
317 self.list.push(DisplayItem::Image {
318 clip_rect,
319 image_id,
320 image_size,
321 rendering,
322 alpha_type,
323 tile_size,
324 tile_spacing,
325 })
326 }
327
328 pub fn push_color(&mut self, clip_rect: PxRect, color: FrameValue<Rgba>) {
330 self.list.push(DisplayItem::Color { clip_rect, color })
331 }
332
333 pub fn push_backdrop_filter(&mut self, clip_rect: PxRect, filters: &[FilterOp]) {
335 self.list.push(DisplayItem::BackdropFilter {
336 clip_rect,
337 filters: filters.to_vec().into_boxed_slice(),
338 })
339 }
340
341 #[expect(clippy::too_many_arguments)]
343 pub fn push_linear_gradient(
344 &mut self,
345 clip_rect: PxRect,
346 start_point: euclid::Point2D<f32, Px>,
347 end_point: euclid::Point2D<f32, Px>,
348 extend_mode: ExtendMode,
349 stops: &[GradientStop],
350 tile_origin: PxPoint,
351 tile_size: PxSize,
352 tile_spacing: PxSize,
353 ) {
354 self.list.push(DisplayItem::LinearGradient {
355 clip_rect,
356 start_point,
357 end_point,
358 extend_mode,
359 stops: stops.to_vec().into_boxed_slice(),
360 tile_origin,
361 tile_size,
362 tile_spacing,
363 })
364 }
365
366 #[expect(clippy::too_many_arguments)]
368 pub fn push_radial_gradient(
369 &mut self,
370 clip_rect: PxRect,
371 center: euclid::Point2D<f32, Px>,
372 radius: euclid::Size2D<f32, Px>,
373 start_offset: f32,
374 end_offset: f32,
375 extend_mode: ExtendMode,
376 stops: &[GradientStop],
377 tile_origin: PxPoint,
378 tile_size: PxSize,
379 tile_spacing: PxSize,
380 ) {
381 self.list.push(DisplayItem::RadialGradient {
382 clip_rect,
383 center,
384 radius,
385 start_offset,
386 end_offset,
387 extend_mode,
388 stops: stops.to_vec().into_boxed_slice(),
389 tile_origin,
390 tile_size,
391 tile_spacing,
392 });
393 }
394
395 #[expect(clippy::too_many_arguments)]
397 pub fn push_conic_gradient(
398 &mut self,
399 clip_rect: PxRect,
400 center: euclid::Point2D<f32, Px>,
401 angle: AngleRadian,
402 start_offset: f32,
403 end_offset: f32,
404 extend_mode: ExtendMode,
405 stops: &[GradientStop],
406 tile_origin: PxPoint,
407 tile_size: PxSize,
408 tile_spacing: PxSize,
409 ) {
410 self.list.push(DisplayItem::ConicGradient {
411 clip_rect,
412 center,
413 angle,
414 start_offset,
415 end_offset,
416 extend_mode,
417 stops: stops.to_vec().into_boxed_slice(),
418 tile_origin,
419 tile_size,
420 tile_spacing,
421 });
422 }
423
424 pub fn push_line(&mut self, clip_rect: PxRect, color: Rgba, style: LineStyle, orientation: LineOrientation) {
426 self.list.push(DisplayItem::Line {
427 clip_rect,
428 color,
429 style,
430 orientation,
431 })
432 }
433
434 pub fn push_extension(&mut self, extension_id: ApiExtensionId, payload: ApiExtensionPayload) {
448 self.list.push(DisplayItem::PushExtension { extension_id, payload })
449 }
450
451 pub fn pop_extension(&mut self, extension_id: ApiExtensionId) {
455 self.list.push(DisplayItem::PopExtension { extension_id })
456 }
457
458 pub fn set_backface_visibility(&mut self, visible: bool) {
460 self.list.push(DisplayItem::SetBackfaceVisibility { visible })
461 }
462
463 pub fn len(&self) -> usize {
465 self.list.len()
466 }
467
468 pub fn is_empty(&self) -> bool {
470 self.list.is_empty()
471 }
472
473 pub fn parallel_split(&self) -> Self {
478 Self {
479 frame_id: self.frame_id,
480 list: vec![],
481 clip_len: 1,
482 mask_len: 1,
483 space_len: 1,
484 stacking_len: 1,
485 seg_id: self.seg_id_gen.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
486 seg_id_gen: self.seg_id_gen.clone(),
487 segments: vec![],
488 has_reuse_ranges: false,
489 }
490 }
491
492 pub fn parallel_fold(&mut self, mut split: Self) {
503 assert!(
504 Arc::ptr_eq(&self.seg_id_gen, &split.seg_id_gen),
505 "cannot fold list not split from this one or parent"
506 );
507 assert_eq!(split.space_len, 1);
508 assert_eq!(split.clip_len, 1);
509 assert_eq!(split.mask_len, 1);
510 assert_eq!(split.stacking_len, 1);
511
512 if !self.list.is_empty() {
513 for (_, offset) in &mut split.segments {
514 *offset += self.list.len();
515 }
516 }
517
518 if self.segments.is_empty() {
519 self.segments = split.segments;
520 } else {
521 self.segments.append(&mut split.segments);
522 }
523 if split.has_reuse_ranges {
524 self.segments.push((split.seg_id, self.list.len()));
525 }
526
527 if self.list.is_empty() {
528 self.list = split.list;
529 } else {
530 self.list.append(&mut split.list);
531 }
532 }
533
534 pub fn finalize(self) -> DisplayList {
536 DisplayList {
537 frame_id: self.frame_id,
538 list: self.list,
539 segments: self.segments,
540 }
541 }
542}
543
544pub type SegmentId = usize;
546
547pub struct ReuseStart {
551 frame_id: FrameId,
552 seg_id: SegmentId,
553 start: usize,
554
555 clip_len: usize,
556 mask_len: usize,
557 space_len: usize,
558 stacking_len: usize,
559}
560
561#[derive(Debug, Clone)]
567pub struct ReuseRange {
568 frame_id: FrameId,
569 seg_id: SegmentId,
570 start: usize,
571 end: usize,
572}
573impl ReuseRange {
574 pub fn frame_id(&self) -> FrameId {
576 self.frame_id
577 }
578
579 pub fn is_empty(&self) -> bool {
581 self.start == self.end
582 }
583}
584
585#[derive(Debug, Clone, Serialize, Deserialize)]
589pub struct DisplayList {
590 frame_id: FrameId,
591 list: Vec<DisplayItem>,
592 segments: Vec<(SegmentId, usize)>,
593}
594impl DisplayList {
595 pub fn frame_id(&self) -> FrameId {
597 self.frame_id
598 }
599
600 pub fn into_parts(self) -> (FrameId, Vec<DisplayItem>, Vec<(SegmentId, usize)>) {
602 (self.frame_id, self.list, self.segments)
603 }
604}
605impl ops::Deref for DisplayList {
606 type Target = [DisplayItem];
607
608 fn deref(&self) -> &Self::Target {
609 &self.list
610 }
611}
612
613#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
619#[serde(transparent)]
620pub struct FrameValueId(u64);
621
622impl FrameValueId {
623 pub const INVALID: Self = Self(0);
625 pub const fn first() -> Self {
627 Self(1)
628 }
629 #[must_use]
635 pub const fn next(self) -> Self {
636 let r = Self(self.0.wrapping_add(1));
637 if r.0 == Self::INVALID.0 { Self::first() } else { r }
638 }
639 #[must_use]
643 pub fn incr(&mut self) -> Self {
644 std::mem::replace(self, self.next())
645 }
646 pub const fn get(self) -> u64 {
648 self.0
649 }
650 pub const fn from_raw(id: u64) -> Self {
661 Self(id)
662 }
663}
664
665#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
669pub enum FrameValue<T> {
670 Bind {
672 id: FrameValueId,
674 value: T,
676 animating: bool,
678 },
679 Value(T),
681}
682impl<T> FrameValue<T> {
683 pub fn value(&self) -> &T {
685 match self {
686 FrameValue::Bind { value, .. } | FrameValue::Value(value) => value,
687 }
688 }
689
690 pub fn into_value(self) -> T {
692 match self {
693 FrameValue::Bind { value, .. } | FrameValue::Value(value) => value,
694 }
695 }
696
697 fn update_bindable(value: &mut T, animating: &mut bool, update: &FrameValueUpdate<T>) -> bool
699 where
700 T: PartialEq + Copy,
701 {
702 let need_frame = (*animating != update.animating) || (!*animating && *value != update.value);
710
711 *animating = update.animating;
712 *value = update.value;
713
714 need_frame
715 }
716
717 fn update_value(value: &mut T, update: &FrameValueUpdate<T>) -> bool
719 where
720 T: PartialEq + Copy,
721 {
722 if value != &update.value {
723 *value = update.value;
724 true
725 } else {
726 false
727 }
728 }
729}
730impl<T> From<T> for FrameValue<T> {
731 fn from(value: T) -> Self {
732 FrameValue::Value(value)
733 }
734}
735
736#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
738pub struct FrameValueUpdate<T> {
739 pub id: FrameValueId,
741 pub value: T,
743 pub animating: bool,
745}
746
747#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
749pub enum FilterOp {
750 Blur(f32, f32),
752 Brightness(f32),
754 Contrast(f32),
756 Grayscale(f32),
758 HueRotate(f32),
760 Invert(f32),
762 Opacity(FrameValue<f32>),
764 Saturate(f32),
766 Sepia(f32),
768 DropShadow {
770 offset: euclid::Vector2D<f32, Px>,
772 color: Rgba,
774 blur_radius: f32,
776 },
777 ColorMatrix([f32; 20]),
781 Flood(Rgba),
783}
784
785#[allow(missing_docs)]
787#[derive(Debug, Clone, Serialize, Deserialize)]
788pub enum DisplayItem {
789 Reuse {
790 frame_id: FrameId,
791 seg_id: SegmentId,
792 start: usize,
793 end: usize,
794 },
795 PushReferenceFrame {
796 id: ReferenceFrameId,
797 transform: FrameValue<PxTransform>,
798 transform_style: TransformStyle,
799 is_2d_scale_translation: bool,
800 },
801 PopReferenceFrame,
802
803 PushStackingContext {
804 transform_style: TransformStyle,
805 blend_mode: MixBlendMode,
806 filters: Box<[FilterOp]>,
807 },
808 PopStackingContext,
809
810 PushClipRect {
811 clip_rect: PxRect,
812 clip_out: bool,
813 },
814 PushClipRoundedRect {
815 clip_rect: PxRect,
816 corners: PxCornerRadius,
817 clip_out: bool,
818 },
819 PopClip,
820 PushMask {
821 image_id: ImageTextureId,
822 rect: PxRect,
823 },
824 PopMask,
825
826 Border {
827 bounds: PxRect,
828 widths: PxSideOffsets,
829 sides: [BorderSide; 4],
830 radius: PxCornerRadius,
831 },
832 NinePatchBorder {
833 bounds: PxRect,
834 source: NinePatchSource,
835 widths: PxSideOffsets,
836 img_size: PxSize,
837 slice: PxSideOffsets,
838 fill: bool,
839 repeat_horizontal: RepeatMode,
840 repeat_vertical: RepeatMode,
841 },
842
843 Text {
844 clip_rect: PxRect,
845 font_id: FontId,
846 glyphs: Box<[GlyphInstance]>,
847 color: FrameValue<Rgba>,
848 options: GlyphOptions,
849 },
850
851 Image {
852 clip_rect: PxRect,
853 image_id: ImageTextureId,
854 image_size: PxSize,
855 rendering: ImageRendering,
856 alpha_type: AlphaType,
857 tile_size: PxSize,
858 tile_spacing: PxSize,
859 },
860
861 Color {
862 clip_rect: PxRect,
863 color: FrameValue<Rgba>,
864 },
865 BackdropFilter {
866 clip_rect: PxRect,
867 filters: Box<[FilterOp]>,
868 },
869
870 LinearGradient {
871 clip_rect: PxRect,
872 start_point: euclid::Point2D<f32, Px>,
873 end_point: euclid::Point2D<f32, Px>,
874 extend_mode: ExtendMode,
875 stops: Box<[GradientStop]>,
876 tile_origin: PxPoint,
877 tile_size: PxSize,
878 tile_spacing: PxSize,
879 },
880 RadialGradient {
881 clip_rect: PxRect,
882 center: euclid::Point2D<f32, Px>,
883 radius: euclid::Size2D<f32, Px>,
884 start_offset: f32,
885 end_offset: f32,
886 extend_mode: ExtendMode,
887 stops: Box<[GradientStop]>,
888 tile_origin: PxPoint,
889 tile_size: PxSize,
890 tile_spacing: PxSize,
891 },
892 ConicGradient {
893 clip_rect: PxRect,
894 center: euclid::Point2D<f32, Px>,
895 angle: AngleRadian,
896 start_offset: f32,
897 end_offset: f32,
898 extend_mode: ExtendMode,
899 stops: Box<[GradientStop]>,
900 tile_origin: PxPoint,
901 tile_size: PxSize,
902 tile_spacing: PxSize,
903 },
904
905 Line {
906 clip_rect: PxRect,
907 color: Rgba,
908 style: LineStyle,
909 orientation: LineOrientation,
910 },
911
912 PushExtension {
913 extension_id: ApiExtensionId,
914 payload: ApiExtensionPayload,
915 },
916 PopExtension {
917 extension_id: ApiExtensionId,
918 },
919
920 SetBackfaceVisibility {
921 visible: bool,
922 },
923}
924impl DisplayItem {
925 pub fn update_transform(&mut self, t: &FrameValueUpdate<PxTransform>) -> bool {
927 match self {
928 DisplayItem::PushReferenceFrame {
929 transform:
930 FrameValue::Bind {
931 id,
932 value,
933 animating: animation,
934 },
935 ..
936 } if *id == t.id => FrameValue::update_bindable(value, animation, t),
937 _ => false,
938 }
939 }
940
941 pub fn update_float(&mut self, t: &FrameValueUpdate<f32>) -> bool {
943 match self {
944 DisplayItem::PushStackingContext { filters, .. } => {
945 let mut new_frame = false;
946 for filter in filters.iter_mut() {
947 match filter {
948 FilterOp::Opacity(FrameValue::Bind {
949 id,
950 value,
951 animating: animation,
952 }) if *id == t.id => {
953 new_frame |= FrameValue::update_bindable(value, animation, t);
954 }
955 _ => {}
956 }
957 }
958 new_frame
959 }
960 _ => false,
961 }
962 }
963
964 pub fn update_color(&mut self, t: &FrameValueUpdate<Rgba>) -> bool {
966 match self {
967 DisplayItem::Color {
968 color:
969 FrameValue::Bind {
970 id,
971 value,
972 animating: animation,
973 },
974 ..
975 } if *id == t.id => FrameValue::update_bindable(value, animation, t),
976 DisplayItem::Text {
977 color: FrameValue::Bind { id, value, .. },
978 ..
979 } if *id == t.id => FrameValue::update_value(value, t),
980 _ => false,
981 }
982 }
983}
984
985#[allow(missing_docs)]
987#[derive(Debug, Clone, Serialize, Deserialize)]
988pub enum NinePatchSource {
989 Image {
990 image_id: ImageTextureId,
991 rendering: ImageRendering,
992 },
993 LinearGradient {
994 start_point: euclid::Point2D<f32, Px>,
995 end_point: euclid::Point2D<f32, Px>,
996 extend_mode: ExtendMode,
997 stops: Box<[GradientStop]>,
998 },
999 RadialGradient {
1000 center: euclid::Point2D<f32, Px>,
1001 radius: euclid::Size2D<f32, Px>,
1002 start_offset: f32,
1003 end_offset: f32,
1004 extend_mode: ExtendMode,
1005 stops: Box<[GradientStop]>,
1006 },
1007 ConicGradient {
1008 center: euclid::Point2D<f32, Px>,
1009 angle: AngleRadian,
1010 start_offset: f32,
1011 end_offset: f32,
1012 extend_mode: ExtendMode,
1013 stops: Box<[GradientStop]>,
1014 },
1015}