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