zng_app/
render.rs

1//! Frame render and metadata API.
2
3use std::{marker::PhantomData, mem, sync::Arc};
4
5use crate::{
6    widget::info::{ParallelSegmentOffsets, WidgetBoundsInfo},
7    window::WINDOW,
8};
9use zng_color::{
10    MixBlendMode, Rgba, colors,
11    filter::RenderFilter,
12    gradient::{RenderExtendMode, RenderGradientStop},
13};
14use zng_layout::unit::{
15    AngleRadian, Factor, FactorUnits, Px, PxCornerRadius, PxLine, PxPoint, PxRect, PxSideOffsets, PxSize, PxTransform, PxVector, euclid,
16};
17use zng_task::rayon::iter::{ParallelBridge, ParallelIterator};
18use zng_unique_id::{impl_unique_id_bytemuck, unique_id_32};
19use zng_var::{Var, VarCapability, VarValue, impl_from_and_into_var};
20use zng_view_api::{
21    ReferenceFrameId as RenderReferenceFrameId, ViewProcessGen,
22    api_extension::{ApiExtensionId, ApiExtensionPayload},
23    config::FontAntiAliasing,
24    display_list::{DisplayList, DisplayListBuilder, FilterOp, NinePatchSource, ReuseStart},
25    font::{GlyphInstance, GlyphOptions},
26    window::FrameId,
27};
28
29use crate::{
30    update::{RenderUpdates, UpdateFlags},
31    view_process::ViewRenderer,
32    widget::{
33        WIDGET, WidgetId,
34        base::{PARALLEL_VAR, Parallel},
35        border::{self, BorderSides},
36        info::{HitTestClips, ParallelBuilder, WidgetInfo, WidgetInfoTree, WidgetRenderInfo},
37    },
38};
39
40pub use zng_view_api::{
41    ImageRendering, RepeatMode, TransformStyle,
42    display_list::{FrameValue, FrameValueUpdate, ReuseRange},
43};
44
45/// A text font.
46///
47/// This trait is an interface for the renderer into the font API used in the application.
48pub trait Font {
49    /// Gets if the font is the fallback that does not have any glyph.
50    fn is_empty_fallback(&self) -> bool;
51
52    /// Gets the instance key in the `renderer` namespace.
53    ///
54    /// The font configuration must be provided by `self`, except the `synthesis` that is used in the font instance.
55    fn renderer_id(&self, renderer: &ViewRenderer, synthesis: FontSynthesis) -> zng_view_api::font::FontId;
56}
57
58/// A loaded or loading image.
59///
60/// This trait is an interface for the renderer into the image API used in the application.
61///
62/// The ideal image format is BGRA with pre-multiplied alpha.
63pub trait Img {
64    /// Gets the image ID in the `renderer` namespace.
65    ///
66    /// The image must be loaded asynchronously by `self` and does not need to
67    /// be loaded yet when the key is returned.
68    fn renderer_id(&self, renderer: &ViewRenderer) -> zng_view_api::image::ImageTextureId;
69
70    /// Image pixel size.
71    fn size(&self) -> PxSize;
72}
73
74macro_rules! expect_inner {
75    ($self:ident.$fn_name:ident) => {
76        if $self.is_outer() {
77            tracing::error!("called `{}` in outer context of `{}`", stringify!($fn_name), $self.widget_id);
78        }
79    };
80}
81
82macro_rules! warn_empty {
83    ($self:ident.$fn_name:ident($rect:tt)) => {
84        #[cfg(debug_assertions)]
85        if $rect.is_empty() {
86            tracing::warn!(
87                "called `{}` with empty `{:?}` in `{:?}`",
88                stringify!($fn_name),
89                $rect,
90                $self.widget_id
91            )
92        }
93    };
94}
95
96struct WidgetData {
97    parent_child_offset: PxVector,
98    inner_is_set: bool, // used to flag if frame is always 2d translate/scale.
99    inner_transform: PxTransform,
100    filter: RenderFilter,
101    blend: MixBlendMode,
102    backdrop_filter: RenderFilter,
103}
104
105/// A full frame builder.
106pub struct FrameBuilder {
107    render_widgets: Arc<RenderUpdates>,
108    render_update_widgets: Arc<RenderUpdates>,
109
110    frame_id: FrameId,
111    widget_id: WidgetId,
112    transform: PxTransform,
113    transform_style: TransformStyle,
114
115    default_font_aa: FontAntiAliasing,
116
117    renderer: Option<ViewRenderer>,
118
119    scale_factor: Factor,
120
121    display_list: DisplayListBuilder,
122
123    hit_testable: bool,
124    visible: bool,
125    backface_visible: bool,
126    auto_hit_test: bool,
127    hit_clips: HitTestClips,
128
129    perspective: Option<(f32, PxPoint)>,
130
131    auto_hide_rect: PxRect,
132    widget_data: Option<WidgetData>,
133    child_offset: PxVector,
134    parent_inner_bounds: Option<PxRect>,
135
136    view_process_has_frame: bool,
137    can_reuse: bool,
138    open_reuse: Option<ReuseStart>,
139
140    clear_color: Option<Rgba>,
141
142    widget_count: usize,
143    widget_count_offsets: ParallelSegmentOffsets,
144
145    debug_dot_overlays: Vec<(PxPoint, Rgba)>,
146}
147impl FrameBuilder {
148    /// New builder.
149    ///
150    /// * `render_widgets` - External render requests.
151    /// * `render_update_widgets` - External render update requests.
152    ///
153    /// * `frame_id` - Id of the new frame.
154    /// * `root_id` - Id of the window root widget.
155    /// * `root_bounds` - Root widget bounds info.
156    /// * `info_tree` - Info tree of the last frame.
157    /// * `renderer` - Connection to the renderer that will render the frame, is `None` in renderless mode.
158    /// * `scale_factor` - Scale factor that will be used to render the frame, usually the scale factor of the screen the window is at.
159    /// * `default_font_aa` - Fallback font anti-aliasing used when the default value is requested.
160    ///   because WebRender does not let us change the initial clear color.
161    #[expect(clippy::too_many_arguments)]
162    pub fn new(
163        render_widgets: Arc<RenderUpdates>,
164        render_update_widgets: Arc<RenderUpdates>,
165        frame_id: FrameId,
166        root_id: WidgetId,
167        root_bounds: &WidgetBoundsInfo,
168        info_tree: &WidgetInfoTree,
169        renderer: Option<ViewRenderer>,
170        scale_factor: Factor,
171        default_font_aa: FontAntiAliasing,
172    ) -> Self {
173        let display_list = DisplayListBuilder::new(frame_id);
174
175        let root_size = root_bounds.outer_size();
176        let auto_hide_rect = PxRect::from_size(root_size).inflate(root_size.width, root_size.height);
177        root_bounds.set_outer_transform(PxTransform::identity(), info_tree);
178
179        let vp_gen = renderer
180            .as_ref()
181            .and_then(|r| r.generation().ok())
182            .unwrap_or(ViewProcessGen::INVALID);
183        let view_process_has_frame = vp_gen != ViewProcessGen::INVALID && vp_gen == info_tree.view_process_gen();
184
185        FrameBuilder {
186            render_widgets,
187            render_update_widgets,
188            frame_id,
189            widget_id: root_id,
190            transform: PxTransform::identity(),
191            transform_style: TransformStyle::Flat,
192            default_font_aa: match default_font_aa {
193                FontAntiAliasing::Default => FontAntiAliasing::Subpixel,
194                aa => aa,
195            },
196            renderer,
197            scale_factor,
198            display_list,
199            hit_testable: true,
200            visible: true,
201            backface_visible: true,
202            auto_hit_test: false,
203            hit_clips: HitTestClips::default(),
204            widget_data: Some(WidgetData {
205                filter: vec![],
206                blend: MixBlendMode::Normal,
207                backdrop_filter: vec![],
208                parent_child_offset: PxVector::zero(),
209                inner_is_set: false,
210                inner_transform: PxTransform::identity(),
211            }),
212            child_offset: PxVector::zero(),
213            parent_inner_bounds: None,
214            perspective: None,
215            view_process_has_frame,
216            can_reuse: view_process_has_frame,
217            open_reuse: None,
218            auto_hide_rect,
219
220            widget_count: 0,
221            widget_count_offsets: ParallelSegmentOffsets::default(),
222
223            clear_color: Some(colors::BLACK.transparent()),
224
225            debug_dot_overlays: vec![],
226        }
227    }
228
229    /// [`new`](Self::new) with only the inputs required for renderless mode.
230    #[expect(clippy::too_many_arguments)]
231    pub fn new_renderless(
232        render_widgets: Arc<RenderUpdates>,
233        render_update_widgets: Arc<RenderUpdates>,
234        frame_id: FrameId,
235        root_id: WidgetId,
236        root_bounds: &WidgetBoundsInfo,
237        info_tree: &WidgetInfoTree,
238        scale_factor: Factor,
239        default_font_aa: FontAntiAliasing,
240    ) -> Self {
241        Self::new(
242            render_widgets,
243            render_update_widgets,
244            frame_id,
245            root_id,
246            root_bounds,
247            info_tree,
248            None,
249            scale_factor,
250            default_font_aa,
251        )
252    }
253
254    /// Pixel scale factor used by the renderer.
255    ///
256    /// All layout values are scaled by this factor in the renderer.
257    pub fn scale_factor(&self) -> Factor {
258        self.scale_factor
259    }
260
261    /// If is building a frame for a headless and renderless window.
262    ///
263    /// In this mode only the meta and layout information will be used as a *frame*.
264    pub fn is_renderless(&self) -> bool {
265        self.renderer.is_none()
266    }
267
268    /// Set the color used to clear the pixel frame before drawing this frame.
269    ///
270    /// Note the default clear color is `rgba(0, 0, 0, 0)`, and it is not retained, a property
271    /// that sets the clear color must set it every render.
272    ///
273    /// Note that the clear color is always *rendered* first before all other layers, if more then
274    /// one layer sets the clear color only the value set on the top-most layer is used.
275    pub fn set_clear_color(&mut self, color: Rgba) {
276        self.clear_color = Some(color);
277    }
278
279    /// Connection to the renderer that will render this frame.
280    ///
281    /// Returns `None` when in [renderless](Self::is_renderless) mode.
282    pub fn renderer(&self) -> Option<&ViewRenderer> {
283        self.renderer.as_ref()
284    }
285
286    /// Id of the new frame.
287    pub fn frame_id(&self) -> FrameId {
288        self.frame_id
289    }
290
291    /// Id of the current widget context.
292    pub fn widget_id(&self) -> WidgetId {
293        self.widget_id
294    }
295
296    /// Current transform.
297    pub fn transform(&self) -> &PxTransform {
298        &self.transform
299    }
300
301    /// Returns `true` if hit-testing is enabled in the widget context, if `false` methods that push
302    /// a hit-test silently skip.
303    ///
304    /// This can be set to `false` in a context using [`with_hit_tests_disabled`].
305    ///
306    /// [`with_hit_tests_disabled`]: Self::with_hit_tests_disabled
307    pub fn is_hit_testable(&self) -> bool {
308        self.hit_testable
309    }
310
311    /// Returns `true` if display items are actually generated, if `false` only transforms and hit-test are rendered.
312    pub fn is_visible(&self) -> bool {
313        self.visible
314    }
315
316    /// Returns `true` if hit-tests are automatically pushed by `push_*` methods.
317    ///
318    /// Note that hit-tests are only added if [`is_hit_testable`] is `true`.
319    ///
320    /// [`is_hit_testable`]: Self::is_hit_testable
321    pub fn auto_hit_test(&self) -> bool {
322        self.auto_hit_test
323    }
324
325    /// Runs `render` with `aa` used as the default text anti-aliasing mode.
326    pub fn with_default_font_aa(&mut self, aa: FontAntiAliasing, render: impl FnOnce(&mut Self)) {
327        let parent = mem::replace(&mut self.default_font_aa, aa);
328        render(self);
329        self.default_font_aa = parent;
330    }
331
332    /// Runs `render` with hit-tests disabled, inside `render` [`is_hit_testable`] is `false`, after
333    /// it is the current value.
334    ///
335    /// [`is_hit_testable`]: Self::is_hit_testable
336    pub fn with_hit_tests_disabled(&mut self, render: impl FnOnce(&mut Self)) {
337        let prev = mem::replace(&mut self.hit_testable, false);
338        render(self);
339        self.hit_testable = prev;
340    }
341
342    /// Runs `render` with [`auto_hit_test`] set to a value for the duration of the `render` call.
343    ///
344    /// If this is used, [`FrameUpdate::with_auto_hit_test`] must also be used.
345    ///
346    /// [`auto_hit_test`]: Self::auto_hit_test
347    pub fn with_auto_hit_test(&mut self, auto_hit_test: bool, render: impl FnOnce(&mut Self)) {
348        let prev = mem::replace(&mut self.auto_hit_test, auto_hit_test);
349        render(self);
350        self.auto_hit_test = prev;
351    }
352
353    /// Current culling rect, widgets with outer-bounds that don't intersect this rect are rendered [hidden].
354    ///
355    /// [hidden]: Self::hide
356    pub fn auto_hide_rect(&self) -> PxRect {
357        self.auto_hide_rect
358    }
359
360    /// Runs `render` and [`hide`] all widgets with outer-bounds that don't intersect with the `auto_hide_rect`.
361    ///
362    /// [`hide`]: Self::hide
363    pub fn with_auto_hide_rect(&mut self, auto_hide_rect: PxRect, render: impl FnOnce(&mut Self)) {
364        let parent_rect = mem::replace(&mut self.auto_hide_rect, auto_hide_rect);
365        render(self);
366        self.auto_hide_rect = parent_rect;
367    }
368
369    /// Start a new widget outer context, this sets [`is_outer`] to `true` until an inner call to [`push_inner`],
370    /// during this period properties can configure the widget stacking context and actual rendering and transforms
371    /// are discouraged.
372    ///
373    /// If the widget has been rendered before, render was not requested for it and [`can_reuse`] allows reuse, the `render`
374    /// closure is not called, an only a reference to the widget range in the previous frame is send.
375    ///
376    /// If the widget is collapsed during layout it is not rendered. See [`WidgetLayout::collapse`] for more details.
377    ///
378    /// [`is_outer`]: Self::is_outer
379    /// [`push_inner`]: Self::push_inner
380    /// [`can_reuse`]: Self::can_reuse
381    /// [`WidgetLayout::collapse`]: crate::widget::info::WidgetLayout::collapse
382    pub fn push_widget(&mut self, render: impl FnOnce(&mut Self)) {
383        // to reduce code bloat, method split to before, push and after. Only push is generic.
384        #[derive(Default)]
385        struct PushWidget {
386            parent_visible: bool,
387            parent_perspective: Option<(f32, PxPoint)>,
388            parent_can_reuse: bool,
389            widget_z: usize,
390            outer_transform: PxTransform,
391            undo_prev_outer_transform: Option<PxTransform>,
392            reuse: Option<ReuseRange>,
393            reused: bool,
394            display_count: usize,
395            child_offset: PxVector,
396
397            wgt_info: Option<WidgetInfo>,
398            collapsed: bool,
399        }
400        impl PushWidget {
401            fn before(&mut self, builder: &mut FrameBuilder) {
402                let wgt_info = WIDGET.info();
403                let id = wgt_info.id();
404
405                #[cfg(debug_assertions)]
406                if builder.widget_data.is_some() && WIDGET.parent_id().is_some() {
407                    tracing::error!(
408                        "called `push_widget` for `{}` without calling `push_inner` for the parent `{}`",
409                        WIDGET.trace_id(),
410                        builder.widget_id
411                    );
412                }
413
414                let bounds = wgt_info.bounds_info();
415                let tree = wgt_info.tree();
416
417                if bounds.is_collapsed() {
418                    // collapse
419                    for info in wgt_info.self_and_descendants() {
420                        info.bounds_info().set_rendered(None, tree);
421                    }
422                    // LAYOUT can be pending if parent called `collapse_child`, cleanup here.
423                    let _ = WIDGET.take_update(UpdateFlags::LAYOUT | UpdateFlags::RENDER | UpdateFlags::RENDER_UPDATE);
424                    let _ = WIDGET.take_render_reuse(&builder.render_widgets, &builder.render_update_widgets);
425                    self.collapsed = true;
426                    return;
427                } else {
428                    #[cfg(debug_assertions)]
429                    if WIDGET.pending_update().contains(UpdateFlags::LAYOUT) {
430                        // pending layout requested from inside the widget should have updated before render,
431                        // this indicates that a widget skipped layout without properly collapsing.
432                        tracing::error!("called `push_widget` for `{}` with pending layout", WIDGET.trace_id());
433                    }
434                }
435
436                let mut try_reuse = true;
437
438                let prev_outer = bounds.outer_transform();
439                self.outer_transform = PxTransform::from(builder.child_offset).then(&builder.transform);
440                bounds.set_outer_transform(self.outer_transform, tree);
441
442                if bounds.parent_child_offset() != builder.child_offset {
443                    bounds.set_parent_child_offset(builder.child_offset);
444                    try_reuse = false;
445                }
446                let outer_bounds = bounds.outer_bounds();
447
448                self.parent_visible = builder.visible;
449
450                if bounds.can_auto_hide() {
451                    // collapse already handled, don't hide empty bounds here
452                    // the bounds could be empty with visible content still,
453                    // for example a Preserve3D object rotated 90º with 3D children also rotated.
454                    let mut outer_bounds = outer_bounds;
455                    if outer_bounds.size.width < Px(1) {
456                        outer_bounds.size.width = Px(1);
457                    }
458                    if outer_bounds.size.height < Px(1) {
459                        outer_bounds.size.height = Px(1);
460                    }
461                    match builder.auto_hide_rect.intersection(&outer_bounds) {
462                        Some(cull) => {
463                            let partial = cull != outer_bounds;
464                            if partial || bounds.is_partially_culled() {
465                                // partial cull, cannot reuse because descendant vis may have changed.
466                                try_reuse = false;
467                                bounds.set_is_partially_culled(partial);
468                            }
469                        }
470                        None => {
471                            // full cull
472                            builder.visible = false;
473                        }
474                    }
475                } else {
476                    bounds.set_is_partially_culled(false);
477                }
478
479                self.parent_perspective = builder.perspective;
480                builder.perspective = wgt_info.perspective();
481                if let Some((_, o)) = &mut builder.perspective {
482                    *o -= builder.child_offset;
483                }
484
485                let can_reuse = builder.view_process_has_frame
486                    && match bounds.render_info() {
487                        Some(i) => i.visible == builder.visible && i.parent_perspective == builder.perspective,
488                        // cannot reuse if the widget was not rendered in the previous frame (clear stale reuse ranges in descendants).
489                        None => false,
490                    };
491                self.parent_can_reuse = mem::replace(&mut builder.can_reuse, can_reuse);
492
493                try_reuse &= can_reuse;
494
495                builder.widget_count += 1;
496                self.widget_z = builder.widget_count;
497
498                self.reuse = WIDGET.take_render_reuse(&builder.render_widgets, &builder.render_update_widgets);
499                if !try_reuse {
500                    self.reuse = None;
501                }
502
503                if self.reuse.is_some() {
504                    // check if is possible to reuse.
505                    if let Some(undo_prev) = prev_outer.inverse() {
506                        self.undo_prev_outer_transform = Some(undo_prev);
507                    } else {
508                        self.reuse = None; // cannot reuse because cannot undo prev-transform.
509                    }
510                }
511
512                let index = builder.hit_clips.push_child(id);
513                bounds.set_hit_index(index);
514
515                self.reused = true;
516                self.display_count = builder.display_list.len();
517
518                self.child_offset = mem::take(&mut builder.child_offset);
519
520                self.wgt_info = Some(wgt_info);
521            }
522            fn push(&mut self, builder: &mut FrameBuilder, render: impl FnOnce(&mut FrameBuilder)) {
523                if self.collapsed {
524                    return;
525                }
526
527                // try to reuse, or calls the closure and saves the reuse range.
528                builder.push_reuse(&mut self.reuse, |frame| {
529                    // did not reuse, render widget.
530
531                    self.reused = false;
532                    self.undo_prev_outer_transform = None;
533
534                    frame.widget_data = Some(WidgetData {
535                        filter: vec![],
536                        blend: MixBlendMode::Normal,
537                        backdrop_filter: vec![],
538                        parent_child_offset: self.child_offset,
539                        inner_is_set: frame.perspective.is_some(),
540                        inner_transform: PxTransform::identity(),
541                    });
542                    let parent_widget = mem::replace(&mut frame.widget_id, self.wgt_info.as_ref().unwrap().id());
543
544                    render(frame);
545
546                    frame.widget_id = parent_widget;
547                    frame.widget_data = None;
548                });
549            }
550            fn after(self, builder: &mut FrameBuilder) {
551                if self.collapsed {
552                    return;
553                }
554
555                WIDGET.set_render_reuse(self.reuse);
556
557                let wgt_info = self.wgt_info.unwrap();
558                let id = wgt_info.id();
559                let tree = wgt_info.tree();
560                let bounds = wgt_info.bounds_info();
561
562                if self.reused {
563                    // if did reuse, patch transforms and z-indexes.
564
565                    let _span = tracing::trace_span!("reuse-descendants", ?id).entered();
566
567                    let transform_patch = self.undo_prev_outer_transform.and_then(|t| {
568                        let t = t.then(&self.outer_transform);
569                        if t != PxTransform::identity() { Some(t) } else { None }
570                    });
571
572                    let current_wgt = tree.get(id).unwrap();
573                    let z_patch = self.widget_z as i64 - current_wgt.z_index().map(|(b, _)| u32::from(b) as i64).unwrap_or(0);
574
575                    let update_transforms = transform_patch.is_some();
576                    let seg_id = builder.widget_count_offsets.id();
577
578                    // apply patches, only iterates over descendants once.
579                    if update_transforms {
580                        let transform_patch = transform_patch.unwrap();
581
582                        // patch descendants outer and inner.
583                        let update_transforms_and_z = |info: WidgetInfo| {
584                            let b = info.bounds_info();
585
586                            if b != bounds {
587                                // only patch outer of descendants
588                                b.set_outer_transform(b.outer_transform().then(&transform_patch), tree);
589                            }
590                            b.set_inner_transform(
591                                b.inner_transform().then(&transform_patch),
592                                tree,
593                                info.id(),
594                                info.parent().map(|p| p.inner_bounds()),
595                            );
596
597                            if let Some(i) = b.render_info() {
598                                let (back, front) = info.z_index().unwrap();
599                                let back = u32::from(back) as i64 + z_patch;
600                                let front = u32::from(front) as i64 + z_patch;
601
602                                b.set_rendered(
603                                    Some(WidgetRenderInfo {
604                                        visible: i.visible,
605                                        parent_perspective: i.parent_perspective,
606                                        seg_id,
607                                        back: back.try_into().unwrap(),
608                                        front: front.try_into().unwrap(),
609                                    }),
610                                    tree,
611                                );
612                            }
613                        };
614
615                        let targets = current_wgt.self_and_descendants();
616                        if PARALLEL_VAR.get().contains(Parallel::RENDER) {
617                            targets.par_bridge().for_each(update_transforms_and_z);
618                        } else {
619                            targets.for_each(update_transforms_and_z);
620                        }
621                    } else {
622                        let update_z = |info: WidgetInfo| {
623                            let bounds = info.bounds_info();
624
625                            if let Some(i) = bounds.render_info() {
626                                let (back, front) = info.z_index().unwrap();
627                                let mut back = u32::from(back) as i64 + z_patch;
628                                let mut front = u32::from(front) as i64 + z_patch;
629                                if back < 0 {
630                                    tracing::error!("incorrect back Z-index ({back}) after patch ({z_patch})");
631                                    back = 0;
632                                }
633                                if front < 0 {
634                                    tracing::error!("incorrect front Z-index ({front}) after patch ({z_patch})");
635                                    front = 0;
636                                }
637                                bounds.set_rendered(
638                                    Some(WidgetRenderInfo {
639                                        visible: i.visible,
640                                        parent_perspective: i.parent_perspective,
641                                        seg_id,
642                                        back: back as _,
643                                        front: front as _,
644                                    }),
645                                    tree,
646                                );
647                            }
648                        };
649
650                        let targets = current_wgt.self_and_descendants();
651                        if PARALLEL_VAR.get().contains(Parallel::RENDER) {
652                            targets.par_bridge().for_each(update_z);
653                        } else {
654                            targets.for_each(update_z);
655                        }
656                    }
657
658                    // increment by reused
659                    builder.widget_count = bounds.render_info().map(|i| i.front).unwrap_or(builder.widget_count);
660                } else {
661                    // if did not reuse and rendered
662                    bounds.set_rendered(
663                        Some(WidgetRenderInfo {
664                            visible: builder.display_list.len() > self.display_count,
665                            parent_perspective: builder.perspective,
666                            seg_id: builder.widget_count_offsets.id(),
667                            back: self.widget_z,
668                            front: builder.widget_count,
669                        }),
670                        tree,
671                    );
672                }
673
674                builder.visible = self.parent_visible;
675                builder.perspective = self.parent_perspective;
676                builder.can_reuse = self.parent_can_reuse;
677            }
678        }
679        let mut push = PushWidget::default();
680        push.before(self);
681        push.push(self, render);
682        push.after(self);
683    }
684
685    /// If previously generated display list items are available for reuse.
686    ///
687    /// If `false` widgets must do a full render using [`push_widget`] even if they did not request a render.
688    ///
689    /// [`push_widget`]: Self::push_widget
690    pub fn can_reuse(&self) -> bool {
691        self.can_reuse
692    }
693
694    /// Calls `render` with [`can_reuse`] set to `false`.
695    ///
696    /// [`can_reuse`]: Self::can_reuse
697    pub fn with_no_reuse(&mut self, render: impl FnOnce(&mut Self)) {
698        let prev_can_reuse = self.can_reuse;
699        self.can_reuse = false;
700        render(self);
701        self.can_reuse = prev_can_reuse;
702    }
703
704    /// If `group` has a range and [`can_reuse`] a reference to the items is added, otherwise `generate` is called and
705    /// any display items generated by it are tracked in `group`.
706    ///
707    /// Note that hit-test items are not part of `group`, only display items are reused here, hit-test items for a widget are only reused if the entire
708    /// widget is reused in [`push_widget`]. This method is recommended for widgets that render a large volume of display data that is likely to be reused
709    /// even when the widget itself is not reused, an example is a widget that renders text and a background, the entire widget is invalidated when the
710    /// background changes, but the text is the same, so placing the text in a reuse group avoids having to upload all glyphs again.
711    ///
712    /// [`can_reuse`]: Self::can_reuse
713    /// [`push_widget`]: Self::push_widget
714    pub fn push_reuse(&mut self, group: &mut Option<ReuseRange>, generate: impl FnOnce(&mut Self)) {
715        if self.can_reuse
716            && let Some(g) = &group
717        {
718            if self.visible {
719                self.display_list.push_reuse_range(g);
720            }
721            return;
722        }
723        *group = None;
724        let parent_group = self.open_reuse.replace(self.display_list.start_reuse_range());
725
726        generate(self);
727
728        let start = self.open_reuse.take().unwrap();
729        let range = self.display_list.finish_reuse_range(start);
730        *group = Some(range);
731        self.open_reuse = parent_group;
732    }
733
734    /// Calls `render` with [`is_visible`] set to `false`.
735    ///
736    /// Nodes that set the visibility to [`Hidden`] must render using this method and update using the [`FrameUpdate::hidden`] method.
737    ///
738    /// Note that for [`Collapsed`] the widget is automatically not rendered if [`WidgetLayout::collapse`] or other related
739    /// collapse method was already called for it.
740    ///
741    /// [`is_visible`]: Self::is_visible
742    /// [`Hidden`]: crate::widget::info::Visibility::Hidden
743    /// [`Collapsed`]: crate::widget::info::Visibility::Collapsed
744    /// [`WidgetLayout::collapse`]: crate::widget::info::WidgetLayout::collapse
745    pub fn hide(&mut self, render: impl FnOnce(&mut Self)) {
746        let parent_visible = mem::replace(&mut self.visible, false);
747        render(self);
748        self.visible = parent_visible;
749    }
750
751    /// Calls `render` with back face visibility set to `visible`.
752    ///
753    /// All visual display items pushed inside `render` will have the `visible` flag.
754    pub fn with_backface_visibility(&mut self, visible: bool, render: impl FnOnce(&mut Self)) {
755        if self.backface_visible != visible {
756            let parent = self.backface_visible;
757            self.display_list.set_backface_visibility(visible);
758            render(self);
759            self.display_list.set_backface_visibility(parent);
760            self.backface_visible = parent;
761        } else {
762            render(self);
763        }
764    }
765
766    /// Returns `true` if the widget stacking context is still being build.
767    ///
768    /// This is `true` when inside an [`push_widget`] call but `false` when inside an [`push_inner`] call.
769    ///
770    /// [`push_widget`]: Self::push_widget
771    /// [`push_inner`]: Self::push_inner
772    pub fn is_outer(&self) -> bool {
773        self.widget_data.is_some()
774    }
775
776    /// Includes a widget filter and continues the render build.
777    ///
778    /// This is valid only when [`is_outer`].
779    ///
780    /// When [`push_inner`] is called a stacking context is created for the widget that includes the `filter`.
781    ///
782    /// [`is_outer`]: Self::is_outer
783    /// [`push_inner`]: Self::push_inner
784    pub fn push_inner_filter(&mut self, filter: RenderFilter, render: impl FnOnce(&mut Self)) {
785        if let Some(data) = self.widget_data.as_mut() {
786            let mut filter = filter;
787            filter.reverse();
788            data.filter.extend(filter.iter().copied());
789
790            render(self);
791        } else {
792            tracing::error!("called `push_inner_filter` inside inner context of `{}`", self.widget_id);
793            render(self);
794        }
795    }
796
797    /// Includes a widget opacity filter and continues the render build.
798    ///
799    /// This is valid only when [`is_outer`].
800    ///
801    /// When [`push_inner`] is called a stacking context is created for the widget that includes the opacity filter.
802    ///
803    /// [`is_outer`]: Self::is_outer
804    /// [`push_inner`]: Self::push_inner
805    pub fn push_inner_opacity(&mut self, bind: FrameValue<f32>, render: impl FnOnce(&mut Self)) {
806        if let Some(data) = self.widget_data.as_mut() {
807            data.filter.push(FilterOp::Opacity(bind));
808
809            render(self);
810        } else {
811            tracing::error!("called `push_inner_opacity` inside inner context of `{}`", self.widget_id);
812            render(self);
813        }
814    }
815
816    /// Include a widget backdrop filter and continue the render build.
817    ///
818    /// This is valid only when [`is_outer`].
819    ///
820    /// When [`push_inner`] is called the widget are is first filled with the backdrop filters.
821    ///
822    /// [`is_outer`]: Self::is_outer
823    /// [`push_inner`]: Self::push_inner
824    pub fn push_inner_backdrop_filter(&mut self, filter: RenderFilter, render: impl FnOnce(&mut Self)) {
825        if let Some(data) = self.widget_data.as_mut() {
826            let mut filter = filter;
827            filter.reverse();
828            data.backdrop_filter.extend(filter.iter().copied());
829
830            render(self);
831        } else {
832            tracing::error!("called `push_inner_backdrop_filter` inside inner context of `{}`", self.widget_id);
833            render(self);
834        }
835    }
836
837    /// Sets the widget blend mode and continue the render build.
838    ///
839    /// This is valid only when [`is_outer`].
840    ///
841    /// When [`push_inner`] is called the `mode` is used to blend with the parent content.
842    ///
843    /// [`is_outer`]: Self::is_outer
844    /// [`push_inner`]: Self::push_inner
845    pub fn push_inner_blend(&mut self, mode: MixBlendMode, render: impl FnOnce(&mut Self)) {
846        if let Some(data) = self.widget_data.as_mut() {
847            data.blend = mode;
848
849            render(self);
850        } else {
851            tracing::error!("called `push_inner_blend` inside inner context of `{}`", self.widget_id);
852            render(self);
853        }
854    }
855
856    /// Pre-starts the scope of a widget with `offset` set for the inner reference frame. The
857    /// `render` closure must call [`push_widget`] before attempting to render.
858    ///
859    /// Nodes that use [`WidgetLayout::with_child`] to optimize reference frames must use this method when
860    /// a reference frame was not created during render.
861    ///
862    /// Nodes that use this must also use [`FrameUpdate::with_child`].
863    ///
864    /// [`push_widget`]: Self::push_widget
865    /// [`WidgetLayout::with_child`]: crate::widget::info::WidgetLayout::with_child
866    pub fn push_child(&mut self, offset: PxVector, render: impl FnOnce(&mut Self)) {
867        if self.widget_data.is_some() {
868            tracing::error!("called `push_child` outside inner context of `{}`", self.widget_id);
869        }
870
871        self.child_offset = offset;
872        render(self);
873        self.child_offset = PxVector::zero();
874    }
875
876    /// Include the `transform` on the widget inner reference frame.
877    ///
878    /// This is valid only when [`is_outer`].
879    ///
880    /// When [`push_inner`] is called a reference frame is created for the widget that applies the layout transform then the `transform`.
881    ///
882    /// [`is_outer`]: Self::is_outer
883    /// [`push_inner`]: Self::push_inner
884    pub fn push_inner_transform(&mut self, transform: &PxTransform, render: impl FnOnce(&mut Self)) {
885        if let Some(data) = &mut self.widget_data {
886            let parent_transform = data.inner_transform;
887            let parent_is_set = mem::replace(&mut data.inner_is_set, true);
888            data.inner_transform = data.inner_transform.then(transform);
889
890            render(self);
891
892            if let Some(data) = &mut self.widget_data {
893                data.inner_transform = parent_transform;
894                data.inner_is_set = parent_is_set;
895            }
896        } else {
897            tracing::error!("called `push_inner_transform` inside inner context of `{}`", self.widget_id);
898            render(self);
899        }
900    }
901
902    /// Push the widget reference frame and stacking context then call `render` inside of it.
903    ///
904    /// If `layout_translation_animating` is `false` the view-process can still be updated using [`FrameUpdate::update_inner`], but
905    /// a full webrender frame will be generated for each update, if is `true` webrender frame updates are used, but webrender
906    /// skips some optimizations, such as auto-merging transforms. When in doubt setting this to `true` is better than `false` as
907    /// a webrender frame update is faster than a full frame, and the transform related optimizations don't gain much.
908    pub fn push_inner(
909        &mut self,
910        layout_translation_key: FrameValueKey<PxTransform>,
911        layout_translation_animating: bool,
912        render: impl FnOnce(&mut Self),
913    ) {
914        // like push_widget, split to reduce generics code bloating
915        #[derive(Default)]
916        struct PushInner {
917            invalid: bool,
918            parent_transform: PxTransform,
919            parent_transform_style: TransformStyle,
920            parent_hit_clips: HitTestClips,
921            parent_parent_inner_bounds: Option<PxRect>,
922            visible: bool,
923            ctx_outside_ref_frame: i32,
924            ctx_inside_ref_frame: i32,
925
926            wgt_info: Option<WidgetInfo>,
927        }
928        impl PushInner {
929            fn before(
930                &mut self,
931                builder: &mut FrameBuilder,
932                layout_translation_key: FrameValueKey<PxTransform>,
933                layout_translation_animating: bool,
934            ) {
935                if let Some(mut data) = builder.widget_data.take() {
936                    self.parent_transform = builder.transform;
937                    self.parent_transform_style = builder.transform_style;
938                    self.parent_hit_clips = mem::take(&mut builder.hit_clips);
939
940                    let wgt_info = WIDGET.info();
941                    let id = wgt_info.id();
942                    let bounds = wgt_info.bounds_info();
943                    let tree = wgt_info.tree();
944
945                    let inner_offset = bounds.inner_offset();
946                    let mut inner_transform = data.inner_transform;
947                    if let Some((d, mut o)) = builder.perspective {
948                        o -= inner_offset;
949                        let x = o.x.0 as f32;
950                        let y = o.y.0 as f32;
951                        let p = PxTransform::translation(-x, -y)
952                            .then(&PxTransform::perspective(d))
953                            .then_translate(euclid::vec2(x, y));
954                        inner_transform = inner_transform.then(&p);
955                    }
956                    let inner_transform = inner_transform.then_translate((data.parent_child_offset + inner_offset).cast());
957
958                    builder.transform = inner_transform.then(&self.parent_transform);
959
960                    bounds.set_inner_transform(builder.transform, tree, id, builder.parent_inner_bounds);
961
962                    self.parent_parent_inner_bounds = builder.parent_inner_bounds.replace(bounds.inner_bounds());
963
964                    if builder.visible {
965                        self.visible = true;
966                        builder.transform_style = wgt_info.transform_style();
967
968                        let has_3d_ctx = matches!(builder.transform_style, TransformStyle::Preserve3D);
969                        let has_filters = !data.filter.is_empty() || data.blend != MixBlendMode::Normal;
970
971                        // reference frame must be just outside the stacking context, except for the
972                        // pre-filter context in Preserve3D roots.
973                        macro_rules! push_reference_frame {
974                            () => {
975                                builder.display_list.push_reference_frame(
976                                    ReferenceFrameId::from_widget(builder.widget_id).into(),
977                                    layout_translation_key.bind(inner_transform, layout_translation_animating),
978                                    builder.transform_style.into(),
979                                    !data.inner_is_set,
980                                );
981                                if !data.backdrop_filter.is_empty() {
982                                    builder
983                                        .display_list
984                                        .push_backdrop_filter(PxRect::from_size(bounds.inner_size()), &data.backdrop_filter);
985                                }
986                            };
987                        }
988
989                        if has_filters {
990                            // we want to apply filters in the top-to-bottom, left-to-right order they appear in
991                            // the widget declaration, but the widget declaration expands to have the top property
992                            // node be inside the bottom property node, so the bottom property ends up inserting
993                            // a filter first, because we cannot insert filters after the child node render is called
994                            // so we need to reverse the filters here. Left-to-right sequences are reversed on insert
995                            // so they get reversed again here and everything ends up in order.
996                            data.filter.reverse();
997
998                            if has_3d_ctx {
999                                // webrender ignores Preserve3D if there are filters, we work around the issue when possible here.
1000
1001                                // push the Preserve3D, unlike CSS we prefer this over filters.
1002
1003                                if matches!(
1004                                    (builder.transform_style, bounds.transform_style()),
1005                                    (TransformStyle::Preserve3D, TransformStyle::Flat)
1006                                ) {
1007                                    // is "flat root", push a nested stacking context with the filters.
1008                                    push_reference_frame!();
1009                                    builder
1010                                        .display_list
1011                                        .push_stacking_context(MixBlendMode::Normal, builder.transform_style, &[]);
1012                                    builder
1013                                        .display_list
1014                                        .push_stacking_context(data.blend, TransformStyle::Flat, &data.filter);
1015                                    self.ctx_inside_ref_frame = 2;
1016                                } else if wgt_info
1017                                    .parent()
1018                                    .map(|p| matches!(p.bounds_info().transform_style(), TransformStyle::Flat))
1019                                    .unwrap_or(false)
1020                                {
1021                                    // is "3D root", push the filters first, then the 3D root.
1022
1023                                    builder
1024                                        .display_list
1025                                        .push_stacking_context(data.blend, TransformStyle::Flat, &data.filter);
1026                                    self.ctx_outside_ref_frame = 1;
1027                                    push_reference_frame!();
1028                                    builder
1029                                        .display_list
1030                                        .push_stacking_context(MixBlendMode::Normal, builder.transform_style, &[]);
1031                                    self.ctx_inside_ref_frame = 1;
1032                                } else {
1033                                    // extends 3D space, cannot splice a filters stacking context because that
1034                                    // would disconnect the sub-tree from the parent space.
1035                                    tracing::warn!(
1036                                        "widget `{id}` cannot have filters because it is `Preserve3D` inside `Preserve3D`, filters & blend ignored"
1037                                    );
1038
1039                                    push_reference_frame!();
1040                                    builder
1041                                        .display_list
1042                                        .push_stacking_context(MixBlendMode::Normal, builder.transform_style, &[]);
1043                                    self.ctx_inside_ref_frame = 1;
1044                                }
1045                            } else {
1046                                // no 3D context, push the filters context
1047                                push_reference_frame!();
1048                                builder
1049                                    .display_list
1050                                    .push_stacking_context(data.blend, TransformStyle::Flat, &data.filter);
1051                                self.ctx_inside_ref_frame = 1;
1052                            }
1053                        } else if has_3d_ctx {
1054                            // just 3D context
1055                            push_reference_frame!();
1056                            builder
1057                                .display_list
1058                                .push_stacking_context(MixBlendMode::Normal, builder.transform_style, &[]);
1059                            self.ctx_inside_ref_frame = 1;
1060                        } else {
1061                            // just flat, no filters
1062                            push_reference_frame!();
1063                        }
1064                    }
1065
1066                    self.wgt_info = Some(wgt_info);
1067                } else {
1068                    tracing::error!("called `push_inner` more then once for `{}`", builder.widget_id);
1069                    self.invalid = true;
1070                }
1071            }
1072            fn push(&mut self, builder: &mut FrameBuilder, render: impl FnOnce(&mut FrameBuilder)) {
1073                if self.invalid {
1074                    return;
1075                }
1076                render(builder)
1077            }
1078            fn after(mut self, builder: &mut FrameBuilder) {
1079                if self.invalid {
1080                    return;
1081                }
1082
1083                if self.visible {
1084                    while self.ctx_inside_ref_frame > 0 {
1085                        builder.display_list.pop_stacking_context();
1086                        self.ctx_inside_ref_frame -= 1;
1087                    }
1088
1089                    builder.display_list.pop_reference_frame();
1090
1091                    while self.ctx_outside_ref_frame > 0 {
1092                        builder.display_list.pop_stacking_context();
1093                        self.ctx_outside_ref_frame -= 1;
1094                    }
1095                }
1096
1097                let wgt_info = self.wgt_info.unwrap();
1098                let bounds = wgt_info.bounds_info();
1099
1100                // shared finish
1101                builder.transform = self.parent_transform;
1102                builder.transform_style = self.parent_transform_style;
1103                builder.parent_inner_bounds = self.parent_parent_inner_bounds;
1104
1105                let hit_clips = mem::replace(&mut builder.hit_clips, self.parent_hit_clips);
1106                bounds.set_hit_clips(hit_clips);
1107
1108                if !builder.debug_dot_overlays.is_empty() && wgt_info.parent().is_none() {
1109                    for (offset, color) in mem::take(&mut builder.debug_dot_overlays) {
1110                        builder.push_debug_dot(offset, color);
1111                    }
1112                }
1113            }
1114        }
1115        let mut push_inner = PushInner::default();
1116        push_inner.before(self, layout_translation_key, layout_translation_animating);
1117        push_inner.push(self, render);
1118        push_inner.after(self);
1119    }
1120
1121    /// Returns `true` if the widget reference frame and stacking context is pushed and now is time for rendering the widget.
1122    ///
1123    /// This is `true` when inside a [`push_inner`] call but `false` when inside a [`push_widget`] call.
1124    ///
1125    /// [`push_widget`]: Self::push_widget
1126    /// [`push_inner`]: Self::push_inner
1127    pub fn is_inner(&self) -> bool {
1128        self.widget_data.is_none()
1129    }
1130
1131    /// Gets the inner-bounds hit-test shape builder.
1132    ///
1133    /// Note that all hit-test is clipped by the inner-bounds, the shapes pushed with this builder
1134    /// only refine the widget inner-bounds, shapes out-of-bounds are clipped.
1135    pub fn hit_test(&mut self) -> HitTestBuilder<'_> {
1136        expect_inner!(self.hit_test);
1137
1138        HitTestBuilder {
1139            hit_clips: &mut self.hit_clips,
1140            is_hit_testable: self.hit_testable,
1141        }
1142    }
1143
1144    /// Calls `render` with a new clip context that adds the `clip_rect`.
1145    ///
1146    /// If `clip_out` is `true` only pixels outside the rect are visible. If `hit_test` is `true` the hit-test shapes
1147    /// rendered inside `render` are also clipped.
1148    ///
1149    /// Note that hit-test will be generated if `hit_test` or [`auto_hit_test`] is `true`.
1150    ///
1151    /// [`auto_hit_test`]: Self::auto_hit_test
1152    pub fn push_clip_rect(&mut self, clip_rect: PxRect, clip_out: bool, hit_test: bool, render: impl FnOnce(&mut FrameBuilder)) {
1153        self.push_clips(move |c| c.push_clip_rect(clip_rect, clip_out, hit_test), render)
1154    }
1155
1156    /// Calls `render` with a new clip context that adds the `clip_rect` with rounded `corners`.
1157    ///
1158    /// If `clip_out` is `true` only pixels outside the rounded rect are visible. If `hit_test` is `true` the hit-test shapes
1159    /// rendered inside `render` are also clipped.
1160    ///
1161    /// Note that hit-test will be generated if `hit_test` or [`auto_hit_test`] is `true`.
1162    ///
1163    /// [`auto_hit_test`]: Self::auto_hit_test
1164    pub fn push_clip_rounded_rect(
1165        &mut self,
1166        clip_rect: PxRect,
1167        corners: PxCornerRadius,
1168        clip_out: bool,
1169        hit_test: bool,
1170        render: impl FnOnce(&mut FrameBuilder),
1171    ) {
1172        self.push_clips(move |c| c.push_clip_rounded_rect(clip_rect, corners, clip_out, hit_test), render)
1173    }
1174
1175    /// Calls `clips` to push multiple clips that define a new clip context, then calls `render` in the clip context.
1176    pub fn push_clips(&mut self, clips: impl FnOnce(&mut ClipBuilder), render: impl FnOnce(&mut FrameBuilder)) {
1177        expect_inner!(self.push_clips);
1178
1179        let (mut render_count, mut hit_test_count) = {
1180            let mut clip_builder = ClipBuilder {
1181                builder: self,
1182                render_count: 0,
1183                hit_test_count: 0,
1184            };
1185            clips(&mut clip_builder);
1186            (clip_builder.render_count, clip_builder.hit_test_count)
1187        };
1188
1189        render(self);
1190
1191        while hit_test_count > 0 {
1192            hit_test_count -= 1;
1193
1194            self.hit_clips.pop_clip();
1195        }
1196        while render_count > 0 {
1197            render_count -= 1;
1198
1199            self.display_list.pop_clip();
1200        }
1201    }
1202
1203    /// Push an image mask that affects all visual rendered by `render`.
1204    pub fn push_mask(&mut self, image: &impl Img, rect: PxRect, render: impl FnOnce(&mut Self)) {
1205        let mut pop = false;
1206        if self.visible
1207            && let Some(r) = &self.renderer
1208        {
1209            self.display_list.push_mask(image.renderer_id(r), rect);
1210            pop = true;
1211        }
1212
1213        render(self);
1214
1215        if pop {
1216            self.display_list.pop_mask();
1217        }
1218    }
1219
1220    /// Calls `render` inside a new reference frame transformed by `transform`.
1221    ///
1222    /// The `is_2d_scale_translation` flag optionally marks the `transform` as only ever having a simple 2D scale or translation,
1223    /// allowing for webrender optimizations.
1224    ///
1225    /// If `hit_test` is `true` the hit-test shapes rendered inside `render` for the same widget are also transformed.
1226    ///
1227    /// Note that [`auto_hit_test`] overwrites `hit_test` if it is `true`.
1228    ///
1229    /// [`push_inner`]: Self::push_inner
1230    /// [`WidgetLayout`]: crate::widget::info::WidgetLayout
1231    /// [`auto_hit_test`]: Self::auto_hit_test
1232    /// [`Preserve3D`]: TransformStyle::Preserve3D
1233    pub fn push_reference_frame(
1234        &mut self,
1235        key: ReferenceFrameId,
1236        transform: FrameValue<PxTransform>,
1237        is_2d_scale_translation: bool,
1238        hit_test: bool,
1239        render: impl FnOnce(&mut Self),
1240    ) {
1241        let transform_value = transform.value();
1242
1243        let prev_transform = self.transform;
1244        self.transform = transform_value.then(&prev_transform);
1245
1246        if self.visible {
1247            self.display_list
1248                .push_reference_frame(key.into(), transform, self.transform_style, is_2d_scale_translation);
1249        }
1250
1251        let hit_test = hit_test || self.auto_hit_test;
1252
1253        if hit_test {
1254            self.hit_clips.push_transform(transform);
1255        }
1256
1257        render(self);
1258
1259        if self.visible {
1260            self.display_list.pop_reference_frame();
1261        }
1262        self.transform = prev_transform;
1263
1264        if hit_test {
1265            self.hit_clips.pop_transform();
1266        }
1267    }
1268
1269    /// Calls `render` with added `blend` and `filter` stacking context.
1270    ///
1271    /// Note that this introduces a new stacking context, you can use the [`push_inner_blend`] and [`push_inner_filter`] methods to
1272    /// add to the widget stacking context.
1273    ///
1274    /// [`push_inner_blend`]: Self::push_inner_blend
1275    /// [`push_inner_filter`]: Self::push_inner_filter
1276    pub fn push_filter(&mut self, blend: MixBlendMode, filter: &RenderFilter, render: impl FnOnce(&mut Self)) {
1277        expect_inner!(self.push_filter);
1278
1279        if self.visible {
1280            self.display_list.push_stacking_context(blend, self.transform_style, filter);
1281
1282            render(self);
1283
1284            self.display_list.pop_stacking_context();
1285        } else {
1286            render(self);
1287        }
1288    }
1289
1290    /// Calls `render` with added opacity stacking context.
1291    ///
1292    /// Note that this introduces a new stacking context, you can use the [`push_inner_opacity`] method to
1293    /// add to the widget stacking context.
1294    ///
1295    /// [`push_inner_opacity`]: Self::push_inner_opacity
1296    pub fn push_opacity(&mut self, bind: FrameValue<f32>, render: impl FnOnce(&mut Self)) {
1297        expect_inner!(self.push_opacity);
1298
1299        if self.visible {
1300            self.display_list
1301                .push_stacking_context(MixBlendMode::Normal, self.transform_style, &[FilterOp::Opacity(bind)]);
1302
1303            render(self);
1304
1305            self.display_list.pop_stacking_context();
1306        } else {
1307            render(self);
1308        }
1309    }
1310
1311    /// Push a standalone backdrop filter.
1312    ///
1313    /// The `filter` will apply to all pixels already rendered in `clip_rect`.
1314    ///
1315    /// Note that you can add backdrop filters to the widget using the [`push_inner_backdrop_filter`] method.
1316    ///
1317    /// [`push_inner_backdrop_filter`]: Self::push_inner_backdrop_filter
1318    pub fn push_backdrop_filter(&mut self, clip_rect: PxRect, filter: &RenderFilter) {
1319        expect_inner!(self.push_backdrop_filter);
1320        warn_empty!(self.push_backdrop_filter(clip_rect));
1321
1322        if self.visible {
1323            self.display_list.push_backdrop_filter(clip_rect, filter);
1324        }
1325
1326        if self.auto_hit_test {
1327            self.hit_test().push_rect(clip_rect);
1328        }
1329    }
1330
1331    /// Push a border.
1332    pub fn push_border(&mut self, bounds: PxRect, widths: PxSideOffsets, sides: BorderSides, radius: PxCornerRadius) {
1333        expect_inner!(self.push_border);
1334        warn_empty!(self.push_border(bounds));
1335
1336        if self.visible {
1337            self.display_list.push_border(
1338                bounds,
1339                widths,
1340                sides.top.into(),
1341                sides.right.into(),
1342                sides.bottom.into(),
1343                sides.left.into(),
1344                radius,
1345            );
1346        }
1347
1348        if self.auto_hit_test {
1349            self.hit_test().push_border(bounds, widths, radius);
1350        }
1351    }
1352
1353    /// Push a nine-patch border with image source.
1354    #[expect(clippy::too_many_arguments)]
1355    pub fn push_border_image(
1356        &mut self,
1357        bounds: PxRect,
1358        widths: PxSideOffsets,
1359        slice: PxSideOffsets,
1360        fill: bool,
1361        repeat_horizontal: RepeatMode,
1362        repeat_vertical: RepeatMode,
1363        image: &impl Img,
1364        rendering: ImageRendering,
1365    ) {
1366        expect_inner!(self.push_border_image);
1367        warn_empty!(self.push_border_image(bounds));
1368
1369        if self.visible
1370            && let Some(r) = &self.renderer
1371        {
1372            let image_id = image.renderer_id(r);
1373            self.display_list.push_nine_patch_border(
1374                bounds,
1375                NinePatchSource::Image { image_id, rendering },
1376                widths,
1377                image.size(),
1378                slice,
1379                fill,
1380                repeat_horizontal,
1381                repeat_vertical,
1382            )
1383        }
1384    }
1385
1386    /// Push a nine-patch border with linear gradient source.
1387    #[expect(clippy::too_many_arguments)]
1388    pub fn push_border_linear_gradient(
1389        &mut self,
1390        bounds: PxRect,
1391        widths: PxSideOffsets,
1392        slice: PxSideOffsets,
1393        fill: bool,
1394        repeat_horizontal: RepeatMode,
1395        repeat_vertical: RepeatMode,
1396        line: PxLine,
1397        stops: &[RenderGradientStop],
1398        extend_mode: RenderExtendMode,
1399    ) {
1400        debug_assert!(stops.len() >= 2);
1401        debug_assert!(stops[0].offset.abs() < 0.00001, "first color stop must be at offset 0.0");
1402        debug_assert!(
1403            (stops[stops.len() - 1].offset - 1.0).abs() < 0.00001,
1404            "last color stop must be at offset 1.0"
1405        );
1406        expect_inner!(self.push_border_linear_gradient);
1407        warn_empty!(self.push_border_linear_gradient(bounds));
1408
1409        if self.visible && !stops.is_empty() {
1410            self.display_list.push_nine_patch_border(
1411                bounds,
1412                NinePatchSource::LinearGradient {
1413                    start_point: line.start.cast(),
1414                    end_point: line.end.cast(),
1415                    extend_mode,
1416                    stops: stops.to_vec().into_boxed_slice(),
1417                },
1418                widths,
1419                bounds.size,
1420                slice,
1421                fill,
1422                repeat_horizontal,
1423                repeat_vertical,
1424            );
1425        }
1426    }
1427
1428    /// Push a nine-patch border with radial gradient source.
1429    #[expect(clippy::too_many_arguments)]
1430    pub fn push_border_radial_gradient(
1431        &mut self,
1432        bounds: PxRect,
1433        widths: PxSideOffsets,
1434        slice: PxSideOffsets,
1435        fill: bool,
1436        repeat_horizontal: RepeatMode,
1437        repeat_vertical: RepeatMode,
1438        center: PxPoint,
1439        radius: PxSize,
1440        stops: &[RenderGradientStop],
1441        extend_mode: RenderExtendMode,
1442    ) {
1443        debug_assert!(stops.len() >= 2);
1444        debug_assert!(stops[0].offset.abs() < 0.00001, "first color stop must be at offset 0.0");
1445        debug_assert!(
1446            (stops[stops.len() - 1].offset - 1.0).abs() < 0.00001,
1447            "last color stop must be at offset 1.0"
1448        );
1449
1450        expect_inner!(self.push_border_radial_gradient);
1451        warn_empty!(self.push_border_radial_gradient(bounds));
1452
1453        if self.visible && !stops.is_empty() {
1454            self.display_list.push_nine_patch_border(
1455                bounds,
1456                NinePatchSource::RadialGradient {
1457                    center: center.cast(),
1458                    radius: radius.cast(),
1459                    start_offset: 0.0,
1460                    end_offset: 1.0,
1461                    extend_mode,
1462                    stops: stops.to_vec().into_boxed_slice(),
1463                },
1464                widths,
1465                bounds.size,
1466                slice,
1467                fill,
1468                repeat_horizontal,
1469                repeat_vertical,
1470            );
1471        }
1472    }
1473
1474    /// Push a nine-patch border with conic gradient source.
1475    #[expect(clippy::too_many_arguments)]
1476    pub fn push_border_conic_gradient(
1477        &mut self,
1478        bounds: PxRect,
1479        widths: PxSideOffsets,
1480        slice: PxSideOffsets,
1481        fill: bool,
1482        repeat_horizontal: RepeatMode,
1483        repeat_vertical: RepeatMode,
1484        center: PxPoint,
1485        angle: AngleRadian,
1486        stops: &[RenderGradientStop],
1487        extend_mode: RenderExtendMode,
1488    ) {
1489        debug_assert!(stops.len() >= 2);
1490        debug_assert!(stops[0].offset.abs() < 0.00001, "first color stop must be at offset 0.0");
1491        debug_assert!(
1492            (stops[stops.len() - 1].offset - 1.0).abs() < 0.00001,
1493            "last color stop must be at offset 1.0"
1494        );
1495
1496        expect_inner!(self.push_border_conic_gradient);
1497        warn_empty!(self.push_border_conic_gradient(bounds));
1498
1499        if self.visible && !stops.is_empty() {
1500            self.display_list.push_nine_patch_border(
1501                bounds,
1502                NinePatchSource::ConicGradient {
1503                    center: center.cast(),
1504                    angle,
1505                    start_offset: 0.0,
1506                    end_offset: 1.0,
1507                    extend_mode,
1508                    stops: stops.to_vec().into_boxed_slice(),
1509                },
1510                widths,
1511                bounds.size,
1512                slice,
1513                fill,
1514                repeat_horizontal,
1515                repeat_vertical,
1516            );
1517        }
1518    }
1519
1520    /// Push a text run.
1521    pub fn push_text(
1522        &mut self,
1523        clip_rect: PxRect,
1524        glyphs: &[GlyphInstance],
1525        font: &impl Font,
1526        color: FrameValue<Rgba>,
1527        synthesis: FontSynthesis,
1528        aa: FontAntiAliasing,
1529    ) {
1530        expect_inner!(self.push_text);
1531        warn_empty!(self.push_text(clip_rect));
1532
1533        if let Some(r) = &self.renderer
1534            && !glyphs.is_empty()
1535            && self.visible
1536            && !font.is_empty_fallback()
1537        {
1538            let font_id = font.renderer_id(r, synthesis);
1539
1540            let opts = GlyphOptions::new(
1541                match aa {
1542                    FontAntiAliasing::Default => self.default_font_aa,
1543                    aa => aa,
1544                },
1545                synthesis.contains(FontSynthesis::BOLD),
1546                synthesis.contains(FontSynthesis::OBLIQUE),
1547            );
1548            self.display_list.push_text(clip_rect, font_id, glyphs, color, opts);
1549        }
1550
1551        if self.auto_hit_test {
1552            self.hit_test().push_rect(clip_rect);
1553        }
1554    }
1555
1556    /// Push an image.
1557    ///
1558    /// The image is resized to `tile_size` and them tiled to fill the `image_size`. The `clip_rect` is applied to the `image_size` area.
1559    ///
1560    /// The `rendering` value defines the real time scaling algorithm used to resize the image on the GPU. Note that the renderer may
1561    /// also generate high quality downscaled images on the CPU, that is not affected by `rendering`.
1562    #[allow(clippy::too_many_arguments)]
1563    pub fn push_image(
1564        &mut self,
1565        clip_rect: PxRect,
1566        image_size: PxSize,
1567        tile_size: PxSize,
1568        tile_spacing: PxSize,
1569        image: &impl Img,
1570        rendering: ImageRendering,
1571    ) {
1572        expect_inner!(self.push_image);
1573        warn_empty!(self.push_image(clip_rect));
1574
1575        if let Some(r) = &self.renderer
1576            && self.visible
1577        {
1578            let image_key = image.renderer_id(r);
1579            self.display_list
1580                .push_image(clip_rect, image_key, image_size, tile_size, tile_spacing, rendering);
1581        }
1582
1583        if self.auto_hit_test {
1584            self.hit_test().push_rect(clip_rect);
1585        }
1586    }
1587
1588    /// Push a color rectangle.
1589    ///
1590    /// The `color` can be bound and updated using [`FrameUpdate::update_color`], note that if the color binding or update
1591    /// is flagged as `animating` webrender frame updates are used when color updates are send, but webrender disables some
1592    /// caching for the entire `clip_rect` region, this can have a big performance impact in [`RenderMode::Software`] if a large
1593    /// part of the screen is affected, as the entire region is redraw every full frame even if the color did not actually change.
1594    ///
1595    /// [`RenderMode::Software`]: zng_view_api::window::RenderMode::Software
1596    pub fn push_color(&mut self, clip_rect: PxRect, color: FrameValue<Rgba>) {
1597        expect_inner!(self.push_color);
1598        warn_empty!(self.push_color(clip_rect));
1599
1600        if self.visible {
1601            self.display_list.push_color(clip_rect, color);
1602        }
1603
1604        if self.auto_hit_test {
1605            self.hit_test().push_rect(clip_rect);
1606        }
1607    }
1608
1609    /// Push a repeating linear gradient rectangle.
1610    ///
1611    /// The gradient fills the `tile_size`, the tile is repeated to fill the `rect`.
1612    /// The `extend_mode` controls how the gradient fills the tile after the last color stop is reached.
1613    ///
1614    /// The gradient `stops` must be normalized, first stop at 0.0 and last stop at 1.0, this
1615    /// is asserted in debug builds.
1616    #[expect(clippy::too_many_arguments)]
1617    pub fn push_linear_gradient(
1618        &mut self,
1619        clip_rect: PxRect,
1620        line: PxLine,
1621        stops: &[RenderGradientStop],
1622        extend_mode: RenderExtendMode,
1623        tile_origin: PxPoint,
1624        tile_size: PxSize,
1625        tile_spacing: PxSize,
1626    ) {
1627        debug_assert!(stops.len() >= 2);
1628        debug_assert!(stops[0].offset.abs() < 0.00001, "first color stop must be at offset 0.0");
1629        debug_assert!(
1630            (stops[stops.len() - 1].offset - 1.0).abs() < 0.00001,
1631            "last color stop must be at offset 1.0"
1632        );
1633
1634        expect_inner!(self.push_linear_gradient);
1635        warn_empty!(self.push_linear_gradient(clip_rect));
1636
1637        if !stops.is_empty() && self.visible {
1638            self.display_list.push_linear_gradient(
1639                clip_rect,
1640                line.start.cast(),
1641                line.end.cast(),
1642                extend_mode,
1643                stops,
1644                tile_origin,
1645                tile_size,
1646                tile_spacing,
1647            );
1648        }
1649
1650        if self.auto_hit_test {
1651            self.hit_test().push_rect(clip_rect);
1652        }
1653    }
1654
1655    /// Push a repeating radial gradient rectangle.
1656    ///
1657    /// The gradient fills the `tile_size`, the tile is repeated to fill the `rect`.
1658    /// The `extend_mode` controls how the gradient fills the tile after the last color stop is reached.
1659    ///
1660    /// The `center` point is relative to the top-left of the tile, the `radius` is the distance between the first
1661    /// and last color stop in both directions and must be a non-zero positive value.
1662    ///
1663    /// The gradient `stops` must be normalized, first stop at 0.0 and last stop at 1.0, this
1664    /// is asserted in debug builds.
1665    #[expect(clippy::too_many_arguments)]
1666    pub fn push_radial_gradient(
1667        &mut self,
1668        clip_rect: PxRect,
1669        center: PxPoint,
1670        radius: PxSize,
1671        stops: &[RenderGradientStop],
1672        extend_mode: RenderExtendMode,
1673        tile_origin: PxPoint,
1674        tile_size: PxSize,
1675        tile_spacing: PxSize,
1676    ) {
1677        debug_assert!(stops.len() >= 2);
1678        debug_assert!(stops[0].offset.abs() < 0.00001, "first color stop must be at offset 0.0");
1679        debug_assert!(
1680            (stops[stops.len() - 1].offset - 1.0).abs() < 0.00001,
1681            "last color stop must be at offset 1.0"
1682        );
1683
1684        expect_inner!(self.push_radial_gradient);
1685        warn_empty!(self.push_radial_gradient(clip_rect));
1686
1687        if !stops.is_empty() && self.visible {
1688            self.display_list.push_radial_gradient(
1689                clip_rect,
1690                center.cast(),
1691                radius.cast(),
1692                0.0,
1693                1.0,
1694                extend_mode,
1695                stops,
1696                tile_origin,
1697                tile_size,
1698                tile_spacing,
1699            );
1700        }
1701
1702        if self.auto_hit_test {
1703            self.hit_test().push_rect(clip_rect);
1704        }
1705    }
1706
1707    /// Push a repeating conic gradient rectangle.
1708    ///
1709    /// The gradient fills the `tile_size`, the tile is repeated to fill the `rect`.
1710    /// The `extend_mode` controls how the gradient fills the tile after the last color stop is reached.
1711    ///
1712    /// The gradient `stops` must be normalized, first stop at 0.0 and last stop at 1.0, this
1713    /// is asserted in debug builds.
1714    #[expect(clippy::too_many_arguments)]
1715    pub fn push_conic_gradient(
1716        &mut self,
1717        clip_rect: PxRect,
1718        center: PxPoint,
1719        angle: AngleRadian,
1720        stops: &[RenderGradientStop],
1721        extend_mode: RenderExtendMode,
1722        tile_origin: PxPoint,
1723        tile_size: PxSize,
1724        tile_spacing: PxSize,
1725    ) {
1726        debug_assert!(stops.len() >= 2);
1727        debug_assert!(stops[0].offset.abs() < 0.00001, "first color stop must be at offset 0.0");
1728        debug_assert!(
1729            (stops[stops.len() - 1].offset - 1.0).abs() < 0.00001,
1730            "last color stop must be at offset 1.0"
1731        );
1732
1733        expect_inner!(self.push_conic_gradient);
1734        warn_empty!(self.push_conic_gradient(clip_rect));
1735
1736        if !stops.is_empty() && self.visible {
1737            self.display_list.push_conic_gradient(
1738                clip_rect,
1739                center.cast(),
1740                angle,
1741                0.0,
1742                1.0,
1743                extend_mode,
1744                stops,
1745                tile_origin,
1746                tile_size,
1747                tile_spacing,
1748            );
1749        }
1750
1751        if self.auto_hit_test {
1752            self.hit_test().push_rect(clip_rect);
1753        }
1754    }
1755
1756    /// Push a styled vertical or horizontal line.
1757    pub fn push_line(&mut self, clip_rect: PxRect, orientation: border::LineOrientation, color: Rgba, style: border::LineStyle) {
1758        expect_inner!(self.push_line);
1759        warn_empty!(self.push_line(clip_rect));
1760
1761        if self.visible {
1762            match style.render_command() {
1763                RenderLineCommand::Line(style) => {
1764                    self.display_list.push_line(clip_rect, color, style, orientation);
1765                }
1766                RenderLineCommand::Border(style) => {
1767                    use border::LineOrientation as LO;
1768                    let widths = match orientation {
1769                        LO::Vertical => PxSideOffsets::new(Px(0), Px(0), Px(0), clip_rect.width()),
1770                        LO::Horizontal => PxSideOffsets::new(clip_rect.height(), Px(0), Px(0), Px(0)),
1771                    };
1772                    self.display_list.push_border(
1773                        clip_rect,
1774                        widths,
1775                        zng_view_api::BorderSide { color, style },
1776                        zng_view_api::BorderSide {
1777                            color: colors::BLACK.transparent(),
1778                            style: zng_view_api::BorderStyle::Hidden,
1779                        },
1780                        zng_view_api::BorderSide {
1781                            color: colors::BLACK.transparent(),
1782                            style: zng_view_api::BorderStyle::Hidden,
1783                        },
1784                        zng_view_api::BorderSide { color, style },
1785                        PxCornerRadius::zero(),
1786                    );
1787                }
1788            }
1789        }
1790
1791        if self.auto_hit_test {
1792            self.hit_test().push_rect(clip_rect);
1793        }
1794    }
1795
1796    /// Record the `offset` in the current context and [`push_debug_dot`] after render.
1797    ///
1798    /// [`push_debug_dot`]: Self::push_debug_dot
1799    pub fn push_debug_dot_overlay(&mut self, offset: PxPoint, color: impl Into<Rgba>) {
1800        if let Some(offset) = self.transform.transform_point(offset) {
1801            self.debug_dot_overlays.push((offset, color.into()));
1802        }
1803    }
1804
1805    /// Push a `color` dot to mark the `offset`.
1806    ///
1807    /// The *dot* is a circle of the `color` highlighted by an white outline and shadow.
1808    pub fn push_debug_dot(&mut self, offset: PxPoint, color: impl Into<Rgba>) {
1809        if !self.visible {
1810            return;
1811        }
1812        let scale = self.scale_factor();
1813
1814        let radius = PxSize::splat(Px(6)) * scale;
1815        let color = color.into();
1816
1817        let center = radius.to_vector().to_point();
1818        let bounds = radius * 2.0.fct();
1819
1820        let offset = offset - radius.to_vector();
1821
1822        self.display_list.push_radial_gradient(
1823            PxRect::new(offset, bounds),
1824            center.cast(),
1825            radius.cast(),
1826            0.0,
1827            1.0,
1828            RenderExtendMode::Clamp,
1829            &[
1830                RenderGradientStop { offset: 0.0, color },
1831                RenderGradientStop { offset: 0.5, color },
1832                RenderGradientStop {
1833                    offset: 0.6,
1834                    color: colors::WHITE,
1835                },
1836                RenderGradientStop {
1837                    offset: 0.7,
1838                    color: colors::WHITE,
1839                },
1840                RenderGradientStop {
1841                    offset: 0.8,
1842                    color: colors::BLACK,
1843                },
1844                RenderGradientStop {
1845                    offset: 1.0,
1846                    color: colors::BLACK.transparent(),
1847                },
1848            ],
1849            PxPoint::zero(),
1850            bounds,
1851            PxSize::zero(),
1852        );
1853    }
1854
1855    /// Push a custom display extension context with custom encoding.
1856    pub fn push_extension_context_raw(
1857        &mut self,
1858        extension_id: ApiExtensionId,
1859        payload: ApiExtensionPayload,
1860        render: impl FnOnce(&mut Self),
1861    ) {
1862        self.display_list.push_extension(extension_id, payload);
1863        render(self);
1864        self.display_list.pop_extension(extension_id);
1865    }
1866
1867    /// Push a custom display extension context that wraps `render`.
1868    pub fn push_extension_context<T: serde::Serialize>(
1869        &mut self,
1870        extension_id: ApiExtensionId,
1871        payload: &T,
1872        render: impl FnOnce(&mut Self),
1873    ) {
1874        self.push_extension_context_raw(extension_id, ApiExtensionPayload::serialize(payload).unwrap(), render)
1875    }
1876
1877    /// Push a custom display extension item with custom encoding.
1878    pub fn push_extension_item_raw(&mut self, extension_id: ApiExtensionId, payload: ApiExtensionPayload) {
1879        self.display_list.push_extension(extension_id, payload);
1880    }
1881
1882    /// Push a custom display extension item.
1883    pub fn push_extension_item<T: serde::Serialize>(&mut self, extension_id: ApiExtensionId, payload: &T) {
1884        self.push_extension_item_raw(extension_id, ApiExtensionPayload::serialize(payload).unwrap())
1885    }
1886
1887    /// Create a new display list builder that can be built in parallel and merged back onto this one using [`parallel_fold`].
1888    ///
1889    /// Note that split list must be folded before any current open reference frames, stacking contexts or clips are closed in this list.
1890    ///
1891    /// Note that calling this inside [`is_outer`] is an error, the current widget must be *finished* first.
1892    ///
1893    /// [`parallel_fold`]: Self::parallel_fold
1894    /// [`is_outer`]: Self::is_outer
1895    pub fn parallel_split(&self) -> ParallelBuilder<Self> {
1896        if self.widget_data.is_some() {
1897            tracing::error!(
1898                "called `parallel_split` inside `{}` and before calling `push_inner`",
1899                self.widget_id
1900            );
1901        }
1902
1903        ParallelBuilder(Some(Self {
1904            render_widgets: self.render_widgets.clone(),
1905            render_update_widgets: self.render_update_widgets.clone(),
1906            frame_id: self.frame_id,
1907            widget_id: self.widget_id,
1908            transform: self.transform,
1909            transform_style: self.transform_style,
1910            default_font_aa: self.default_font_aa,
1911            renderer: self.renderer.clone(),
1912            scale_factor: self.scale_factor,
1913            display_list: self.display_list.parallel_split(),
1914            hit_testable: self.hit_testable,
1915            visible: self.visible,
1916            backface_visible: self.backface_visible,
1917            auto_hit_test: self.auto_hit_test,
1918            hit_clips: self.hit_clips.parallel_split(),
1919            auto_hide_rect: self.auto_hide_rect,
1920            widget_data: None,
1921            child_offset: self.child_offset,
1922            parent_inner_bounds: self.parent_inner_bounds,
1923            perspective: self.perspective,
1924            view_process_has_frame: self.view_process_has_frame,
1925            can_reuse: self.can_reuse,
1926            open_reuse: None,
1927            clear_color: None,
1928            widget_count: 0,
1929            widget_count_offsets: self.widget_count_offsets.parallel_split(),
1930            debug_dot_overlays: vec![],
1931        }))
1932    }
1933
1934    /// Collect display list from `split` into `self`.
1935    pub fn parallel_fold(&mut self, mut split: ParallelBuilder<Self>) {
1936        let split = split.take();
1937        if split.clear_color.is_some() {
1938            self.clear_color = split.clear_color;
1939        }
1940        self.hit_clips.parallel_fold(split.hit_clips);
1941        self.display_list.parallel_fold(split.display_list);
1942        self.widget_count_offsets
1943            .parallel_fold(split.widget_count_offsets, self.widget_count);
1944
1945        self.widget_count += split.widget_count;
1946        self.debug_dot_overlays.extend(split.debug_dot_overlays);
1947    }
1948
1949    /// Calls `render` to render a separate nested window on this frame.
1950    #[expect(clippy::too_many_arguments)]
1951    pub fn with_nested_window(
1952        &mut self,
1953        render_widgets: Arc<RenderUpdates>,
1954        render_update_widgets: Arc<RenderUpdates>,
1955
1956        root_id: WidgetId,
1957        root_bounds: &WidgetBoundsInfo,
1958        info_tree: &WidgetInfoTree,
1959        default_font_aa: FontAntiAliasing,
1960
1961        render: impl FnOnce(&mut Self),
1962    ) {
1963        // similar to parallel_split, but without parent context
1964        let mut nested = Self::new(
1965            render_widgets,
1966            render_update_widgets,
1967            self.frame_id,
1968            root_id,
1969            root_bounds,
1970            info_tree,
1971            self.renderer.clone(),
1972            self.scale_factor,
1973            default_font_aa,
1974        );
1975        nested.display_list = self.display_list.parallel_split();
1976        nested.hit_clips = self.hit_clips.parallel_split();
1977        // not this, different info tree for the nested window
1978        // nested.widget_count_offsets = self.widget_count_offsets.parallel_split();
1979
1980        render(&mut nested);
1981
1982        // finalize nested window
1983        info_tree.root().bounds_info().set_rendered(
1984            Some(WidgetRenderInfo {
1985                visible: nested.visible,
1986                parent_perspective: nested.perspective,
1987                seg_id: 0,
1988                back: 0,
1989                front: nested.widget_count,
1990            }),
1991            info_tree,
1992        );
1993        info_tree.after_render(
1994            nested.frame_id,
1995            nested.scale_factor,
1996            Some(
1997                nested
1998                    .renderer
1999                    .as_ref()
2000                    .and_then(|r| r.generation().ok())
2001                    .unwrap_or(ViewProcessGen::INVALID),
2002            ),
2003            Some(nested.widget_count_offsets.clone()),
2004        );
2005
2006        // fold nested window into host window
2007        self.hit_clips.parallel_fold(nested.hit_clips);
2008        self.display_list.parallel_fold(nested.display_list);
2009
2010        // self.widget_count_offsets
2011        //     .parallel_fold(nested.widget_count_offsets, self.widget_count);
2012
2013        self.widget_count += nested.widget_count;
2014        self.debug_dot_overlays.extend(nested.debug_dot_overlays);
2015    }
2016
2017    /// External render requests for this frame.
2018    pub fn render_widgets(&self) -> &Arc<RenderUpdates> {
2019        &self.render_widgets
2020    }
2021
2022    /// External render update requests for this frame.
2023    pub fn render_update_widgets(&self) -> &Arc<RenderUpdates> {
2024        &self.render_update_widgets
2025    }
2026
2027    /// Finalizes the build.
2028    pub fn finalize(self, info_tree: &WidgetInfoTree) -> BuiltFrame {
2029        info_tree.root().bounds_info().set_rendered(
2030            Some(WidgetRenderInfo {
2031                visible: self.visible,
2032                parent_perspective: self.perspective,
2033                seg_id: 0,
2034                back: 0,
2035                front: self.widget_count,
2036            }),
2037            info_tree,
2038        );
2039
2040        info_tree.after_render(
2041            self.frame_id,
2042            self.scale_factor,
2043            Some(
2044                self.renderer
2045                    .as_ref()
2046                    .and_then(|r| r.generation().ok())
2047                    .unwrap_or(ViewProcessGen::INVALID),
2048            ),
2049            Some(self.widget_count_offsets),
2050        );
2051
2052        let display_list = self.display_list.finalize();
2053
2054        let clear_color = self.clear_color.unwrap_or_default();
2055
2056        BuiltFrame { display_list, clear_color }
2057    }
2058}
2059
2060/// Builder for a chain of render and hit-test clips.
2061///
2062/// The builder is available in [`FrameBuilder::push_clips`].
2063pub struct ClipBuilder<'a> {
2064    builder: &'a mut FrameBuilder,
2065    render_count: usize,
2066    hit_test_count: usize,
2067}
2068impl ClipBuilder<'_> {
2069    /// Pushes the `clip_rect`.
2070    ///
2071    /// If `clip_out` is `true` only pixels outside the rect are visible. If `hit_test` is `true` the hit-test shapes
2072    /// rendered inside `render` are also clipped.
2073    ///
2074    /// Note that hit-test will be generated if `hit_test` or [`auto_hit_test`] is `true`.
2075    ///
2076    /// [`auto_hit_test`]: FrameBuilder::auto_hit_test
2077    pub fn push_clip_rect(&mut self, clip_rect: PxRect, clip_out: bool, hit_test: bool) {
2078        if self.builder.visible {
2079            self.builder.display_list.push_clip_rect(clip_rect, clip_out);
2080            self.render_count += 1;
2081        }
2082
2083        if hit_test || self.builder.auto_hit_test {
2084            self.builder.hit_clips.push_clip_rect(clip_rect.to_box2d(), clip_out);
2085            self.hit_test_count += 1;
2086        }
2087    }
2088
2089    /// Push the `clip_rect` with rounded `corners`.
2090    ///
2091    /// If `clip_out` is `true` only pixels outside the rounded rect are visible. If `hit_test` is `true` the hit-test shapes
2092    /// rendered inside `render` are also clipped.
2093    ///
2094    /// Note that hit-test will be generated if `hit_test` or [`auto_hit_test`] is `true`.
2095    ///
2096    /// [`auto_hit_test`]: FrameBuilder::auto_hit_test
2097    pub fn push_clip_rounded_rect(&mut self, clip_rect: PxRect, corners: PxCornerRadius, clip_out: bool, hit_test: bool) {
2098        if self.builder.visible {
2099            self.builder.display_list.push_clip_rounded_rect(clip_rect, corners, clip_out);
2100            self.render_count += 1;
2101        }
2102
2103        if hit_test || self.builder.auto_hit_test {
2104            self.builder
2105                .hit_clips
2106                .push_clip_rounded_rect(clip_rect.to_box2d(), corners, clip_out);
2107            self.hit_test_count += 1;
2108        }
2109    }
2110}
2111
2112/// Builder for a chain of hit-test clips.
2113///
2114/// The build is available in [`HitTestBuilder::push_clips`].
2115pub struct HitTestClipBuilder<'a> {
2116    hit_clips: &'a mut HitTestClips,
2117    count: usize,
2118}
2119impl HitTestClipBuilder<'_> {
2120    /// Push a clip `rect`.
2121    ///
2122    /// If `clip_out` is `true` only hits outside the rect are valid.
2123    pub fn push_clip_rect(&mut self, rect: PxRect, clip_out: bool) {
2124        self.hit_clips.push_clip_rect(rect.to_box2d(), clip_out);
2125        self.count += 1;
2126    }
2127
2128    /// Push a clip `rect` with rounded `corners`.
2129    ///
2130    /// If `clip_out` is `true` only hits outside the rect are valid.
2131    pub fn push_clip_rounded_rect(&mut self, rect: PxRect, corners: PxCornerRadius, clip_out: bool) {
2132        self.hit_clips.push_clip_rounded_rect(rect.to_box2d(), corners, clip_out);
2133        self.count += 1;
2134    }
2135
2136    /// Push a clip ellipse.
2137    ///
2138    /// If `clip_out` is `true` only hits outside the ellipses are valid.
2139    pub fn push_clip_ellipse(&mut self, center: PxPoint, radii: PxSize, clip_out: bool) {
2140        self.hit_clips.push_clip_ellipse(center, radii, clip_out);
2141        self.count += 1;
2142    }
2143}
2144
2145/// Builder for the hit-testable shape of the inner-bounds of a widget.
2146///
2147/// This builder is available in [`FrameBuilder::hit_test`] inside the inner-bounds of the rendering widget.
2148pub struct HitTestBuilder<'a> {
2149    hit_clips: &'a mut HitTestClips,
2150    is_hit_testable: bool,
2151}
2152impl HitTestBuilder<'_> {
2153    /// If the widget is hit-testable, if this is `false` all hit-test push methods are ignored.
2154    pub fn is_hit_testable(&self) -> bool {
2155        self.is_hit_testable
2156    }
2157
2158    /// Push a hit-test `rect`.
2159    pub fn push_rect(&mut self, rect: PxRect) {
2160        if self.is_hit_testable && rect.size != PxSize::zero() {
2161            self.hit_clips.push_rect(rect.to_box2d());
2162        }
2163    }
2164
2165    /// Push a hit-test `rect` with rounded `corners`.
2166    pub fn push_rounded_rect(&mut self, rect: PxRect, corners: PxCornerRadius) {
2167        if self.is_hit_testable && rect.size != PxSize::zero() {
2168            self.hit_clips.push_rounded_rect(rect.to_box2d(), corners);
2169        }
2170    }
2171
2172    /// Push a hit-test ellipse.
2173    pub fn push_ellipse(&mut self, center: PxPoint, radii: PxSize) {
2174        if self.is_hit_testable && radii != PxSize::zero() {
2175            self.hit_clips.push_ellipse(center, radii);
2176        }
2177    }
2178
2179    /// Push a clip `rect` that affects the `inner_hit_test`.
2180    pub fn push_clip_rect(&mut self, rect: PxRect, clip_out: bool, inner_hit_test: impl FnOnce(&mut Self)) {
2181        if !self.is_hit_testable {
2182            return;
2183        }
2184
2185        self.hit_clips.push_clip_rect(rect.to_box2d(), clip_out);
2186
2187        inner_hit_test(self);
2188
2189        self.hit_clips.pop_clip();
2190    }
2191
2192    /// Push a clip `rect` with rounded `corners` that affects the `inner_hit_test`.
2193    pub fn push_clip_rounded_rect(
2194        &mut self,
2195        rect: PxRect,
2196        corners: PxCornerRadius,
2197        clip_out: bool,
2198        inner_hit_test: impl FnOnce(&mut Self),
2199    ) {
2200        self.push_clips(move |c| c.push_clip_rounded_rect(rect, corners, clip_out), inner_hit_test);
2201    }
2202
2203    /// Push a clip ellipse that affects the `inner_hit_test`.
2204    pub fn push_clip_ellipse(&mut self, center: PxPoint, radii: PxSize, clip_out: bool, inner_hit_test: impl FnOnce(&mut Self)) {
2205        self.push_clips(move |c| c.push_clip_ellipse(center, radii, clip_out), inner_hit_test);
2206    }
2207
2208    /// Push clips that affect the `inner_hit_test`.
2209    pub fn push_clips(&mut self, clips: impl FnOnce(&mut HitTestClipBuilder), inner_hit_test: impl FnOnce(&mut Self)) {
2210        if !self.is_hit_testable {
2211            return;
2212        }
2213
2214        let mut count = {
2215            let mut builder = HitTestClipBuilder {
2216                hit_clips: &mut *self.hit_clips,
2217                count: 0,
2218            };
2219            clips(&mut builder);
2220            builder.count
2221        };
2222
2223        inner_hit_test(self);
2224
2225        while count > 0 {
2226            count -= 1;
2227            self.hit_clips.pop_clip();
2228        }
2229    }
2230
2231    /// Pushes a transform that affects the `inner_hit_test`.
2232    pub fn push_transform(&mut self, transform: PxTransform, inner_hit_test: impl FnOnce(&mut Self)) {
2233        if !self.is_hit_testable {
2234            return;
2235        }
2236
2237        self.hit_clips.push_transform(FrameValue::Value(transform));
2238
2239        inner_hit_test(self);
2240
2241        self.hit_clips.pop_transform();
2242    }
2243
2244    /// Pushes a composite hit-test that defines a border.
2245    pub fn push_border(&mut self, bounds: PxRect, widths: PxSideOffsets, corners: PxCornerRadius) {
2246        if !self.is_hit_testable {
2247            return;
2248        }
2249
2250        let bounds = bounds.to_box2d();
2251        let mut inner_bounds = bounds;
2252        inner_bounds.min.x += widths.left;
2253        inner_bounds.min.y += widths.top;
2254        inner_bounds.max.x -= widths.right;
2255        inner_bounds.max.y -= widths.bottom;
2256
2257        if inner_bounds.is_negative() {
2258            self.hit_clips.push_rounded_rect(bounds, corners);
2259        } else if corners == PxCornerRadius::zero() {
2260            self.hit_clips.push_clip_rect(inner_bounds, true);
2261            self.hit_clips.push_rect(bounds);
2262            self.hit_clips.pop_clip();
2263        } else {
2264            let inner_radii = corners.deflate(widths);
2265
2266            self.hit_clips.push_clip_rounded_rect(inner_bounds, inner_radii, true);
2267            self.hit_clips.push_rounded_rect(bounds, corners);
2268            self.hit_clips.pop_clip();
2269        }
2270    }
2271}
2272
2273/// Output of a [`FrameBuilder`].
2274#[non_exhaustive]
2275pub struct BuiltFrame {
2276    /// Built display list.
2277    pub display_list: DisplayList,
2278    /// Clear color selected for the frame.
2279    pub clear_color: Rgba,
2280}
2281
2282enum RenderLineCommand {
2283    Line(zng_view_api::LineStyle),
2284    Border(zng_view_api::BorderStyle),
2285}
2286impl border::LineStyle {
2287    fn render_command(self) -> RenderLineCommand {
2288        use RenderLineCommand::*;
2289        use border::LineStyle as LS;
2290        match self {
2291            LS::Solid => Line(zng_view_api::LineStyle::Solid),
2292            LS::Double => Border(zng_view_api::BorderStyle::Double),
2293            LS::Dotted => Line(zng_view_api::LineStyle::Dotted),
2294            LS::Dashed => Line(zng_view_api::LineStyle::Dashed),
2295            LS::Groove => Border(zng_view_api::BorderStyle::Groove),
2296            LS::Ridge => Border(zng_view_api::BorderStyle::Ridge),
2297            LS::Wavy(thickness) => Line(zng_view_api::LineStyle::Wavy(thickness)),
2298            LS::Hidden => Border(zng_view_api::BorderStyle::Hidden),
2299        }
2300    }
2301}
2302
2303/// A frame quick update.
2304///
2305/// A frame update causes a frame render without needing to fully rebuild the display list. It
2306/// is a more performant but also more limited way of generating a frame.
2307///
2308/// Any [`FrameValueKey`] used in the creation of the frame can be used for updating the frame.
2309pub struct FrameUpdate {
2310    render_update_widgets: Arc<RenderUpdates>,
2311
2312    transforms: Vec<FrameValueUpdate<PxTransform>>,
2313    floats: Vec<FrameValueUpdate<f32>>,
2314    colors: Vec<FrameValueUpdate<Rgba>>,
2315
2316    extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
2317
2318    current_clear_color: Rgba,
2319    clear_color: Option<Rgba>,
2320    frame_id: FrameId,
2321
2322    widget_id: WidgetId,
2323    transform: PxTransform,
2324    parent_child_offset: PxVector,
2325    perspective: Option<(f32, PxPoint)>,
2326    inner_transform: Option<PxTransform>,
2327    child_offset: PxVector,
2328    can_reuse_widget: bool,
2329    widget_bounds: WidgetBoundsInfo,
2330    parent_inner_bounds: Option<PxRect>,
2331
2332    auto_hit_test: bool,
2333    visible: bool,
2334}
2335impl FrameUpdate {
2336    /// New frame update builder.
2337    ///
2338    /// * `render_update_widgets` - External update requests.
2339    /// * `frame_id` - Id of the new frame.
2340    /// * `root_id` - Id of the window root widget.
2341    /// * `root_bounds` - Bounds info of the window root widget.
2342    /// * `clear_color` - The current clear color.
2343    pub fn new(
2344        render_update_widgets: Arc<RenderUpdates>,
2345        frame_id: FrameId,
2346        root_id: WidgetId,
2347        root_bounds: WidgetBoundsInfo,
2348        clear_color: Rgba,
2349    ) -> Self {
2350        FrameUpdate {
2351            render_update_widgets,
2352            widget_id: root_id,
2353            widget_bounds: root_bounds,
2354            transforms: vec![],
2355            floats: vec![],
2356            colors: vec![],
2357            extensions: vec![],
2358            clear_color: None,
2359            frame_id,
2360            current_clear_color: clear_color,
2361
2362            transform: PxTransform::identity(),
2363            perspective: None,
2364            parent_child_offset: PxVector::zero(),
2365            inner_transform: Some(PxTransform::identity()),
2366            child_offset: PxVector::zero(),
2367            can_reuse_widget: true,
2368
2369            auto_hit_test: false,
2370            parent_inner_bounds: None,
2371            visible: true,
2372        }
2373    }
2374
2375    /// Id of the new frame.
2376    pub fn frame_id(&self) -> FrameId {
2377        self.frame_id
2378    }
2379
2380    /// Returns `true` if the widget inner transform update is still being build.
2381    ///
2382    /// This is `true` when inside an [`update_widget`] call but `false` when inside an [`update_inner`] call.
2383    ///
2384    /// [`update_widget`]: Self::update_widget
2385    /// [`update_inner`]: Self::update_inner
2386    pub fn is_outer(&self) -> bool {
2387        self.inner_transform.is_some()
2388    }
2389
2390    /// Current transform.
2391    pub fn transform(&self) -> &PxTransform {
2392        &self.transform
2393    }
2394
2395    /// Change the color used to clear the pixel buffer when redrawing the frame.
2396    pub fn set_clear_color(&mut self, color: Rgba) {
2397        if self.visible {
2398            self.clear_color = Some(color);
2399        }
2400    }
2401
2402    /// Returns `true` if all transform updates are also applied to hit-test transforms.
2403    pub fn auto_hit_test(&self) -> bool {
2404        self.auto_hit_test
2405    }
2406    /// Runs `render_update` with [`auto_hit_test`] set to a value for the duration of the `render` call.
2407    ///
2408    /// [`auto_hit_test`]: Self::auto_hit_test
2409    pub fn with_auto_hit_test(&mut self, auto_hit_test: bool, render_update: impl FnOnce(&mut Self)) {
2410        let prev = mem::replace(&mut self.auto_hit_test, auto_hit_test);
2411        render_update(self);
2412        self.auto_hit_test = prev;
2413    }
2414
2415    /// Returns `true` if view updates are actually collected, if `false` only transforms and hit-test are updated.
2416    pub fn is_visible(&self) -> bool {
2417        self.visible
2418    }
2419
2420    /// Calls `update` with [`is_visible`] set to `false`.
2421    ///
2422    /// Nodes that set the visibility to [`Hidden`] must render using the [`FrameBuilder::hide`] method and update using this method.
2423    ///
2424    /// [`is_visible`]: Self::is_visible
2425    /// [`Hidden`]: crate::widget::info::Visibility::Hidden
2426    pub fn hidden(&mut self, update: impl FnOnce(&mut Self)) {
2427        let parent_visible = mem::replace(&mut self.visible, false);
2428        update(self);
2429        self.visible = parent_visible;
2430    }
2431
2432    /// Update a transform value that does not potentially affect widget bounds.
2433    ///
2434    /// Use [`with_transform`] to update transforms that affect widget bounds.
2435    ///
2436    /// If `hit_test` is `true` the hit-test transform is also updated.
2437    ///
2438    /// [`with_transform`]: Self::with_transform
2439    pub fn update_transform(&mut self, new_value: FrameValueUpdate<PxTransform>, hit_test: bool) {
2440        if self.visible {
2441            self.transforms.push(new_value);
2442        }
2443
2444        if hit_test || self.auto_hit_test {
2445            self.widget_bounds.update_hit_test_transform(new_value);
2446        }
2447    }
2448
2449    /// Update a transform value, if there is one.
2450    pub fn update_transform_opt(&mut self, new_value: Option<FrameValueUpdate<PxTransform>>, hit_test: bool) {
2451        if let Some(value) = new_value {
2452            self.update_transform(value, hit_test)
2453        }
2454    }
2455
2456    /// Update a transform that potentially affects widget bounds.
2457    ///
2458    /// The [`transform`] is updated to include this space for the call to the `render_update` closure. The closure
2459    /// must call render update on child nodes.
2460    ///
2461    /// If `hit_test` is `true` the hit-test transform is also updated.
2462    ///
2463    /// [`transform`]: Self::transform
2464    pub fn with_transform(&mut self, new_value: FrameValueUpdate<PxTransform>, hit_test: bool, render_update: impl FnOnce(&mut Self)) {
2465        self.with_transform_value(&new_value.value, render_update);
2466        self.update_transform(new_value, hit_test);
2467    }
2468
2469    /// Update a transform that potentially affects widget bounds, if there is one.
2470    ///
2471    /// The `render_update` is always called.
2472    pub fn with_transform_opt(
2473        &mut self,
2474        new_value: Option<FrameValueUpdate<PxTransform>>,
2475        hit_test: bool,
2476        render_update: impl FnOnce(&mut Self),
2477    ) {
2478        match new_value {
2479            Some(value) => self.with_transform(value, hit_test, render_update),
2480            None => render_update(self),
2481        }
2482    }
2483
2484    /// Calls `render_update` with an `offset` that affects the first inner child inner bounds.
2485    ///
2486    /// Nodes that used [`FrameBuilder::push_child`] during render must use this method to update the value.
2487    pub fn with_child(&mut self, offset: PxVector, render_update: impl FnOnce(&mut Self)) {
2488        self.child_offset = offset;
2489        render_update(self);
2490        self.child_offset = PxVector::zero();
2491    }
2492
2493    /// Calls `render_update` while the [`transform`] is updated to include the `value` space.
2494    ///
2495    /// This is useful for cases where the inner transforms are affected by a `value` that is only rendered, never updated.
2496    ///
2497    /// [`transform`]: Self::transform
2498    pub fn with_transform_value(&mut self, value: &PxTransform, render_update: impl FnOnce(&mut Self)) {
2499        let parent_transform = self.transform;
2500        self.transform = value.then(&parent_transform);
2501
2502        render_update(self);
2503        self.transform = parent_transform;
2504    }
2505
2506    /// Update the transform applied after the inner bounds translate.
2507    ///
2508    /// This is only valid if [`is_outer`].
2509    ///
2510    /// [`is_outer`]: Self::is_outer
2511    pub fn with_inner_transform(&mut self, transform: &PxTransform, render_update: impl FnOnce(&mut Self)) {
2512        if let Some(inner_transform) = &mut self.inner_transform {
2513            let parent = *inner_transform;
2514            *inner_transform = inner_transform.then(transform);
2515
2516            render_update(self);
2517
2518            if let Some(inner_transform) = &mut self.inner_transform {
2519                *inner_transform = parent;
2520            }
2521        } else {
2522            tracing::error!("called `with_inner_transform` inside inner context of `{}`", self.widget_id);
2523            render_update(self);
2524        }
2525    }
2526
2527    /// If widget update can be *skipped* by setting reuse in [`update_widget`].
2528    ///
2529    /// [`update_widget`]: Self::update_widget
2530    pub fn can_reuse_widget(&self) -> bool {
2531        self.can_reuse_widget
2532    }
2533
2534    /// Calls `render_update` with [`can_reuse_widget`] set to `false`.
2535    ///
2536    /// [`can_reuse_widget`]: Self::can_reuse_widget
2537    pub fn with_no_reuse(&mut self, render_update: impl FnOnce(&mut Self)) {
2538        let prev_can_reuse = self.can_reuse_widget;
2539        self.can_reuse_widget = false;
2540        render_update(self);
2541        self.can_reuse_widget = prev_can_reuse;
2542    }
2543
2544    /// Update the widget's outer transform.
2545    ///
2546    /// If render-update was not requested for the widget and [`can_reuse_widget`] only update outer/inner transforms of descendants.
2547    /// If the widget is reused the `render_update` is not called.
2548    ///
2549    /// [`can_reuse_widget`]: Self::can_reuse_widget
2550    pub fn update_widget(&mut self, render_update: impl FnOnce(&mut Self)) {
2551        let wgt_info = WIDGET.info();
2552        let id = wgt_info.id();
2553
2554        #[cfg(debug_assertions)]
2555        if self.inner_transform.is_some() && wgt_info.parent().is_some() {
2556            tracing::error!(
2557                "called `update_widget` for `{}` without calling `update_inner` for the parent `{}`",
2558                WIDGET.trace_id(),
2559                self.widget_id
2560            );
2561        }
2562
2563        let bounds = wgt_info.bounds_info();
2564        if bounds.is_collapsed() {
2565            let _ = WIDGET.take_update(UpdateFlags::LAYOUT | UpdateFlags::RENDER | UpdateFlags::RENDER_UPDATE);
2566            return;
2567        } else {
2568            #[cfg(debug_assertions)]
2569            if WIDGET.pending_update().contains(UpdateFlags::LAYOUT) {
2570                tracing::error!("called `update_widget` for `{}` with pending layout", WIDGET.trace_id());
2571            }
2572        }
2573
2574        let tree = wgt_info.tree();
2575
2576        let parent_can_reuse = self.can_reuse_widget;
2577        let parent_perspective = mem::replace(&mut self.perspective, wgt_info.perspective());
2578        let parent_bounds = mem::replace(&mut self.widget_bounds, bounds.clone());
2579
2580        if let Some((_, o)) = &mut self.perspective {
2581            *o -= self.child_offset;
2582        }
2583
2584        let render_info = bounds.render_info();
2585        if let Some(i) = &render_info
2586            && i.parent_perspective != self.perspective
2587        {
2588            self.can_reuse_widget = false;
2589        }
2590
2591        let outer_transform = PxTransform::from(self.child_offset).then(&self.transform);
2592
2593        if !WIDGET.take_update(UpdateFlags::RENDER_UPDATE)
2594            && self.can_reuse_widget
2595            && !self.render_update_widgets.delivery_list().enter_widget(id)
2596            && bounds.parent_child_offset() == self.child_offset
2597        {
2598            let _span = tracing::trace_span!("reuse-descendants", id=?self.widget_id).entered();
2599
2600            let prev_outer = bounds.outer_transform();
2601            if prev_outer != outer_transform {
2602                if let Some(undo_prev) = prev_outer.inverse() {
2603                    let patch = undo_prev.then(&outer_transform);
2604
2605                    let update = |info: WidgetInfo| {
2606                        let bounds = info.bounds_info();
2607                        bounds.set_outer_transform(bounds.outer_transform().then(&patch), tree);
2608                        bounds.set_inner_transform(
2609                            bounds.inner_transform().then(&patch),
2610                            tree,
2611                            info.id(),
2612                            info.parent().map(|p| p.inner_bounds()),
2613                        );
2614                    };
2615                    let targets = tree.get(id).unwrap().self_and_descendants();
2616                    if PARALLEL_VAR.get().contains(Parallel::RENDER) {
2617                        targets.par_bridge().for_each(update);
2618                    } else {
2619                        targets.for_each(update);
2620                    }
2621
2622                    return; // can reuse and patched.
2623                }
2624            } else {
2625                return; // can reuse and no change.
2626            }
2627
2628            // actually cannot reuse because cannot undo prev-transform.
2629            self.can_reuse_widget = false;
2630        }
2631
2632        bounds.set_parent_child_offset(self.child_offset);
2633        bounds.set_outer_transform(outer_transform, tree);
2634        self.parent_child_offset = mem::take(&mut self.child_offset);
2635        self.inner_transform = Some(PxTransform::identity());
2636        let parent_id = self.widget_id;
2637        self.widget_id = id;
2638
2639        render_update(self);
2640
2641        if let Some(mut i) = render_info {
2642            i.parent_perspective = self.perspective;
2643            bounds.set_rendered(Some(i), tree);
2644        }
2645        self.parent_child_offset = PxVector::zero();
2646        self.inner_transform = None;
2647        self.widget_id = parent_id;
2648        self.can_reuse_widget = parent_can_reuse;
2649        self.perspective = parent_perspective;
2650        self.widget_bounds = parent_bounds;
2651    }
2652
2653    /// Update the info transforms of the widget and descendants.
2654    ///
2655    /// Widgets that did not request render-update can use this method to update only the outer and inner transforms
2656    /// of itself and descendants as those values are global and the parent widget may have changed.
2657    pub fn reuse_widget(&mut self) {
2658        if self.inner_transform.is_some() {
2659            tracing::error!(
2660                "called `reuse_widget` for `{}` without calling `update_inner` for the parent `{}`",
2661                WIDGET.trace_id(),
2662                self.widget_id
2663            );
2664        }
2665    }
2666
2667    /// Update the widget's inner transform.
2668    ///
2669    /// The `layout_translation_animating` affects some webrender caches, see [`FrameBuilder::push_inner`] for details.
2670    pub fn update_inner(
2671        &mut self,
2672        layout_translation_key: FrameValueKey<PxTransform>,
2673        layout_translation_animating: bool,
2674        render_update: impl FnOnce(&mut Self),
2675    ) {
2676        let id = WIDGET.id();
2677        if let Some(mut inner_transform) = self.inner_transform.take() {
2678            let bounds = WIDGET.bounds();
2679            let tree = WINDOW.info();
2680
2681            let inner_offset = bounds.inner_offset();
2682            if let Some((p, mut o)) = self.perspective {
2683                o -= inner_offset;
2684                let x = o.x.0 as f32;
2685                let y = o.y.0 as f32;
2686                let p = PxTransform::translation(-x, -y)
2687                    .then(&PxTransform::perspective(p))
2688                    .then_translate(euclid::vec2(x, y));
2689                inner_transform = inner_transform.then(&p);
2690            }
2691            let inner_transform = inner_transform.then_translate((self.parent_child_offset + inner_offset).cast());
2692            self.update_transform(layout_translation_key.update(inner_transform, layout_translation_animating), false);
2693            let parent_transform = self.transform;
2694
2695            self.transform = inner_transform.then(&parent_transform);
2696
2697            bounds.set_inner_transform(self.transform, &tree, id, self.parent_inner_bounds);
2698            let parent_inner_bounds = self.parent_inner_bounds.replace(bounds.inner_bounds());
2699
2700            render_update(self);
2701
2702            self.transform = parent_transform;
2703            self.parent_inner_bounds = parent_inner_bounds;
2704        } else {
2705            tracing::error!("called `update_inner` more then once for `{}`", id);
2706            render_update(self)
2707        }
2708    }
2709
2710    /// Update a float value.
2711    pub fn update_f32(&mut self, new_value: FrameValueUpdate<f32>) {
2712        if self.visible {
2713            self.floats.push(new_value);
2714        }
2715    }
2716
2717    /// Update a float value, if there is one.
2718    pub fn update_f32_opt(&mut self, new_value: Option<FrameValueUpdate<f32>>) {
2719        if let Some(value) = new_value {
2720            self.update_f32(value)
2721        }
2722    }
2723
2724    /// Update a color value.
2725    ///
2726    /// See [`FrameBuilder::push_color`] for details.
2727    pub fn update_color(&mut self, new_value: FrameValueUpdate<Rgba>) {
2728        if self.visible {
2729            self.colors.push(new_value)
2730        }
2731    }
2732
2733    /// Update a color value, if there is one.
2734    pub fn update_color_opt(&mut self, new_value: Option<FrameValueUpdate<Rgba>>) {
2735        if let Some(value) = new_value {
2736            self.update_color(value)
2737        }
2738    }
2739
2740    /// Update a custom extension value with custom encoding.
2741    pub fn update_extension_raw(&mut self, extension_id: ApiExtensionId, extension_payload: ApiExtensionPayload) {
2742        self.extensions.push((extension_id, extension_payload))
2743    }
2744
2745    /// Update a custom extension value.
2746    pub fn update_extension<T: serde::Serialize>(&mut self, extension_id: ApiExtensionId, payload: &T) {
2747        self.update_extension_raw(extension_id, ApiExtensionPayload::serialize(payload).unwrap())
2748    }
2749
2750    /// Create an update builder that can be send to a parallel task and must be folded back into this builder.
2751    ///
2752    /// This should be called just before the call to [`update_widget`], an error is traced if called inside a widget outer bounds.
2753    ///
2754    /// [`update_widget`]: Self::update_widget
2755    pub fn parallel_split(&self) -> ParallelBuilder<Self> {
2756        if self.inner_transform.is_some() && WIDGET.parent_id().is_some() {
2757            tracing::error!(
2758                "called `parallel_split` inside `{}` and before calling `update_inner`",
2759                self.widget_id
2760            );
2761        }
2762
2763        ParallelBuilder(Some(Self {
2764            render_update_widgets: self.render_update_widgets.clone(),
2765            current_clear_color: self.current_clear_color,
2766            frame_id: self.frame_id,
2767
2768            transforms: vec![],
2769            floats: vec![],
2770            colors: vec![],
2771            extensions: vec![],
2772            clear_color: None,
2773
2774            widget_id: self.widget_id,
2775            transform: self.transform,
2776            perspective: self.perspective,
2777            parent_child_offset: self.parent_child_offset,
2778            inner_transform: self.inner_transform,
2779            child_offset: self.child_offset,
2780            can_reuse_widget: self.can_reuse_widget,
2781            widget_bounds: self.widget_bounds.clone(),
2782            parent_inner_bounds: self.parent_inner_bounds,
2783            auto_hit_test: self.auto_hit_test,
2784            visible: self.visible,
2785        }))
2786    }
2787
2788    /// Collect updates from `split` into `self`.
2789    pub fn parallel_fold(&mut self, mut split: ParallelBuilder<Self>) {
2790        let mut split = split.take();
2791
2792        debug_assert_eq!(self.frame_id, split.frame_id);
2793        debug_assert_eq!(self.widget_id, split.widget_id);
2794
2795        fn take_or_append<T>(t: &mut Vec<T>, s: &mut Vec<T>) {
2796            if t.is_empty() {
2797                *t = mem::take(s);
2798            } else {
2799                t.append(s)
2800            }
2801        }
2802
2803        take_or_append(&mut self.transforms, &mut split.transforms);
2804        take_or_append(&mut self.floats, &mut split.floats);
2805        take_or_append(&mut self.colors, &mut split.colors);
2806        take_or_append(&mut self.extensions, &mut split.extensions);
2807
2808        if let Some(c) = self.clear_color.take() {
2809            self.clear_color = Some(c);
2810        }
2811    }
2812
2813    /// Calls `update` to render update a separate nested window on this frame.
2814    pub fn with_nested_window(
2815        &mut self,
2816        render_update_widgets: Arc<RenderUpdates>,
2817        root_id: WidgetId,
2818        root_bounds: WidgetBoundsInfo,
2819        update: impl FnOnce(&mut Self),
2820    ) {
2821        let mut nested = Self::new(render_update_widgets, self.frame_id, root_id, root_bounds, self.current_clear_color);
2822
2823        update(&mut nested);
2824
2825        // fold
2826        fn take_or_append<T>(t: &mut Vec<T>, s: &mut Vec<T>) {
2827            if t.is_empty() {
2828                *t = mem::take(s);
2829            } else {
2830                t.append(s)
2831            }
2832        }
2833        take_or_append(&mut self.transforms, &mut nested.transforms);
2834        take_or_append(&mut self.floats, &mut nested.floats);
2835        take_or_append(&mut self.colors, &mut nested.colors);
2836        take_or_append(&mut self.extensions, &mut nested.extensions);
2837    }
2838
2839    /// External render update requests for this frame.
2840    pub fn render_update_widgets(&self) -> &Arc<RenderUpdates> {
2841        &self.render_update_widgets
2842    }
2843
2844    /// Finalize the update.
2845    ///
2846    /// Returns the property updates and the new clear color if any was set.
2847    pub fn finalize(mut self, info_tree: &WidgetInfoTree) -> BuiltFrameUpdate {
2848        info_tree.after_render_update(self.frame_id);
2849
2850        if self.clear_color == Some(self.current_clear_color) {
2851            self.clear_color = None;
2852        }
2853
2854        BuiltFrameUpdate {
2855            clear_color: self.clear_color,
2856            transforms: self.transforms,
2857            floats: self.floats,
2858            colors: self.colors,
2859            extensions: self.extensions,
2860        }
2861    }
2862}
2863
2864/// Output of a [`FrameBuilder`].
2865#[non_exhaustive]
2866pub struct BuiltFrameUpdate {
2867    /// Bound transforms update.
2868    pub transforms: Vec<FrameValueUpdate<PxTransform>>,
2869    /// Bound floats update.
2870    pub floats: Vec<FrameValueUpdate<f32>>,
2871    /// Bound colors update.
2872    pub colors: Vec<FrameValueUpdate<Rgba>>,
2873    /// New clear color.
2874    pub clear_color: Option<Rgba>,
2875    /// Renderer extension updates.
2876    pub extensions: Vec<(ApiExtensionId, ApiExtensionPayload)>,
2877}
2878
2879unique_id_32! {
2880    #[derive(Debug)]
2881    struct FrameBindingKeyId;
2882}
2883impl_unique_id_bytemuck!(FrameBindingKeyId);
2884
2885unique_id_32! {
2886    /// Unique ID of a reference frame.
2887    #[derive(Debug)]
2888    pub struct SpatialFrameId;
2889}
2890impl_unique_id_bytemuck!(SpatialFrameId);
2891
2892#[derive(Clone, Copy, PartialEq, Eq)]
2893enum ReferenceFrameIdInner {
2894    Unique(SpatialFrameId),
2895    UniqueIndex(SpatialFrameId, u32),
2896    Widget(WidgetId),
2897    WidgetIndex(WidgetId, u32),
2898    FrameValue(FrameValueKey<PxTransform>),
2899    FrameValueIndex(FrameValueKey<PxTransform>, u32),
2900}
2901impl ReferenceFrameIdInner {
2902    const _RESERVED: u64 = 1 << 63; // view process
2903    const UNIQUE: u64 = 1 << 62;
2904    const WIDGET: u64 = 1 << 61;
2905    const FRAME_VALUE: u64 = 1 << 60;
2906}
2907impl From<ReferenceFrameIdInner> for RenderReferenceFrameId {
2908    fn from(value: ReferenceFrameIdInner) -> Self {
2909        match value {
2910            ReferenceFrameIdInner::UniqueIndex(id, index) => {
2911                RenderReferenceFrameId(id.get() as u64, index as u64 | ReferenceFrameIdInner::UNIQUE)
2912            }
2913            ReferenceFrameIdInner::WidgetIndex(id, index) => RenderReferenceFrameId(id.get(), index as u64 | ReferenceFrameIdInner::WIDGET),
2914            ReferenceFrameIdInner::FrameValue(key) => {
2915                RenderReferenceFrameId(((key.id.get() as u64) << 32) | u32::MAX as u64, ReferenceFrameIdInner::FRAME_VALUE)
2916            }
2917            ReferenceFrameIdInner::FrameValueIndex(key, index) => {
2918                RenderReferenceFrameId(((key.id.get() as u64) << 32) | index as u64, ReferenceFrameIdInner::FRAME_VALUE)
2919            }
2920            ReferenceFrameIdInner::Unique(id) => {
2921                RenderReferenceFrameId(id.get() as u64, (u32::MAX as u64 + 1) | ReferenceFrameIdInner::UNIQUE)
2922            }
2923            ReferenceFrameIdInner::Widget(id) => RenderReferenceFrameId(id.get(), (u32::MAX as u64 + 1) | ReferenceFrameIdInner::WIDGET),
2924        }
2925    }
2926}
2927
2928/// Represents an unique key for a spatial reference frame that is recreated in multiple frames.
2929///
2930/// The key can be generated from [`WidgetId`], [`SpatialFrameId`] or [`FrameValueKey<PxTransform>`] all guaranteed
2931/// to be unique even if the inner value of IDs is the same.
2932///
2933/// [`FrameValueKey<PxTransform>`]: FrameValueKey
2934#[derive(Clone, Copy, PartialEq, Eq)]
2935pub struct ReferenceFrameId(ReferenceFrameIdInner);
2936impl ReferenceFrameId {
2937    /// Key used for the widget inner transform.
2938    ///
2939    /// See [`FrameBuilder::push_inner`].
2940    fn from_widget(widget_id: WidgetId) -> Self {
2941        Self(ReferenceFrameIdInner::Widget(widget_id))
2942    }
2943
2944    /// Key from [`WidgetId`] and [`u32`] index.
2945    ///
2946    /// This can be used in nodes that know that they are the only one rendering children nodes.
2947    pub fn from_widget_child(parent_id: WidgetId, child_index: u32) -> Self {
2948        Self(ReferenceFrameIdInner::WidgetIndex(parent_id, child_index))
2949    }
2950
2951    /// Key from [`SpatialFrameId`].
2952    pub fn from_unique(id: SpatialFrameId) -> Self {
2953        Self(ReferenceFrameIdInner::Unique(id))
2954    }
2955
2956    /// Key from [`SpatialFrameId`] and [`u32`] index.
2957    pub fn from_unique_child(id: SpatialFrameId, child_index: u32) -> Self {
2958        Self(ReferenceFrameIdInner::UniqueIndex(id, child_index))
2959    }
2960
2961    /// Key from a [`FrameValueKey<PxTransform>`].
2962    pub fn from_frame_value(frame_value_key: FrameValueKey<PxTransform>) -> Self {
2963        Self(ReferenceFrameIdInner::FrameValue(frame_value_key))
2964    }
2965
2966    /// Key from a [`FrameValueKey<PxTransform>`] and [`u32`] index.
2967    pub fn from_frame_value_child(frame_value_key: FrameValueKey<PxTransform>, child_index: u32) -> Self {
2968        Self(ReferenceFrameIdInner::FrameValueIndex(frame_value_key, child_index))
2969    }
2970}
2971impl From<ReferenceFrameId> for RenderReferenceFrameId {
2972    fn from(value: ReferenceFrameId) -> Self {
2973        value.0.into()
2974    }
2975}
2976impl From<FrameValueKey<PxTransform>> for ReferenceFrameId {
2977    fn from(value: FrameValueKey<PxTransform>) -> Self {
2978        Self::from_frame_value(value)
2979    }
2980}
2981impl From<SpatialFrameId> for ReferenceFrameId {
2982    fn from(id: SpatialFrameId) -> Self {
2983        Self::from_unique(id)
2984    }
2985}
2986impl From<(SpatialFrameId, u32)> for ReferenceFrameId {
2987    fn from((id, index): (SpatialFrameId, u32)) -> Self {
2988        Self::from_unique_child(id, index)
2989    }
2990}
2991impl From<(WidgetId, u32)> for ReferenceFrameId {
2992    fn from((id, index): (WidgetId, u32)) -> Self {
2993        Self::from_widget_child(id, index)
2994    }
2995}
2996impl From<(FrameValueKey<PxTransform>, u32)> for ReferenceFrameId {
2997    fn from((key, index): (FrameValueKey<PxTransform>, u32)) -> Self {
2998        Self::from_frame_value_child(key, index)
2999    }
3000}
3001
3002/// Unique key of an updatable value in the view-process frame.
3003#[derive(Debug)]
3004pub struct FrameValueKey<T> {
3005    id: FrameBindingKeyId,
3006    _type: PhantomData<T>,
3007}
3008impl<T> PartialEq for FrameValueKey<T> {
3009    fn eq(&self, other: &Self) -> bool {
3010        self.id == other.id
3011    }
3012}
3013impl<T> Eq for FrameValueKey<T> {}
3014impl<T> Clone for FrameValueKey<T> {
3015    fn clone(&self) -> Self {
3016        *self
3017    }
3018}
3019impl<T> Copy for FrameValueKey<T> {}
3020impl<T> FrameValueKey<T> {
3021    /// Generates a new unique ID.
3022    pub fn new_unique() -> Self {
3023        FrameValueKey {
3024            id: FrameBindingKeyId::new_unique(),
3025            _type: PhantomData,
3026        }
3027    }
3028
3029    /// To view key.
3030    pub fn to_wr(self) -> zng_view_api::display_list::FrameValueId {
3031        Self::to_wr_child(self, u32::MAX)
3032    }
3033
3034    /// To view key with an extra `index` modifier.
3035    pub fn to_wr_child(self, child_index: u32) -> zng_view_api::display_list::FrameValueId {
3036        zng_view_api::display_list::FrameValueId::from_raw(((self.id.get() as u64) << 32) | child_index as u64)
3037    }
3038
3039    /// Create a binding with this key.
3040    ///
3041    /// The `animating` flag controls if the binding will propagate to webrender, if `true`
3042    /// webrender frame updates are generated for frame update requests, if `false` only the display
3043    /// list is patched, a full frame is rendered on frame update requests.
3044    pub fn bind(self, value: T, animating: bool) -> FrameValue<T> {
3045        self.bind_child(u32::MAX, value, animating)
3046    }
3047
3048    /// Like [`bind`] but the key is modified to include the `child_index`.
3049    ///
3050    /// [`bind`]: Self::bind
3051    pub fn bind_child(self, child_index: u32, value: T, animating: bool) -> FrameValue<T> {
3052        FrameValue::Bind {
3053            id: self.to_wr_child(child_index),
3054            value,
3055            animating,
3056        }
3057    }
3058
3059    /// Create a value update with this key.
3060    pub fn update(self, value: T, animating: bool) -> FrameValueUpdate<T> {
3061        self.update_child(u32::MAX, value, animating)
3062    }
3063
3064    /// Like [`update`] but the key is modified to include the `child_index`.
3065    ///
3066    /// [`update`]: Self::update
3067    pub fn update_child(self, child_index: u32, value: T, animating: bool) -> FrameValueUpdate<T> {
3068        FrameValueUpdate::new(self.to_wr_child(child_index), value, animating)
3069    }
3070
3071    /// Create a binding with this key and `var`.
3072    ///
3073    /// The `map` must produce a copy or clone of the frame value.
3074    pub fn bind_var<VT: VarValue>(self, var: &Var<VT>, map: impl FnOnce(&VT) -> T) -> FrameValue<T> {
3075        self.bind_var_child(u32::MAX, var, map)
3076    }
3077
3078    /// Like [`bind_var`] but the key is modified to include the `child_index`.
3079    ///
3080    /// [`bind_var`]: Self::bind_var
3081    pub fn bind_var_child<VT: VarValue>(self, child_index: u32, var: &Var<VT>, map: impl FnOnce(&VT) -> T) -> FrameValue<T> {
3082        if var.capabilities().contains(VarCapability::NEW) {
3083            FrameValue::Bind {
3084                id: self.to_wr_child(child_index),
3085                value: var.with(map),
3086                animating: var.is_animating(),
3087            }
3088        } else {
3089            FrameValue::Value(var.with(map))
3090        }
3091    }
3092
3093    /// Create a binding with this key, `var` and already mapped `value`.
3094    pub fn bind_var_mapped<VT: VarValue>(&self, var: &Var<VT>, value: T) -> FrameValue<T> {
3095        self.bind_var_mapped_child(u32::MAX, var, value)
3096    }
3097
3098    /// Like [`bind_var_mapped`] but the key is modified to include the `child_index`.
3099    ///
3100    /// [`bind_var_mapped`]: Self::bind_var_mapped
3101    pub fn bind_var_mapped_child<VT: VarValue>(&self, child_index: u32, var: &Var<VT>, value: T) -> FrameValue<T> {
3102        if var.capabilities().contains(VarCapability::NEW) {
3103            FrameValue::Bind {
3104                id: self.to_wr_child(child_index),
3105                value,
3106                animating: var.is_animating(),
3107            }
3108        } else {
3109            FrameValue::Value(value)
3110        }
3111    }
3112
3113    /// Create a value update with this key and `var`.
3114    pub fn update_var<VT: VarValue>(self, var: &Var<VT>, map: impl FnOnce(&VT) -> T) -> Option<FrameValueUpdate<T>> {
3115        self.update_var_child(u32::MAX, var, map)
3116    }
3117
3118    /// Like [`update_var`] but the key is modified to include the `child_index`.
3119    ///
3120    /// [`update_var`]: Self::update_var
3121    pub fn update_var_child<VT: VarValue>(
3122        self,
3123        child_index: u32,
3124        var: &Var<VT>,
3125        map: impl FnOnce(&VT) -> T,
3126    ) -> Option<FrameValueUpdate<T>> {
3127        if var.capabilities().contains(VarCapability::NEW) {
3128            Some(FrameValueUpdate::new(
3129                self.to_wr_child(child_index),
3130                var.with(map),
3131                var.is_animating(),
3132            ))
3133        } else {
3134            None
3135        }
3136    }
3137
3138    /// Create a value update with this key, `var` and already mapped `value`.
3139    pub fn update_var_mapped<VT: VarValue>(self, var: &Var<VT>, value: T) -> Option<FrameValueUpdate<T>> {
3140        self.update_var_mapped_child(u32::MAX, var, value)
3141    }
3142
3143    /// Like [`update_var_mapped`] but the key is modified to include the `child_index`.
3144    ///
3145    /// [`update_var_mapped`]: Self::update_var_mapped
3146    pub fn update_var_mapped_child<VT: VarValue>(self, child_index: u32, var: &Var<VT>, value: T) -> Option<FrameValueUpdate<T>> {
3147        if var.capabilities().contains(VarCapability::NEW) {
3148            Some(FrameValueUpdate::new(self.to_wr_child(child_index), value, var.is_animating()))
3149        } else {
3150            None
3151        }
3152    }
3153}
3154
3155bitflags::bitflags! {
3156    /// Configure if a synthetic font is generated for fonts that do not implement **bold** or *oblique* variants.
3157    #[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
3158    #[serde(transparent)]
3159    pub struct FontSynthesis: u8 {
3160        /// No synthetic font generated, if font resolution does not find a variant the matches the requested style and weight
3161        /// the request is ignored and the normal font is returned.
3162        const DISABLED = 0;
3163        /// Enable synthetic bold. Font resolution finds the closest bold variant, the difference is added using extra stroke.
3164        const BOLD = 1;
3165        /// Enable synthetic oblique. If the font resolution does not find an oblique or italic variant a skew transform is applied.
3166        const OBLIQUE = 2;
3167        /// Enabled all synthetic font possibilities.
3168        const ENABLED = Self::BOLD.bits() | Self::OBLIQUE.bits();
3169    }
3170}
3171impl Default for FontSynthesis {
3172    /// [`FontSynthesis::ENABLED`]
3173    fn default() -> Self {
3174        FontSynthesis::ENABLED
3175    }
3176}
3177impl_from_and_into_var! {
3178    /// Convert to full [`ENABLED`](FontSynthesis::ENABLED) or [`DISABLED`](FontSynthesis::DISABLED).
3179    fn from(enabled: bool) -> FontSynthesis {
3180        if enabled { FontSynthesis::ENABLED } else { FontSynthesis::DISABLED }
3181    }
3182}