zng_app/
render.rs

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