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)]
669pub enum FrameValue<T> {
670    /// Value that is updated with frame update requests.
671    Bind {
672        /// ID that will be used to update the value.
673        id: FrameValueId,
674        /// Initial value.
675        value: T,
676        /// If the value will update rapidly.
677        animating: bool,
678    },
679    /// Value is not updated, a new frame must be send to change this value.
680    Value(T),
681}
682impl<T> FrameValue<T> {
683    /// Reference the (initial) value.
684    pub fn value(&self) -> &T {
685        match self {
686            FrameValue::Bind { value, .. } | FrameValue::Value(value) => value,
687        }
688    }
689
690    /// Into the (initial) value.
691    pub fn into_value(self) -> T {
692        match self {
693            FrameValue::Bind { value, .. } | FrameValue::Value(value) => value,
694        }
695    }
696
697    /// Returns `true` if a new frame must be generated.
698    fn update_bindable(value: &mut T, animating: &mut bool, update: &FrameValueUpdate<T>) -> bool
699    where
700        T: PartialEq + Copy,
701    {
702        // if changed to `true`, needs a frame to register the binding.
703        //
704        // if changed to `false`, needs a frame to un-register the binding so that the renderer can start caching
705        // the tiles in the region again, we can't use the binding "one last time" because if a smaller region
706        // continues animating it would keep refreshing the large region too.
707        //
708        // if continues to be `false` only needs to update if the value actually changed.
709        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    /// Returns `true` if a new frame must be generated.
718    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/// Represents an update targeting a previously setup [`FrameValue`].
737#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
738pub struct FrameValueUpdate<T> {
739    /// Value ID.
740    pub id: FrameValueId,
741    /// New value.
742    pub value: T,
743    /// If the value is updating rapidly.
744    pub animating: bool,
745}
746
747/// Represents one of the filters applied to a stacking context.
748#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
749pub enum FilterOp {
750    /// Blur, width and height in pixels.
751    Blur(f32, f32),
752    /// Brightness, in [0..=1] range.
753    Brightness(f32),
754    /// Contrast, in [0..=1] range.
755    Contrast(f32),
756    /// Grayscale, in [0..=1] range.
757    Grayscale(f32),
758    /// Hue shift, in degrees.
759    HueRotate(f32),
760    /// Invert, in [0..=1] range.
761    Invert(f32),
762    /// Opacity, in [0..=1] range, can be bound.
763    Opacity(FrameValue<f32>),
764    /// Saturation, in [0..=1] range.
765    Saturate(f32),
766    /// Sepia, in [0..=1] range.
767    Sepia(f32),
768    /// Pixel perfect shadow.
769    DropShadow {
770        /// Shadow offset.
771        offset: euclid::Vector2D<f32, Px>,
772        /// Shadow color.
773        color: Rgba,
774        /// Shadow blur.
775        blur_radius: f32,
776    },
777    /// Custom filter.
778    ///
779    /// The color matrix is in the format of SVG color matrix, [0..5] is the first matrix row.
780    ColorMatrix([f32; 20]),
781    /// Fill with color.
782    Flood(Rgba),
783}
784
785/// Display item in a [`DisplayList`].
786#[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    /// Update the value and returns if a new full frame must be rendered.
926    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    /// Update the value and returns if a new full frame must be rendered.
942    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    /// Update the value and returns if a new full frame must be rendered.
965    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/// Nine-patch image source.
986#[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}