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    BorderSide, ExtendMode, GradientStop, ImageRendering, LineOrientation, LineStyle, MixBlendMode, ReferenceFrameId, RepeatMode,
12    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    pub fn push_image(
307        &mut self,
308        clip_rect: PxRect,
309        image_id: ImageTextureId,
310        image_size: PxSize,
311        tile_size: PxSize,
312        tile_spacing: PxSize,
313        rendering: ImageRendering,
314    ) {
315        self.list.push(DisplayItem::Image {
316            clip_rect,
317            image_id,
318            image_size,
319            rendering,
320            tile_size,
321            tile_spacing,
322        })
323    }
324
325    /// Push a color rectangle.
326    pub fn push_color(&mut self, clip_rect: PxRect, color: FrameValue<Rgba>) {
327        self.list.push(DisplayItem::Color { clip_rect, color })
328    }
329
330    /// Push a filter that applies to all rendered pixels behind `clip_rect`.
331    pub fn push_backdrop_filter(&mut self, clip_rect: PxRect, filters: &[FilterOp]) {
332        self.list.push(DisplayItem::BackdropFilter {
333            clip_rect,
334            filters: filters.to_vec().into_boxed_slice(),
335        })
336    }
337
338    /// Push a linear gradient rectangle.
339    #[expect(clippy::too_many_arguments)]
340    pub fn push_linear_gradient(
341        &mut self,
342        clip_rect: PxRect,
343        start_point: euclid::Point2D<f32, Px>,
344        end_point: euclid::Point2D<f32, Px>,
345        extend_mode: ExtendMode,
346        stops: &[GradientStop],
347        tile_origin: PxPoint,
348        tile_size: PxSize,
349        tile_spacing: PxSize,
350    ) {
351        self.list.push(DisplayItem::LinearGradient {
352            clip_rect,
353            start_point,
354            end_point,
355            extend_mode,
356            stops: stops.to_vec().into_boxed_slice(),
357            tile_origin,
358            tile_size,
359            tile_spacing,
360        })
361    }
362
363    /// Push a radial gradient rectangle.
364    #[expect(clippy::too_many_arguments)]
365    pub fn push_radial_gradient(
366        &mut self,
367        clip_rect: PxRect,
368        center: euclid::Point2D<f32, Px>,
369        radius: euclid::Size2D<f32, Px>,
370        start_offset: f32,
371        end_offset: f32,
372        extend_mode: ExtendMode,
373        stops: &[GradientStop],
374        tile_origin: PxPoint,
375        tile_size: PxSize,
376        tile_spacing: PxSize,
377    ) {
378        self.list.push(DisplayItem::RadialGradient {
379            clip_rect,
380            center,
381            radius,
382            start_offset,
383            end_offset,
384            extend_mode,
385            stops: stops.to_vec().into_boxed_slice(),
386            tile_origin,
387            tile_size,
388            tile_spacing,
389        });
390    }
391
392    /// Push a conic gradient rectangle.
393    #[expect(clippy::too_many_arguments)]
394    pub fn push_conic_gradient(
395        &mut self,
396        clip_rect: PxRect,
397        center: euclid::Point2D<f32, Px>,
398        angle: AngleRadian,
399        start_offset: f32,
400        end_offset: f32,
401        extend_mode: ExtendMode,
402        stops: &[GradientStop],
403        tile_origin: PxPoint,
404        tile_size: PxSize,
405        tile_spacing: PxSize,
406    ) {
407        self.list.push(DisplayItem::ConicGradient {
408            clip_rect,
409            center,
410            angle,
411            start_offset,
412            end_offset,
413            extend_mode,
414            stops: stops.to_vec().into_boxed_slice(),
415            tile_origin,
416            tile_size,
417            tile_spacing,
418        });
419    }
420
421    /// Push a styled vertical or horizontal line.
422    pub fn push_line(&mut self, clip_rect: PxRect, color: Rgba, style: LineStyle, orientation: LineOrientation) {
423        self.list.push(DisplayItem::Line {
424            clip_rect,
425            color,
426            style,
427            orientation,
428        })
429    }
430
431    /// Push a custom extension payload.
432    ///
433    /// This can be used by custom renderer implementations to support custom items defined in the context
434    /// of normal display items.
435    ///
436    /// There are two types of display items, normal items like [`push_color`] and context items like
437    /// [`push_clip_rect`]-[`pop_clip`], if the extension is a normal item only the `push_extension` method must
438    /// be called, if the extension is a context item the [`pop_extension`] must also be called.
439    ///
440    /// [`push_color`]: Self::push_color
441    /// [`push_clip_rect`]: Self::push_clip_rect
442    /// [`pop_clip`]: Self::pop_clip
443    /// [`pop_extension`]: Self::pop_extension
444    pub fn push_extension(&mut self, extension_id: ApiExtensionId, payload: ApiExtensionPayload) {
445        self.list.push(DisplayItem::PushExtension { extension_id, payload })
446    }
447
448    /// Pop an extension previously pushed.
449    ///
450    /// Only required if the extension implementation requires it, item extensions do not need to pop.
451    pub fn pop_extension(&mut self, extension_id: ApiExtensionId) {
452        self.list.push(DisplayItem::PopExtension { extension_id })
453    }
454
455    /// Sets the backface visibility of all display items after this call.
456    pub fn set_backface_visibility(&mut self, visible: bool) {
457        self.list.push(DisplayItem::SetBackfaceVisibility { visible })
458    }
459
460    /// Number of display items.
461    pub fn len(&self) -> usize {
462        self.list.len()
463    }
464
465    /// Returns `true` if the list has no display items.
466    pub fn is_empty(&self) -> bool {
467        self.list.is_empty()
468    }
469
470    /// Create a display list builder that can be send to be build in parallel and then folded back onto
471    /// this list using [`parallel_fold`].
472    ///
473    /// [`parallel_fold`]: Self::parallel_fold
474    pub fn parallel_split(&self) -> Self {
475        Self {
476            frame_id: self.frame_id,
477            list: vec![],
478            clip_len: 1,
479            mask_len: 1,
480            space_len: 1,
481            stacking_len: 1,
482            seg_id: self.seg_id_gen.fetch_add(1, std::sync::atomic::Ordering::Relaxed),
483            seg_id_gen: self.seg_id_gen.clone(),
484            segments: vec![],
485            has_reuse_ranges: false,
486        }
487    }
488
489    /// Append the `split` onto `self`.
490    ///
491    /// # Panics
492    ///
493    /// Panics if `split` was not generated by a call to [`parallel_split`] on `self` or a
494    /// *parent* display list.
495    ///
496    /// Panics if `split` has not closed all reference frames, clips or stacking contexts that it opened.
497    ///
498    /// [`parallel_split`]: Self::parallel_split
499    pub fn parallel_fold(&mut self, mut split: Self) {
500        assert!(
501            Arc::ptr_eq(&self.seg_id_gen, &split.seg_id_gen),
502            "cannot fold list not split from this one or parent"
503        );
504        assert_eq!(split.space_len, 1);
505        assert_eq!(split.clip_len, 1);
506        assert_eq!(split.mask_len, 1);
507        assert_eq!(split.stacking_len, 1);
508
509        if !self.list.is_empty() {
510            for (_, offset) in &mut split.segments {
511                *offset += self.list.len();
512            }
513        }
514
515        if self.segments.is_empty() {
516            self.segments = split.segments;
517        } else {
518            self.segments.append(&mut split.segments);
519        }
520        if split.has_reuse_ranges {
521            self.segments.push((split.seg_id, self.list.len()));
522        }
523
524        if self.list.is_empty() {
525            self.list = split.list;
526        } else {
527            self.list.append(&mut split.list);
528        }
529    }
530
531    /// Returns the display list.
532    pub fn finalize(self) -> DisplayList {
533        DisplayList {
534            frame_id: self.frame_id,
535            list: self.list,
536            segments: self.segments,
537        }
538    }
539}
540
541/// Id of the offset a parallel list folded onto the main list is at.
542pub type SegmentId = usize;
543
544/// Represents the start of a display list reuse range.
545///
546/// See [`DisplayListBuilder::start_reuse_range`] for more details.
547pub struct ReuseStart {
548    frame_id: FrameId,
549    seg_id: SegmentId,
550    start: usize,
551
552    clip_len: usize,
553    mask_len: usize,
554    space_len: usize,
555    stacking_len: usize,
556}
557
558/// Represents a display list reuse range.
559///
560/// See [`DisplayListBuilder::push_reuse_range`] for more details.
561///
562/// [`DisplayListBuilder::push_reuse_range`]: crate::display_list::DisplayListBuilder::push_reuse_range
563#[derive(Debug, Clone)]
564pub struct ReuseRange {
565    frame_id: FrameId,
566    seg_id: SegmentId,
567    start: usize,
568    end: usize,
569}
570impl ReuseRange {
571    /// Frame that owns the reused items selected by this range.
572    pub fn frame_id(&self) -> FrameId {
573        self.frame_id
574    }
575
576    /// If the reuse range did not capture any display item.
577    pub fn is_empty(&self) -> bool {
578        self.start == self.end
579    }
580}
581
582/// Represents a finalized display list.
583///
584/// See [`DisplayListBuilder::finalize`] for more details.
585#[derive(Debug, Clone, Serialize, Deserialize)]
586pub struct DisplayList {
587    frame_id: FrameId,
588    list: Vec<DisplayItem>,
589    segments: Vec<(SegmentId, usize)>,
590}
591impl DisplayList {
592    /// Frame that will be rendered by this display list.
593    pub fn frame_id(&self) -> FrameId {
594        self.frame_id
595    }
596
597    /// Destructure the list.
598    pub fn into_parts(self) -> (FrameId, Vec<DisplayItem>, Vec<(SegmentId, usize)>) {
599        (self.frame_id, self.list, self.segments)
600    }
601}
602impl ops::Deref for DisplayList {
603    type Target = [DisplayItem];
604
605    fn deref(&self) -> &Self::Target {
606        &self.list
607    }
608}
609
610/// Frame value binding ID.
611///
612/// This ID is defined by the app-process.
613///
614/// See [`FrameValue`] for more details.
615#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
616#[serde(transparent)]
617pub struct FrameValueId(u64);
618
619impl FrameValueId {
620    /// Dummy ID, zero.
621    pub const INVALID: Self = Self(0);
622    /// Create the first valid ID.
623    pub const fn first() -> Self {
624        Self(1)
625    }
626    /// Create the next ID.
627    ///
628    /// IDs wrap around to [`first`] when the entire `u32` space is used, it is never `INVALID`.
629    ///
630    /// [`first`]: Self::first
631    #[must_use]
632    pub const fn next(self) -> Self {
633        let r = Self(self.0.wrapping_add(1));
634        if r.0 == Self::INVALID.0 { Self::first() } else { r }
635    }
636    /// Replace self with [`next`] and returns.
637    ///
638    /// [`next`]: Self::next
639    #[must_use]
640    pub fn incr(&mut self) -> Self {
641        std::mem::replace(self, self.next())
642    }
643    /// Get the raw ID.
644    pub const fn get(self) -> u64 {
645        self.0
646    }
647    /// Create an ID using a custom value.
648    ///
649    /// Note that only the documented process must generate IDs, and that it must only
650    /// generate IDs using this function or the [`next`] function.
651    ///
652    /// If the `id` is zero it will still be [`INVALID`] and handled differently by the other process,
653    /// zero is never valid.
654    ///
655    /// [`next`]: Self::next
656    /// [`INVALID`]: Self::INVALID
657    pub const fn from_raw(id: u64) -> Self {
658        Self(id)
659    }
660}
661
662/// Represents a frame value that may be updated.
663///
664/// This value is send in a full frame request, after frame updates may be send targeting the key.
665#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
666#[non_exhaustive]
667pub enum FrameValue<T> {
668    /// Value that is updated with frame update requests.
669    Bind {
670        /// ID that will be used to update the value.
671        id: FrameValueId,
672        /// Initial value.
673        value: T,
674        /// If the value will update rapidly.
675        animating: bool,
676    },
677    /// Value is not updated, a new frame must be send to change this value.
678    Value(T),
679}
680impl<T> FrameValue<T> {
681    /// Reference the (initial) value.
682    pub fn value(&self) -> &T {
683        match self {
684            FrameValue::Bind { value, .. } | FrameValue::Value(value) => value,
685        }
686    }
687
688    /// Into the (initial) value.
689    pub fn into_value(self) -> T {
690        match self {
691            FrameValue::Bind { value, .. } | FrameValue::Value(value) => value,
692        }
693    }
694
695    /// Returns `true` if a new frame must be generated.
696    fn update_bindable(value: &mut T, animating: &mut bool, update: &FrameValueUpdate<T>) -> bool
697    where
698        T: PartialEq + Copy,
699    {
700        // if changed to `true`, needs a frame to register the binding.
701        //
702        // if changed to `false`, needs a frame to un-register the binding so that the renderer can start caching
703        // the tiles in the region again, we can't use the binding "one last time" because if a smaller region
704        // continues animating it would keep refreshing the large region too.
705        //
706        // if continues to be `false` only needs to update if the value actually changed.
707        let need_frame = (*animating != update.animating) || (!*animating && *value != update.value);
708
709        *animating = update.animating;
710        *value = update.value;
711
712        need_frame
713    }
714
715    /// Returns `true` if a new frame must be generated.
716    fn update_value(value: &mut T, update: &FrameValueUpdate<T>) -> bool
717    where
718        T: PartialEq + Copy,
719    {
720        if value != &update.value {
721            *value = update.value;
722            true
723        } else {
724            false
725        }
726    }
727}
728impl<T> From<T> for FrameValue<T> {
729    fn from(value: T) -> Self {
730        FrameValue::Value(value)
731    }
732}
733
734/// Represents an update targeting a previously setup [`FrameValue`].
735#[derive(Debug, Clone, Copy, Serialize, Deserialize)]
736#[non_exhaustive]
737pub struct FrameValueUpdate<T> {
738    /// Value ID.
739    pub id: FrameValueId,
740    /// New value.
741    pub value: T,
742    /// If the value is updating rapidly.
743    pub animating: bool,
744}
745impl<T> FrameValueUpdate<T> {
746    /// New update.
747    pub fn new(id: FrameValueId, value: T, animating: bool) -> Self {
748        Self { id, value, animating }
749    }
750}
751
752/// Represents one of the filters applied to a stacking context.
753#[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)]
754#[non_exhaustive]
755pub enum FilterOp {
756    /// Blur, width and height in pixels.
757    Blur(f32, f32),
758    /// Brightness, in [0..=1] range.
759    Brightness(f32),
760    /// Contrast, in [0..=1] range.
761    Contrast(f32),
762    /// Grayscale, in [0..=1] range.
763    Grayscale(f32),
764    /// Hue shift, in degrees.
765    HueRotate(f32),
766    /// Invert, in [0..=1] range.
767    Invert(f32),
768    /// Opacity, in [0..=1] range, can be bound.
769    Opacity(FrameValue<f32>),
770    /// Saturation, in [0..=1] range.
771    Saturate(f32),
772    /// Sepia, in [0..=1] range.
773    Sepia(f32),
774    /// Pixel perfect shadow.
775    DropShadow {
776        /// Shadow offset.
777        offset: euclid::Vector2D<f32, Px>,
778        /// Shadow color.
779        color: Rgba,
780        /// Shadow blur.
781        blur_radius: f32,
782    },
783    /// Custom filter.
784    ///
785    /// The color matrix is in the format of SVG color matrix, [0..5] is the first matrix row.
786    ColorMatrix([f32; 20]),
787    /// Fill with color.
788    Flood(Rgba),
789}
790
791/// Display item in a [`DisplayList`].
792#[allow(missing_docs)]
793#[derive(Debug, Clone, Serialize, Deserialize)]
794#[non_exhaustive]
795pub enum DisplayItem {
796    Reuse {
797        frame_id: FrameId,
798        seg_id: SegmentId,
799        start: usize,
800        end: usize,
801    },
802    PushReferenceFrame {
803        id: ReferenceFrameId,
804        transform: FrameValue<PxTransform>,
805        transform_style: TransformStyle,
806        is_2d_scale_translation: bool,
807    },
808    PopReferenceFrame,
809
810    PushStackingContext {
811        transform_style: TransformStyle,
812        blend_mode: MixBlendMode,
813        filters: Box<[FilterOp]>,
814    },
815    PopStackingContext,
816
817    PushClipRect {
818        clip_rect: PxRect,
819        clip_out: bool,
820    },
821    PushClipRoundedRect {
822        clip_rect: PxRect,
823        corners: PxCornerRadius,
824        clip_out: bool,
825    },
826    PopClip,
827    PushMask {
828        image_id: ImageTextureId,
829        rect: PxRect,
830    },
831    PopMask,
832
833    Border {
834        bounds: PxRect,
835        widths: PxSideOffsets,
836        sides: [BorderSide; 4],
837        radius: PxCornerRadius,
838    },
839    NinePatchBorder {
840        bounds: PxRect,
841        source: NinePatchSource,
842        widths: PxSideOffsets,
843        img_size: PxSize,
844        slice: PxSideOffsets,
845        fill: bool,
846        repeat_horizontal: RepeatMode,
847        repeat_vertical: RepeatMode,
848    },
849
850    Text {
851        clip_rect: PxRect,
852        font_id: FontId,
853        glyphs: Box<[GlyphInstance]>,
854        color: FrameValue<Rgba>,
855        options: GlyphOptions,
856    },
857
858    Image {
859        clip_rect: PxRect,
860        image_id: ImageTextureId,
861        image_size: PxSize,
862        rendering: ImageRendering,
863        tile_size: PxSize,
864        tile_spacing: PxSize,
865    },
866
867    Color {
868        clip_rect: PxRect,
869        color: FrameValue<Rgba>,
870    },
871    BackdropFilter {
872        clip_rect: PxRect,
873        filters: Box<[FilterOp]>,
874    },
875
876    LinearGradient {
877        clip_rect: PxRect,
878        start_point: euclid::Point2D<f32, Px>,
879        end_point: euclid::Point2D<f32, Px>,
880        extend_mode: ExtendMode,
881        stops: Box<[GradientStop]>,
882        tile_origin: PxPoint,
883        tile_size: PxSize,
884        tile_spacing: PxSize,
885    },
886    RadialGradient {
887        clip_rect: PxRect,
888        center: euclid::Point2D<f32, Px>,
889        radius: euclid::Size2D<f32, Px>,
890        start_offset: f32,
891        end_offset: f32,
892        extend_mode: ExtendMode,
893        stops: Box<[GradientStop]>,
894        tile_origin: PxPoint,
895        tile_size: PxSize,
896        tile_spacing: PxSize,
897    },
898    ConicGradient {
899        clip_rect: PxRect,
900        center: euclid::Point2D<f32, Px>,
901        angle: AngleRadian,
902        start_offset: f32,
903        end_offset: f32,
904        extend_mode: ExtendMode,
905        stops: Box<[GradientStop]>,
906        tile_origin: PxPoint,
907        tile_size: PxSize,
908        tile_spacing: PxSize,
909    },
910
911    Line {
912        clip_rect: PxRect,
913        color: Rgba,
914        style: LineStyle,
915        orientation: LineOrientation,
916    },
917
918    PushExtension {
919        extension_id: ApiExtensionId,
920        payload: ApiExtensionPayload,
921    },
922    PopExtension {
923        extension_id: ApiExtensionId,
924    },
925
926    SetBackfaceVisibility {
927        visible: bool,
928    },
929}
930impl DisplayItem {
931    /// Update the value and returns if a new full frame must be rendered.
932    pub fn update_transform(&mut self, t: &FrameValueUpdate<PxTransform>) -> bool {
933        match self {
934            DisplayItem::PushReferenceFrame {
935                transform:
936                    FrameValue::Bind {
937                        id,
938                        value,
939                        animating: animation,
940                    },
941                ..
942            } if *id == t.id => FrameValue::update_bindable(value, animation, t),
943            _ => false,
944        }
945    }
946
947    /// Update the value and returns if a new full frame must be rendered.
948    pub fn update_float(&mut self, t: &FrameValueUpdate<f32>) -> bool {
949        match self {
950            DisplayItem::PushStackingContext { filters, .. } => {
951                let mut new_frame = false;
952                for filter in filters.iter_mut() {
953                    match filter {
954                        FilterOp::Opacity(FrameValue::Bind {
955                            id,
956                            value,
957                            animating: animation,
958                        }) if *id == t.id => {
959                            new_frame |= FrameValue::update_bindable(value, animation, t);
960                        }
961                        _ => {}
962                    }
963                }
964                new_frame
965            }
966            _ => false,
967        }
968    }
969
970    /// Update the value and returns if a new full frame must be rendered.
971    pub fn update_color(&mut self, t: &FrameValueUpdate<Rgba>) -> bool {
972        match self {
973            DisplayItem::Color {
974                color:
975                    FrameValue::Bind {
976                        id,
977                        value,
978                        animating: animation,
979                    },
980                ..
981            } if *id == t.id => FrameValue::update_bindable(value, animation, t),
982            DisplayItem::Text {
983                color: FrameValue::Bind { id, value, .. },
984                ..
985            } if *id == t.id => FrameValue::update_value(value, t),
986            _ => false,
987        }
988    }
989}
990
991/// Nine-patch image source.
992#[allow(missing_docs)]
993#[derive(Debug, Clone, Serialize, Deserialize)]
994pub enum NinePatchSource {
995    Image {
996        image_id: ImageTextureId,
997        rendering: ImageRendering,
998    },
999    LinearGradient {
1000        start_point: euclid::Point2D<f32, Px>,
1001        end_point: euclid::Point2D<f32, Px>,
1002        extend_mode: ExtendMode,
1003        stops: Box<[GradientStop]>,
1004    },
1005    RadialGradient {
1006        center: euclid::Point2D<f32, Px>,
1007        radius: euclid::Size2D<f32, Px>,
1008        start_offset: f32,
1009        end_offset: f32,
1010        extend_mode: ExtendMode,
1011        stops: Box<[GradientStop]>,
1012    },
1013    ConicGradient {
1014        center: euclid::Point2D<f32, Px>,
1015        angle: AngleRadian,
1016        start_offset: f32,
1017        end_offset: f32,
1018        extend_mode: ExtendMode,
1019        stops: Box<[GradientStop]>,
1020    },
1021}