zng_view_api/
display_list.rs

1//! Frame builder types.
2
3use 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/// Represents a builder for display items that will be rendered in the view process.
21#[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    /// New default.
38    pub fn new(frame_id: FrameId) -> Self {
39        Self::with_capacity(frame_id, 100)
40    }
41
42    /// New with pre-allocation.
43    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    /// Frame that will be rendered by this display list.
60    pub fn frame_id(&self) -> FrameId {
61        self.frame_id
62    }
63
64    /// Mark the start of a reuse range, the range can be completed with [`finish_reuse_range`].
65    ///
66    /// Reuse ranges can be nested.
67    ///
68    /// [`finish_reuse_range`]: Self::finish_reuse_range
69    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    /// Mark the end of a reuse range.
82    ///
83    /// # Panics
84    ///
85    /// Panics if `start` was not generated by a call to [`start_reuse_range`] on the same builder.
86    ///
87    /// Panics if clips, masks, reference frames or stacking contexts where pushed inside the reuse range and not popped
88    /// before the call to finish.
89    ///
90    /// [`start_reuse_range`]: Self::start_reuse_range
91    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    /// Push a range of items to be copied from the previous display list on the same pipeline.
123    ///
124    /// Panics if `range` does not have a compatible pipeline id.
125    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    /// Start a new spatial context, must be paired with a call to [`pop_reference_frame`].
137    ///
138    /// If `transform_style` is `Preserve3D` if extends the 3D context of the parent. If the parent
139    /// is not `Preserve3D` a stacking context with `Preserve3D` must be the next display item.
140    ///
141    /// [`pop_reference_frame`]: Self::pop_reference_frame
142    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    /// Finish the flat spatial context started by a call to [`push_reference_frame`].
161    ///
162    /// [`push_reference_frame`]: Self::push_reference_frame
163    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    /// Start a new filters context or extend 3D space, must be paired with a call to [`pop_stacking_context`].
170    ///
171    /// Note that `transform_style` is coerced to `Flat` if any filter is also set.
172    ///
173    /// [`pop_stacking_context`]: Self::pop_stacking_context
174    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    /// Finish the filters context started by a call to [`push_stacking_context`].
184    ///
185    /// [`push_stacking_context`]: Self::push_stacking_context
186    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    /// Push a rectangular clip that will affect all pushed items until a paired call to [`pop_clip`].
193    ///
194    /// [`pop_clip`]: Self::pop_clip
195    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    /// Push a rectangular clip with rounded corners that will affect all pushed items until a paired call to [`pop_clip`].
201    ///
202    /// If `clip_out` is `true` only pixels outside the rounded rect are visible.
203    ///
204    /// [`pop_clip`]: Self::pop_clip
205    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    /// Pop a clip previously pushed by a call to [`push_clip_rect`]. Items pushed after this call are not
215    /// clipped.
216    ///
217    /// [`push_clip_rect`]: Self::push_clip_rect
218    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    /// Push an image mask that will affect all pushed items until a paired call to [`pop_mask`].
225    ///
226    /// [`pop_mask`]: Self::pop_mask
227    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    /// Pop an image mask previously pushed by a call to [`push_mask`]. Items pushed after this call are not
233    /// masked.
234    ///
235    /// [`push_mask`]: Self::push_mask
236    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    /// Push a normal border.
243    #[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    /// Push a nine-patch border.
263    #[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    /// Push a text run.
288    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    /// Push an image.
306    #[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    /// Push a color rectangle.
329    pub fn push_color(&mut self, clip_rect: PxRect, color: FrameValue<Rgba>) {
330        self.list.push(DisplayItem::Color { clip_rect, color })
331    }
332
333    /// Push a filter that applies to all rendered pixels behind `clip_rect`.
334    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    /// Push a linear gradient rectangle.
342    #[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    /// Push a radial gradient rectangle.
367    #[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    /// Push a conic gradient rectangle.
396    #[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    /// Push a styled vertical or horizontal line.
425    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    /// Push a custom extension payload.
435    ///
436    /// This can be used by custom renderer implementations to support custom items defined in the context
437    /// of normal display items.
438    ///
439    /// There are two types of display items, normal items like [`push_color`] and context items like
440    /// [`push_clip_rect`]-[`pop_clip`], if the extension is a normal item only the `push_extension` method must
441    /// be called, if the extension is a context item the [`pop_extension`] must also be called.
442    ///
443    /// [`push_color`]: Self::push_color
444    /// [`push_clip_rect`]: Self::push_clip_rect
445    /// [`pop_clip`]: Self::pop_clip
446    /// [`pop_extension`]: Self::pop_extension
447    pub fn push_extension(&mut self, extension_id: ApiExtensionId, payload: ApiExtensionPayload) {
448        self.list.push(DisplayItem::PushExtension { extension_id, payload })
449    }
450
451    /// Pop an extension previously pushed.
452    ///
453    /// Only required if the extension implementation requires it, item extensions do not need to pop.
454    pub fn pop_extension(&mut self, extension_id: ApiExtensionId) {
455        self.list.push(DisplayItem::PopExtension { extension_id })
456    }
457
458    /// Sets the backface visibility of all display items after this call.
459    pub fn set_backface_visibility(&mut self, visible: bool) {
460        self.list.push(DisplayItem::SetBackfaceVisibility { visible })
461    }
462
463    /// Number of display items.
464    pub fn len(&self) -> usize {
465        self.list.len()
466    }
467
468    /// Returns `true` if the list has no display items.
469    pub fn is_empty(&self) -> bool {
470        self.list.is_empty()
471    }
472
473    /// Create a display list builder that can be send to be build in parallel and then folded back onto
474    /// this list using [`parallel_fold`].
475    ///
476    /// [`parallel_fold`]: Self::parallel_fold
477    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    /// Append the `split` onto `self`.
493    ///
494    /// # Panics
495    ///
496    /// Panics if `split` was not generated by a call to [`parallel_split`] on `self` or a
497    /// *parent* display list.
498    ///
499    /// Panics if `split` has not closed all reference frames, clips or stacking contexts that it opened.
500    ///
501    /// [`parallel_split`]: Self::parallel_split
502    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    /// Returns the display list.
535    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
544/// Id of the offset a parallel list folded onto the main list is at.
545pub type SegmentId = usize;
546
547/// Represents the start of a display list reuse range.
548///
549/// See [`DisplayListBuilder::start_reuse_range`] for more details.
550pub 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/// Represents a display list reuse range.
562///
563/// See [`DisplayListBuilder::push_reuse_range`] for more details.
564///
565/// [`DisplayListBuilder::push_reuse_range`]: crate::display_list::DisplayListBuilder::push_reuse_range
566#[derive(Debug, Clone)]
567pub struct ReuseRange {
568    frame_id: FrameId,
569    seg_id: SegmentId,
570    start: usize,
571    end: usize,
572}
573impl ReuseRange {
574    /// Frame that owns the reused items selected by this range.
575    pub fn frame_id(&self) -> FrameId {
576        self.frame_id
577    }
578
579    /// If the reuse range did not capture any display item.
580    pub fn is_empty(&self) -> bool {
581        self.start == self.end
582    }
583}
584
585/// Represents a finalized display list.
586///
587/// See [`DisplayListBuilder::finalize`] for more details.
588#[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    /// Frame that will be rendered by this display list.
596    pub fn frame_id(&self) -> FrameId {
597        self.frame_id
598    }
599
600    /// Destructure the list.
601    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/// Frame value binding ID.
614///
615/// This ID is defined by the app-process.
616///
617/// See [`FrameValue`] for more details.
618#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
619#[serde(transparent)]
620pub struct FrameValueId(u64);
621
622impl FrameValueId {
623    /// Dummy ID, zero.
624    pub const INVALID: Self = Self(0);
625    /// Create the first valid ID.
626    pub const fn first() -> Self {
627        Self(1)
628    }
629    /// Create the next ID.
630    ///
631    /// IDs wrap around to [`first`] when the entire `u32` space is used, it is never `INVALID`.
632    ///
633    /// [`first`]: Self::first
634    #[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    /// Replace self with [`next`] and returns.
640    ///
641    /// [`next`]: Self::next
642    #[must_use]
643    pub fn incr(&mut self) -> Self {
644        std::mem::replace(self, self.next())
645    }
646    /// Get the raw ID.
647    pub const fn get(self) -> u64 {
648        self.0
649    }
650    /// Create an ID using a custom value.
651    ///
652    /// Note that only the documented process must generate IDs, and that it must only
653    /// generate IDs using this function or the [`next`] function.
654    ///
655    /// If the `id` is zero it will still be [`INVALID`] and handled differently by the other process,
656    /// zero is never valid.
657    ///
658    /// [`next`]: Self::next
659    /// [`INVALID`]: Self::INVALID
660    pub const fn from_raw(id: u64) -> Self {
661        Self(id)
662    }
663}
664
665/// Represents a frame value that may be updated.
666///
667/// This value is send in a full frame request, after frame updates may be send targeting the key.
668#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
669#[non_exhaustive]
670pub enum FrameValue<T> {
671    /// Value that is updated with frame update requests.
672    Bind {
673        /// ID that will be used to update the value.
674        id: FrameValueId,
675        /// Initial value.
676        value: T,
677        /// If the value will update rapidly.
678        animating: bool,
679    },
680    /// Value is not updated, a new frame must be send to change this value.
681    Value(T),
682}
683impl<T> FrameValue<T> {
684    /// Reference the (initial) value.
685    pub fn value(&self) -> &T {
686        match self {
687            FrameValue::Bind { value, .. } | FrameValue::Value(value) => value,
688        }
689    }
690
691    /// Into the (initial) value.
692    pub fn into_value(self) -> T {
693        match self {
694            FrameValue::Bind { value, .. } | FrameValue::Value(value) => value,
695        }
696    }
697
698    /// Returns `true` if a new frame must be generated.
699    fn update_bindable(value: &mut T, animating: &mut bool, update: &FrameValueUpdate<T>) -> bool
700    where
701        T: PartialEq + Copy,
702    {
703        // if changed to `true`, needs a frame to register the binding.
704        //
705        // if changed to `false`, needs a frame to un-register the binding so that the renderer can start caching
706        // the tiles in the region again, we can't use the binding "one last time" because if a smaller region
707        // continues animating it would keep refreshing the large region too.
708        //
709        // if continues to be `false` only needs to update if the value actually changed.
710        let need_frame = (*animating != update.animating) || (!*animating && *value != update.value);
711
712        *animating = update.animating;
713        *value = update.value;
714
715        need_frame
716    }
717
718    /// Returns `true` if a new frame must be generated.
719    fn update_value(value: &mut T, update: &FrameValueUpdate<T>) -> bool
720    where
721        T: PartialEq + Copy,
722    {
723        if value != &update.value {
724            *value = update.value;
725            true
726        } else {
727            false
728        }
729    }
730}
731impl<T> From<T> for FrameValue<T> {
732    fn from(value: T) -> Self {
733        FrameValue::Value(value)
734    }
735}
736
737/// Represents an update targeting a previously setup [`FrameValue`].
738#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
739#[non_exhaustive]
740pub struct FrameValueUpdate<T> {
741    /// Value ID.
742    pub id: FrameValueId,
743    /// New value.
744    pub value: T,
745    /// If the value is updating rapidly.
746    pub animating: bool,
747}
748impl<T> FrameValueUpdate<T> {
749    /// New update.
750    pub fn new(id: FrameValueId, value: T, animating: bool) -> Self {
751        Self { id, value, animating }
752    }
753}
754
755/// Represents one of the filters applied to a stacking context.
756#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
757#[non_exhaustive]
758pub enum FilterOp {
759    /// Blur, width and height in pixels.
760    Blur(f32, f32),
761    /// Brightness, in [0..=1] range.
762    Brightness(f32),
763    /// Contrast, in [0..=1] range.
764    Contrast(f32),
765    /// Grayscale, in [0..=1] range.
766    Grayscale(f32),
767    /// Hue shift, in degrees.
768    HueRotate(f32),
769    /// Invert, in [0..=1] range.
770    Invert(f32),
771    /// Opacity, in [0..=1] range, can be bound.
772    Opacity(FrameValue<f32>),
773    /// Saturation, in [0..=1] range.
774    Saturate(f32),
775    /// Sepia, in [0..=1] range.
776    Sepia(f32),
777    /// Pixel perfect shadow.
778    DropShadow {
779        /// Shadow offset.
780        offset: euclid::Vector2D<f32, Px>,
781        /// Shadow color.
782        color: Rgba,
783        /// Shadow blur.
784        blur_radius: f32,
785    },
786    /// Custom filter.
787    ///
788    /// The color matrix is in the format of SVG color matrix, [0..5] is the first matrix row.
789    ColorMatrix([f32; 20]),
790    /// Fill with color.
791    Flood(Rgba),
792}
793
794/// Display item in a [`DisplayList`].
795#[allow(missing_docs)]
796#[derive(Debug, Clone, Serialize, Deserialize)]
797#[non_exhaustive]
798pub enum DisplayItem {
799    Reuse {
800        frame_id: FrameId,
801        seg_id: SegmentId,
802        start: usize,
803        end: usize,
804    },
805    PushReferenceFrame {
806        id: ReferenceFrameId,
807        transform: FrameValue<PxTransform>,
808        transform_style: TransformStyle,
809        is_2d_scale_translation: bool,
810    },
811    PopReferenceFrame,
812
813    PushStackingContext {
814        transform_style: TransformStyle,
815        blend_mode: MixBlendMode,
816        filters: Box<[FilterOp]>,
817    },
818    PopStackingContext,
819
820    PushClipRect {
821        clip_rect: PxRect,
822        clip_out: bool,
823    },
824    PushClipRoundedRect {
825        clip_rect: PxRect,
826        corners: PxCornerRadius,
827        clip_out: bool,
828    },
829    PopClip,
830    PushMask {
831        image_id: ImageTextureId,
832        rect: PxRect,
833    },
834    PopMask,
835
836    Border {
837        bounds: PxRect,
838        widths: PxSideOffsets,
839        sides: [BorderSide; 4],
840        radius: PxCornerRadius,
841    },
842    NinePatchBorder {
843        bounds: PxRect,
844        source: NinePatchSource,
845        widths: PxSideOffsets,
846        img_size: PxSize,
847        slice: PxSideOffsets,
848        fill: bool,
849        repeat_horizontal: RepeatMode,
850        repeat_vertical: RepeatMode,
851    },
852
853    Text {
854        clip_rect: PxRect,
855        font_id: FontId,
856        glyphs: Box<[GlyphInstance]>,
857        color: FrameValue<Rgba>,
858        options: GlyphOptions,
859    },
860
861    Image {
862        clip_rect: PxRect,
863        image_id: ImageTextureId,
864        image_size: PxSize,
865        rendering: ImageRendering,
866        alpha_type: AlphaType,
867        tile_size: PxSize,
868        tile_spacing: PxSize,
869    },
870
871    Color {
872        clip_rect: PxRect,
873        color: FrameValue<Rgba>,
874    },
875    BackdropFilter {
876        clip_rect: PxRect,
877        filters: Box<[FilterOp]>,
878    },
879
880    LinearGradient {
881        clip_rect: PxRect,
882        start_point: euclid::Point2D<f32, Px>,
883        end_point: euclid::Point2D<f32, Px>,
884        extend_mode: ExtendMode,
885        stops: Box<[GradientStop]>,
886        tile_origin: PxPoint,
887        tile_size: PxSize,
888        tile_spacing: PxSize,
889    },
890    RadialGradient {
891        clip_rect: PxRect,
892        center: euclid::Point2D<f32, Px>,
893        radius: euclid::Size2D<f32, Px>,
894        start_offset: f32,
895        end_offset: f32,
896        extend_mode: ExtendMode,
897        stops: Box<[GradientStop]>,
898        tile_origin: PxPoint,
899        tile_size: PxSize,
900        tile_spacing: PxSize,
901    },
902    ConicGradient {
903        clip_rect: PxRect,
904        center: euclid::Point2D<f32, Px>,
905        angle: AngleRadian,
906        start_offset: f32,
907        end_offset: f32,
908        extend_mode: ExtendMode,
909        stops: Box<[GradientStop]>,
910        tile_origin: PxPoint,
911        tile_size: PxSize,
912        tile_spacing: PxSize,
913    },
914
915    Line {
916        clip_rect: PxRect,
917        color: Rgba,
918        style: LineStyle,
919        orientation: LineOrientation,
920    },
921
922    PushExtension {
923        extension_id: ApiExtensionId,
924        payload: ApiExtensionPayload,
925    },
926    PopExtension {
927        extension_id: ApiExtensionId,
928    },
929
930    SetBackfaceVisibility {
931        visible: bool,
932    },
933}
934impl DisplayItem {
935    /// Update the value and returns if a new full frame must be rendered.
936    pub fn update_transform(&mut self, t: &FrameValueUpdate<PxTransform>) -> bool {
937        match self {
938            DisplayItem::PushReferenceFrame {
939                transform:
940                    FrameValue::Bind {
941                        id,
942                        value,
943                        animating: animation,
944                    },
945                ..
946            } if *id == t.id => FrameValue::update_bindable(value, animation, t),
947            _ => false,
948        }
949    }
950
951    /// Update the value and returns if a new full frame must be rendered.
952    pub fn update_float(&mut self, t: &FrameValueUpdate<f32>) -> bool {
953        match self {
954            DisplayItem::PushStackingContext { filters, .. } => {
955                let mut new_frame = false;
956                for filter in filters.iter_mut() {
957                    match filter {
958                        FilterOp::Opacity(FrameValue::Bind {
959                            id,
960                            value,
961                            animating: animation,
962                        }) if *id == t.id => {
963                            new_frame |= FrameValue::update_bindable(value, animation, t);
964                        }
965                        _ => {}
966                    }
967                }
968                new_frame
969            }
970            _ => false,
971        }
972    }
973
974    /// Update the value and returns if a new full frame must be rendered.
975    pub fn update_color(&mut self, t: &FrameValueUpdate<Rgba>) -> bool {
976        match self {
977            DisplayItem::Color {
978                color:
979                    FrameValue::Bind {
980                        id,
981                        value,
982                        animating: animation,
983                    },
984                ..
985            } if *id == t.id => FrameValue::update_bindable(value, animation, t),
986            DisplayItem::Text {
987                color: FrameValue::Bind { id, value, .. },
988                ..
989            } if *id == t.id => FrameValue::update_value(value, t),
990            _ => false,
991        }
992    }
993}
994
995/// Nine-patch image source.
996#[allow(missing_docs)]
997#[derive(Debug, Clone, Serialize, Deserialize)]
998pub enum NinePatchSource {
999    Image {
1000        image_id: ImageTextureId,
1001        rendering: ImageRendering,
1002    },
1003    LinearGradient {
1004        start_point: euclid::Point2D<f32, Px>,
1005        end_point: euclid::Point2D<f32, Px>,
1006        extend_mode: ExtendMode,
1007        stops: Box<[GradientStop]>,
1008    },
1009    RadialGradient {
1010        center: euclid::Point2D<f32, Px>,
1011        radius: euclid::Size2D<f32, Px>,
1012        start_offset: f32,
1013        end_offset: f32,
1014        extend_mode: ExtendMode,
1015        stops: Box<[GradientStop]>,
1016    },
1017    ConicGradient {
1018        center: euclid::Point2D<f32, Px>,
1019        angle: AngleRadian,
1020        start_offset: f32,
1021        end_offset: f32,
1022        extend_mode: ExtendMode,
1023        stops: Box<[GradientStop]>,
1024    },
1025}