zng_wgt_fill/
node.rs

1//! Color and gradient fill nodes and builders.
2
3use zng_wgt::prelude::{gradient::*, *};
4
5/// Gradient builder start.
6///
7/// Use [`gradient`] to start building.
8///
9/// [`gradient`]: fn@gradient
10pub struct GradientBuilder {
11    stops: Var<GradientStops>,
12}
13
14/// Starts building a gradient with the color stops.
15pub fn gradient(stops: impl IntoVar<GradientStops>) -> GradientBuilder {
16    GradientBuilder { stops: stops.into_var() }
17}
18
19/// Starts building a linear gradient with the axis and color stops.
20///
21/// Returns a node that is also a builder that can be used to refine the gradient definition.
22pub fn linear_gradient(axis: impl IntoVar<LinearGradientAxis>, stops: impl IntoVar<GradientStops>) -> LinearGradient {
23    gradient(stops).linear(axis)
24}
25
26/// Starts building a radial gradient with the radius and color stops.
27///
28/// Returns a node that is also a builder that can be used to refine the gradient definition.
29pub fn radial_gradient(
30    center: impl IntoVar<Point>,
31    radius: impl IntoVar<GradientRadius>,
32    stops: impl IntoVar<GradientStops>,
33) -> RadialGradient {
34    gradient(stops).radial(center, radius)
35}
36
37/// Starts building a conic gradient with the angle and color stops.
38///
39/// Returns a node that is also a builder that can be used to refine the gradient definition.
40pub fn conic_gradient(center: impl IntoVar<Point>, angle: impl IntoVar<AngleRadian>, stops: impl IntoVar<GradientStops>) -> ConicGradient {
41    gradient(stops).conic(center, angle)
42}
43
44impl GradientBuilder {
45    /// Builds a linear gradient.
46    ///
47    /// Returns a node that fills the available space with the gradient, the node type doubles
48    /// as a builder that can continue building a linear gradient.
49    pub fn linear(self, axis: impl IntoVar<LinearGradientAxis>) -> LinearGradient {
50        LinearGradient {
51            stops: self.stops,
52            axis: axis.into_var(),
53            extend_mode: ExtendMode::Clamp.into_var(),
54
55            data: LinearNodeData::default(),
56        }
57    }
58
59    /// Builds a radial gradient.
60    ///
61    /// Returns a node that fills the available space with the gradient, the node type doubles
62    /// as a builder that can continue building a radial gradient.
63    pub fn radial(self, center: impl IntoVar<Point>, radius: impl IntoVar<GradientRadius>) -> RadialGradient {
64        RadialGradient {
65            stops: self.stops,
66            center: center.into_var(),
67            radius: radius.into_var(),
68            extend_mode: ExtendMode::Clamp.into_var(),
69            data: RadialNodeData::default(),
70        }
71    }
72
73    /// Builds a conic gradient.
74    ///
75    /// Returns a node that fills the available space with the gradient, the node type doubles
76    /// as a builder that can continue building a conic gradient.
77    pub fn conic(self, center: impl IntoVar<Point>, angle: impl IntoVar<AngleRadian>) -> ConicGradient {
78        ConicGradient {
79            stops: self.stops,
80            center: center.into_var(),
81            angle: angle.into_var(),
82            extend_mode: ExtendMode::Clamp.into_var(),
83            data: ConicNodeData::default(),
84        }
85    }
86}
87
88/// Linear gradient.
89///
90/// Can be used as a node that fills the available space with the gradient, or can continue building a linear
91/// or tiled linear gradient.
92///
93/// Use [`gradient`] or [`linear_gradient`] to start building.
94///
95/// [`gradient`]: fn@gradient
96pub struct LinearGradient {
97    stops: Var<GradientStops>,
98    axis: Var<LinearGradientAxis>,
99    extend_mode: Var<ExtendMode>,
100
101    data: LinearNodeData,
102}
103impl LinearGradient {
104    /// Sets the extend mode of the linear gradient.
105    ///
106    /// By default is [`ExtendMode::Clamp`].
107    ///
108    /// [`ExtendMode::Clamp`]: zng_wgt::prelude::gradient::ExtendMode::Clamp
109    pub fn extend_mode(self, mode: impl IntoVar<ExtendMode>) -> LinearGradient {
110        LinearGradient {
111            stops: self.stops,
112            axis: self.axis,
113            extend_mode: mode.into_var(),
114            data: self.data,
115        }
116    }
117
118    /// Sets the extend mode to [`ExtendMode::Repeat`].
119    ///
120    /// [`ExtendMode::Repeat`]: zng_wgt::prelude::gradient::ExtendMode::Repeat
121    pub fn repeat(self) -> LinearGradient {
122        self.extend_mode(ExtendMode::Repeat)
123    }
124
125    /// Sets the extend mode to [`ExtendMode::Reflect`].
126    ///
127    /// [`ExtendMode::Reflect`]: zng_wgt::prelude::gradient::ExtendMode::Reflect
128    pub fn reflect(self) -> LinearGradient {
129        self.extend_mode(ExtendMode::Reflect)
130    }
131
132    /// Continue building a tiled linear gradient.
133    pub fn tile(self, tile_size: impl IntoVar<Size>, tile_spacing: impl IntoVar<Size>) -> TiledLinearGradient {
134        TiledLinearGradient {
135            stops: self.stops,
136            axis: self.axis,
137            extend_mode: self.extend_mode,
138            tile_origin: const_var(Point::zero()),
139            tile_size: tile_size.into_var(),
140            tile_spacing: tile_spacing.into_var(),
141            data: self.data,
142            tile_data: TiledNodeData::default(),
143        }
144    }
145
146    /// Continue building a tiled linear gradient.
147    ///
148    /// Relative values are resolved on the full available size, so settings this to `100.pct()` is
149    /// the same as not tiling.
150    pub fn tile_size(self, size: impl IntoVar<Size>) -> TiledLinearGradient {
151        self.tile(size, Size::zero())
152    }
153}
154
155/// Tiled linear gradient.
156///
157/// Can be used as a node that fills the available space with the gradient tiles, or can continue building a
158/// tiled linear gradient.
159///
160/// Use [`LinearGradient::tile`] to build.
161pub struct TiledLinearGradient {
162    stops: Var<GradientStops>,
163    axis: Var<LinearGradientAxis>,
164    extend_mode: Var<ExtendMode>,
165    tile_origin: Var<Point>,
166    tile_size: Var<Size>,
167    tile_spacing: Var<Size>,
168    data: LinearNodeData,
169    tile_data: TiledNodeData,
170}
171impl TiledLinearGradient {
172    /// Set the space between tiles.
173    ///
174    /// Relative values are resolved on the tile size, so setting this to `100.pct()` will
175    /// *skip* a tile.
176    ///
177    /// Leftover values are resolved on the space taken by tiles that do not
178    /// fully fit in the available space, so setting this to `1.lft()` will cause the *border* tiles
179    /// to always touch the full bounds and the middle filled with the maximum full tiles that fit or
180    /// empty space.
181    pub fn tile_spacing(self, spacing: impl IntoVar<Size>) -> TiledLinearGradient {
182        TiledLinearGradient {
183            stops: self.stops,
184            axis: self.axis,
185            extend_mode: self.extend_mode,
186            tile_origin: self.tile_origin,
187            tile_size: self.tile_size,
188            tile_spacing: spacing.into_var(),
189            data: self.data,
190            tile_data: self.tile_data,
191        }
192    }
193
194    /// Sets the tile offset.
195    ///
196    /// Relative values are resolved on the tile size, so setting this to `100.pct()` will
197    /// offset a full *turn*.
198    pub fn tile_origin(self, origin: impl IntoVar<Point>) -> TiledLinearGradient {
199        TiledLinearGradient {
200            stops: self.stops,
201            axis: self.axis,
202            extend_mode: self.extend_mode,
203            tile_origin: origin.into_var(),
204            tile_size: self.tile_size,
205            tile_spacing: self.tile_spacing,
206            data: self.data,
207            tile_data: self.tile_data,
208        }
209    }
210}
211
212/// Radial gradient.
213///
214/// Can be used as a node that fills the available space with the gradient, or can continue building a radial
215/// or tiled radial gradient.
216///
217/// Use [`gradient`] or [`radial_gradient`] to start building.
218///  
219/// [`gradient`]: fn@gradient
220pub struct RadialGradient {
221    stops: Var<GradientStops>,
222    center: Var<Point>,
223    radius: Var<GradientRadius>,
224    extend_mode: Var<ExtendMode>,
225
226    data: RadialNodeData,
227}
228impl RadialGradient {
229    /// Sets the extend mode of the radial gradient.
230    ///
231    /// By default is [`ExtendMode::Clamp`].
232    ///
233    /// [`ExtendMode::Clamp`]: zng_wgt::prelude::gradient::ExtendMode::Clamp
234    pub fn extend_mode(self, mode: impl IntoVar<ExtendMode>) -> RadialGradient {
235        RadialGradient {
236            stops: self.stops,
237            center: self.center,
238            radius: self.radius,
239            extend_mode: mode.into_var(),
240            data: self.data,
241        }
242    }
243
244    /// Sets the extend mode to [`ExtendMode::Repeat`].
245    ///
246    /// [`ExtendMode::Repeat`]: zng_wgt::prelude::gradient::ExtendMode::Repeat
247    pub fn repeat(self) -> RadialGradient {
248        self.extend_mode(ExtendMode::Repeat)
249    }
250
251    /// Sets the extend mode to [`ExtendMode::Reflect`].
252    ///
253    /// [`ExtendMode::Reflect`]: zng_wgt::prelude::gradient::ExtendMode::Reflect
254    pub fn reflect(self) -> RadialGradient {
255        self.extend_mode(ExtendMode::Reflect)
256    }
257
258    /// Continue building a tiled radial gradient.
259    pub fn tile(self, tile_size: impl IntoVar<Size>, tile_spacing: impl IntoVar<Size>) -> TiledRadialGradient {
260        TiledRadialGradient {
261            stops: self.stops,
262            center: self.center,
263            radius: self.radius,
264            extend_mode: self.extend_mode,
265            tile_origin: const_var(Point::zero()),
266            tile_size: tile_size.into_var(),
267            tile_spacing: tile_spacing.into_var(),
268            data: self.data,
269            tile_data: TiledNodeData::default(),
270        }
271    }
272
273    /// Continue building a tiled radial gradient.
274    pub fn tile_size(self, size: impl IntoVar<Size>) -> TiledRadialGradient {
275        self.tile(size, Size::zero())
276    }
277}
278
279/// Tiled radial gradient.
280///
281/// Can be used as a node that fills the available space with the gradient tiles, or can continue building the gradient.
282///
283/// Use [`RadialGradient::tile`] to build.
284///  
285/// [`gradient`]: fn@gradient
286pub struct TiledRadialGradient {
287    stops: Var<GradientStops>,
288    center: Var<Point>,
289    radius: Var<GradientRadius>,
290    extend_mode: Var<ExtendMode>,
291    tile_origin: Var<Point>,
292    tile_size: Var<Size>,
293    tile_spacing: Var<Size>,
294    data: RadialNodeData,
295    tile_data: TiledNodeData,
296}
297impl TiledRadialGradient {
298    /// Set the space between tiles.
299    ///
300    /// Relative values are resolved on the tile size, so setting this to `100.pct()` will
301    /// *skip* a tile.
302    ///
303    /// Leftover values are resolved on the space taken by tiles that do not
304    /// fully fit in the available space, so setting this to `1.lft()` will cause the *border* tiles
305    /// to always touch the full bounds and the middle filled with the maximum full tiles that fit or
306    /// empty space.
307    pub fn tile_spacing(self, spacing: impl IntoVar<Size>) -> TiledRadialGradient {
308        TiledRadialGradient {
309            stops: self.stops,
310            center: self.center,
311            radius: self.radius,
312            extend_mode: self.extend_mode,
313            tile_origin: self.tile_origin,
314            tile_size: self.tile_size,
315            tile_spacing: spacing.into_var(),
316            data: self.data,
317            tile_data: self.tile_data,
318        }
319    }
320
321    /// Sets the tile offset.
322    ///
323    /// Relative values are resolved on the tile size, so setting this to `100.pct()` will
324    /// offset a full *turn*.
325    pub fn tile_origin(self, origin: impl IntoVar<Point>) -> TiledRadialGradient {
326        TiledRadialGradient {
327            stops: self.stops,
328            center: self.center,
329            radius: self.radius,
330            extend_mode: self.extend_mode,
331            tile_origin: origin.into_var(),
332            tile_size: self.tile_size,
333            tile_spacing: self.tile_spacing,
334            data: self.data,
335            tile_data: self.tile_data,
336        }
337    }
338}
339
340/// Conic gradient.
341///
342/// Can be used as a node that fills the available space with the gradient, or can continue building the conic
343/// or a tiled conic gradient.
344///
345/// Use [`gradient`] or [`conic_gradient`] to start building.
346///  
347/// [`gradient`]: fn@gradient
348pub struct ConicGradient {
349    stops: Var<GradientStops>,
350    center: Var<Point>,
351    angle: Var<AngleRadian>,
352    extend_mode: Var<ExtendMode>,
353
354    data: ConicNodeData,
355}
356impl ConicGradient {
357    /// Sets the extend mode of the conic gradient.
358    ///
359    /// By default is [`ExtendMode::Clamp`].
360    ///
361    /// [`ExtendMode::Clamp`]: zng_wgt::prelude::gradient::ExtendMode::Clamp
362    pub fn extend_mode(self, mode: impl IntoVar<ExtendMode>) -> ConicGradient {
363        ConicGradient {
364            stops: self.stops,
365            center: self.center,
366            angle: self.angle,
367            extend_mode: mode.into_var(),
368            data: self.data,
369        }
370    }
371
372    /// Sets the extend mode to [`ExtendMode::Repeat`].
373    ///
374    /// [`ExtendMode::Repeat`]: zng_wgt::prelude::gradient::ExtendMode::Repeat
375    pub fn repeat(self) -> ConicGradient {
376        self.extend_mode(ExtendMode::Repeat)
377    }
378
379    /// Sets the extend mode to [`ExtendMode::Reflect`].
380    ///
381    /// [`ExtendMode::Reflect`]: zng_wgt::prelude::gradient::ExtendMode::Reflect
382    pub fn reflect(self) -> ConicGradient {
383        self.extend_mode(ExtendMode::Reflect)
384    }
385
386    /// Continue building a tiled radial gradient.
387    pub fn tile(self, tile_size: impl IntoVar<Size>, tile_spacing: impl IntoVar<Size>) -> TiledConicGradient {
388        TiledConicGradient {
389            stops: self.stops,
390            center: self.center,
391            angle: self.angle,
392            extend_mode: self.extend_mode,
393            tile_origin: const_var(Point::zero()),
394            tile_size: tile_size.into_var(),
395            tile_spacing: tile_spacing.into_var(),
396            data: self.data,
397            tile_data: TiledNodeData::default(),
398        }
399    }
400
401    /// Continue building a tiled radial gradient.
402    pub fn tile_size(self, size: impl IntoVar<Size>) -> TiledConicGradient {
403        self.tile(size, Size::zero())
404    }
405}
406
407/// Tiled conic gradient.
408///
409/// Can be used as a node that fills the available space with the gradient tiles, or can continue building the gradient.
410///
411/// Use [`ConicGradient::tile`] to build.
412///  
413/// [`gradient`]: fn@gradient
414pub struct TiledConicGradient {
415    stops: Var<GradientStops>,
416    center: Var<Point>,
417    angle: Var<AngleRadian>,
418    extend_mode: Var<ExtendMode>,
419    tile_origin: Var<Point>,
420    tile_size: Var<Size>,
421    tile_spacing: Var<Size>,
422    data: ConicNodeData,
423    tile_data: TiledNodeData,
424}
425impl TiledConicGradient {
426    /// Set the space between tiles.
427    ///
428    /// Relative values are resolved on the tile size, so setting this to `100.pct()` will
429    /// *skip* a tile.
430    ///
431    /// Leftover values are resolved on the space taken by tiles that do not
432    /// fully fit in the available space, so setting this to `1.lft()` will cause the *border* tiles
433    /// to always touch the full bounds and the middle filled with the maximum full tiles that fit or
434    /// empty space.
435    pub fn tile_spacing<TS2>(self, spacing: impl IntoVar<Size>) -> TiledConicGradient {
436        TiledConicGradient {
437            stops: self.stops,
438            center: self.center,
439            angle: self.angle,
440            extend_mode: self.extend_mode,
441            tile_origin: self.tile_origin,
442            tile_size: self.tile_size,
443            tile_spacing: spacing.into_var(),
444            data: self.data,
445            tile_data: self.tile_data,
446        }
447    }
448
449    /// Sets the tile offset.
450    ///
451    /// Relative values are resolved on the tile size, so setting this to `100.pct()` will
452    /// offset a full *turn*.
453    pub fn tile_origin(self, origin: impl IntoVar<Point>) -> TiledConicGradient {
454        TiledConicGradient {
455            stops: self.stops,
456            center: self.center,
457            angle: self.angle,
458            extend_mode: self.extend_mode,
459            tile_origin: origin.into_var(),
460            tile_size: self.tile_size,
461            tile_spacing: self.tile_spacing,
462            data: self.data,
463            tile_data: self.tile_data,
464        }
465    }
466}
467
468#[derive(Default)]
469struct LinearNodeData {
470    line: PxLine,
471    stops: Vec<RenderGradientStop>,
472    size: PxSize,
473}
474impl UiNodeImpl for LinearGradient {
475    fn children_len(&self) -> usize {
476        0
477    }
478
479    fn with_child(&mut self, _: usize, _: &mut dyn FnMut(&mut UiNode)) {}
480
481    fn init(&mut self) {
482        WIDGET.sub_var_layout(&self.axis).sub_var(&self.stops).sub_var(&self.extend_mode);
483    }
484
485    fn measure(&mut self, _: &mut WidgetMeasure) -> PxSize {
486        LAYOUT.constraints().fill_size()
487    }
488
489    fn update(&mut self, _: &WidgetUpdates) {
490        if self.stops.is_new() || self.extend_mode.is_new() {
491            WIDGET.layout().render();
492        }
493    }
494
495    fn layout(&mut self, _: &mut WidgetLayout) -> PxSize {
496        let size = LAYOUT.constraints().fill_size();
497        let axis = self.axis.layout();
498        if self.data.size != size || self.data.line != axis {
499            self.data.size = size;
500            self.data.line = axis;
501            WIDGET.render();
502        }
503
504        let length = self.data.line.length();
505        LAYOUT.with_constraints(LAYOUT.constraints().with_new_exact_x(length), || {
506            self.stops
507                .with(|s| s.layout_linear(LayoutAxis::X, self.extend_mode.get(), &mut self.data.line, &mut self.data.stops))
508        });
509
510        size
511    }
512
513    fn render(&mut self, frame: &mut FrameBuilder) {
514        frame.push_linear_gradient(
515            PxRect::from_size(self.data.size),
516            self.data.line,
517            &self.data.stops,
518            self.extend_mode.get().into(),
519            PxPoint::zero(),
520            self.data.size,
521            PxSize::zero(),
522        );
523    }
524}
525
526#[derive(Default)]
527struct TiledNodeData {
528    origin: PxPoint,
529    size: PxSize,
530    spacing: PxSize,
531}
532impl UiNodeImpl for TiledLinearGradient {
533    fn children_len(&self) -> usize {
534        0
535    }
536
537    fn with_child(&mut self, _: usize, _: &mut dyn FnMut(&mut UiNode)) {}
538
539    fn init(&mut self) {
540        WIDGET
541            .sub_var_layout(&self.axis)
542            .sub_var(&self.stops)
543            .sub_var(&self.extend_mode)
544            .sub_var_layout(&self.tile_origin)
545            .sub_var_layout(&self.tile_size)
546            .sub_var_layout(&self.tile_spacing);
547    }
548
549    fn update(&mut self, _: &WidgetUpdates) {
550        if self.stops.is_new() || self.extend_mode.is_new() {
551            WIDGET.layout().render();
552        }
553    }
554
555    fn measure(&mut self, _: &mut WidgetMeasure) -> PxSize {
556        LAYOUT.constraints().fill_size()
557    }
558
559    fn layout(&mut self, _: &mut WidgetLayout) -> PxSize {
560        let constraints = LAYOUT.constraints();
561        let size = constraints.fill_size();
562        let axis = self.axis.layout();
563        let tile_size = self.tile_size.layout_dft(size);
564
565        let mut request_render = false;
566
567        if self.data.size != size || self.data.line != axis || self.tile_data.size != tile_size {
568            self.data.size = size;
569            self.data.line = self.axis.layout();
570            self.tile_data.size = tile_size;
571            request_render = true;
572        }
573
574        LAYOUT.with_constraints(PxConstraints2d::new_exact_size(self.tile_data.size), || {
575            let leftover = tile_leftover(self.tile_data.size, size);
576            LAYOUT.with_leftover(Some(leftover.width), Some(leftover.height), || {
577                let spacing = self.tile_spacing.layout();
578                request_render |= self.tile_data.spacing != spacing;
579                self.tile_data.spacing = spacing;
580            });
581            let origin = self.tile_origin.layout();
582            request_render |= self.tile_data.origin != origin;
583            self.tile_data.origin = origin;
584        });
585
586        let length = self.data.line.length();
587        LAYOUT.with_constraints(constraints.with_new_exact_x(length), || {
588            self.stops
589                .with(|s| s.layout_linear(LayoutAxis::X, self.extend_mode.get(), &mut self.data.line, &mut self.data.stops))
590        });
591
592        if request_render {
593            WIDGET.render();
594        }
595
596        size
597    }
598
599    fn render(&mut self, frame: &mut FrameBuilder) {
600        frame.push_linear_gradient(
601            PxRect::from_size(self.data.size),
602            self.data.line,
603            &self.data.stops,
604            self.extend_mode.get().into(),
605            self.tile_data.origin,
606            self.tile_data.size,
607            self.tile_data.spacing,
608        );
609    }
610}
611
612#[derive(Default)]
613struct RadialNodeData {
614    size: PxSize,
615    center: PxPoint,
616    radius: PxSize,
617    stops: Vec<RenderGradientStop>,
618}
619impl UiNodeImpl for RadialGradient {
620    fn children_len(&self) -> usize {
621        0
622    }
623
624    fn with_child(&mut self, _: usize, _: &mut dyn FnMut(&mut UiNode)) {}
625
626    fn init(&mut self) {
627        WIDGET
628            .sub_var_layout(&self.center)
629            .sub_var_layout(&self.radius)
630            .sub_var(&self.stops)
631            .sub_var(&self.extend_mode);
632    }
633
634    fn update(&mut self, _: &WidgetUpdates) {
635        if self.stops.is_new() || self.extend_mode.is_new() {
636            WIDGET.layout().render();
637        }
638    }
639
640    fn measure(&mut self, _: &mut WidgetMeasure) -> PxSize {
641        LAYOUT.constraints().fill_size()
642    }
643
644    fn layout(&mut self, _: &mut WidgetLayout) -> PxSize {
645        let size = LAYOUT.constraints().fill_size();
646
647        let mut request_render = size != self.data.size;
648
649        self.data.size = size;
650        LAYOUT.with_constraints(PxConstraints2d::new_fill_size(size), || {
651            let center = self.center.layout_dft(size.to_vector().to_point() * 0.5.fct());
652            let radius = self.radius.get().layout(center);
653            request_render |= center != self.data.center || radius != self.data.radius;
654            self.data.center = center;
655            self.data.radius = radius;
656        });
657
658        LAYOUT.with_constraints(
659            LAYOUT
660                .constraints()
661                .with_exact_x(self.data.radius.width.max(self.data.radius.height)),
662            || {
663                self.stops
664                    .with(|s| s.layout_radial(LayoutAxis::X, self.extend_mode.get(), &mut self.data.stops))
665            },
666        );
667
668        if request_render {
669            WIDGET.render();
670        }
671
672        size
673    }
674
675    fn render(&mut self, frame: &mut FrameBuilder) {
676        frame.push_radial_gradient(
677            PxRect::from_size(self.data.size),
678            self.data.center,
679            self.data.radius,
680            &self.data.stops,
681            self.extend_mode.get().into(),
682            PxPoint::zero(),
683            self.data.size,
684            PxSize::zero(),
685        );
686    }
687}
688impl UiNodeImpl for TiledRadialGradient {
689    fn children_len(&self) -> usize {
690        0
691    }
692
693    fn with_child(&mut self, _: usize, _: &mut dyn FnMut(&mut UiNode)) {}
694
695    fn init(&mut self) {
696        WIDGET
697            .sub_var_layout(&self.center)
698            .sub_var_layout(&self.radius)
699            .sub_var(&self.stops)
700            .sub_var(&self.extend_mode)
701            .sub_var_layout(&self.tile_origin)
702            .sub_var_layout(&self.tile_size)
703            .sub_var_layout(&self.tile_spacing);
704    }
705
706    fn update(&mut self, _: &WidgetUpdates) {
707        if self.stops.is_new() || self.extend_mode.is_new() {
708            WIDGET.layout().render();
709        }
710    }
711
712    fn measure(&mut self, _: &mut WidgetMeasure) -> PxSize {
713        LAYOUT.constraints().fill_size()
714    }
715
716    fn layout(&mut self, _: &mut WidgetLayout) -> PxSize {
717        let size = LAYOUT.constraints().fill_size();
718        let tile_size = self.tile_size.layout_dft(size);
719
720        let mut request_render = size != self.data.size || self.tile_data.size != tile_size;
721
722        self.data.size = size;
723        self.tile_data.size = tile_size;
724
725        LAYOUT.with_constraints(PxConstraints2d::new_exact_size(self.tile_data.size), || {
726            let leftover = tile_leftover(self.tile_data.size, size);
727            LAYOUT.with_leftover(Some(leftover.width), Some(leftover.height), || {
728                let spacing = self.tile_spacing.layout();
729                request_render |= self.tile_data.spacing != spacing;
730                self.tile_data.spacing = spacing;
731            });
732
733            let center = self.center.layout_dft(tile_size.to_vector().to_point() * 0.5.fct());
734            let radius = self.radius.get().layout(center);
735            let origin = self.tile_origin.layout();
736
737            request_render |= self.data.center != center || self.data.radius != radius || self.tile_data.origin != origin;
738
739            self.data.center = center;
740            self.data.radius = radius;
741            self.tile_data.origin = origin;
742        });
743
744        LAYOUT.with_constraints(
745            LAYOUT
746                .constraints()
747                .with_exact_x(self.data.radius.width.max(self.data.radius.height)),
748            || {
749                self.stops
750                    .with(|s| s.layout_radial(LayoutAxis::X, self.extend_mode.get(), &mut self.data.stops))
751            },
752        );
753
754        if request_render {
755            WIDGET.render();
756        }
757
758        size
759    }
760
761    fn render(&mut self, frame: &mut FrameBuilder) {
762        frame.push_radial_gradient(
763            PxRect::from_size(self.data.size),
764            self.data.center,
765            self.data.radius,
766            &self.data.stops,
767            self.extend_mode.get().into(),
768            self.tile_data.origin,
769            self.tile_data.size,
770            self.tile_data.spacing,
771        );
772    }
773}
774
775#[derive(Default)]
776struct ConicNodeData {
777    size: PxSize,
778    center: PxPoint,
779    stops: Vec<RenderGradientStop>,
780}
781impl UiNodeImpl for ConicGradient {
782    fn children_len(&self) -> usize {
783        0
784    }
785    fn with_child(&mut self, _: usize, _: &mut dyn FnMut(&mut UiNode)) {}
786
787    fn init(&mut self) {
788        WIDGET
789            .sub_var_layout(&self.center)
790            .sub_var_layout(&self.angle)
791            .sub_var(&self.stops)
792            .sub_var(&self.extend_mode);
793    }
794
795    fn update(&mut self, _: &WidgetUpdates) {
796        if self.stops.is_new() || self.extend_mode.is_new() {
797            WIDGET.layout().render();
798        }
799    }
800
801    fn measure(&mut self, _: &mut WidgetMeasure) -> PxSize {
802        LAYOUT.constraints().fill_size()
803    }
804
805    fn layout(&mut self, _: &mut WidgetLayout) -> PxSize {
806        let size = LAYOUT.constraints().fill_size();
807
808        let mut request_render = size != self.data.size;
809
810        self.data.size = size;
811        LAYOUT.with_constraints(PxConstraints2d::new_fill_size(size), || {
812            let center = self.center.layout_dft(size.to_vector().to_point() * 0.5.fct());
813            request_render |= self.data.center != center;
814            self.data.center = center;
815        });
816
817        let perimeter = Px({
818            let a = size.width.0 as f32;
819            let b = size.height.0 as f32;
820            std::f32::consts::PI * 2.0 * ((a * a + b * b) / 2.0).sqrt()
821        } as _);
822        LAYOUT.with_constraints(LAYOUT.constraints().with_exact_x(perimeter), || {
823            self.stops
824                .with(|s| s.layout_radial(LayoutAxis::X, self.extend_mode.get(), &mut self.data.stops))
825        });
826
827        if request_render {
828            WIDGET.render();
829        }
830
831        size
832    }
833
834    fn render(&mut self, frame: &mut FrameBuilder) {
835        frame.push_conic_gradient(
836            PxRect::from_size(self.data.size),
837            self.data.center,
838            self.angle.get(),
839            &self.data.stops,
840            self.extend_mode.get().into(),
841            PxPoint::zero(),
842            self.data.size,
843            PxSize::zero(),
844        );
845    }
846}
847impl UiNodeImpl for TiledConicGradient {
848    fn children_len(&self) -> usize {
849        0
850    }
851    fn with_child(&mut self, _: usize, _: &mut dyn FnMut(&mut UiNode)) {}
852
853    fn init(&mut self) {
854        WIDGET
855            .sub_var_layout(&self.center)
856            .sub_var_layout(&self.angle)
857            .sub_var(&self.stops)
858            .sub_var(&self.extend_mode)
859            .sub_var_layout(&self.tile_origin)
860            .sub_var_layout(&self.tile_size)
861            .sub_var_layout(&self.tile_spacing);
862    }
863
864    fn update(&mut self, _: &WidgetUpdates) {
865        if self.stops.is_new() || self.extend_mode.is_new() {
866            WIDGET.layout().render();
867        }
868    }
869
870    fn measure(&mut self, _: &mut WidgetMeasure) -> PxSize {
871        LAYOUT.constraints().fill_size()
872    }
873
874    fn layout(&mut self, _: &mut WidgetLayout) -> PxSize {
875        let size = LAYOUT.constraints().fill_size();
876        let tile_size = self.tile_size.layout_dft(size);
877
878        let mut request_render = size != self.data.size || tile_size != self.tile_data.size;
879
880        self.data.size = size;
881        self.tile_data.size = tile_size;
882
883        LAYOUT.with_constraints(PxConstraints2d::new_exact_size(tile_size), || {
884            let leftover = tile_leftover(tile_size, size);
885            LAYOUT.with_leftover(Some(leftover.width), Some(leftover.height), || {
886                let spacing = self.tile_spacing.layout();
887                request_render |= self.tile_data.spacing != spacing;
888                self.tile_data.spacing = spacing;
889            });
890            let center = self.center.get().layout_dft(tile_size.to_vector().to_point() * 0.5.fct());
891            let origin = self.tile_origin.layout();
892            request_render |= self.data.center != center || self.tile_data.origin != origin;
893            self.data.center = center;
894            self.tile_data.origin = origin;
895        });
896
897        let perimeter = Px({
898            let a = self.tile_data.size.width.0 as f32;
899            let b = self.tile_data.size.height.0 as f32;
900            std::f32::consts::PI * 2.0 * ((a * a + b * b) / 2.0).sqrt()
901        } as _);
902        LAYOUT.with_constraints(LAYOUT.constraints().with_exact_x(perimeter), || {
903            self.stops
904                .with(|s| s.layout_radial(LayoutAxis::X, self.extend_mode.get(), &mut self.data.stops))
905        });
906
907        if request_render {
908            WIDGET.render();
909        }
910
911        size
912    }
913
914    fn render(&mut self, frame: &mut FrameBuilder) {
915        frame.push_conic_gradient(
916            PxRect::from_size(self.data.size),
917            self.data.center,
918            self.angle.get(),
919            &self.data.stops,
920            self.extend_mode.get().into(),
921            self.tile_data.origin,
922            self.tile_data.size,
923            self.tile_data.spacing,
924        );
925    }
926}
927
928/// Node that fills the widget area with a color.
929///
930/// Note that this node is not a full widget, it can be used as part of a widget without adding to the info tree.
931pub fn flood(color: impl IntoVar<Rgba>) -> UiNode {
932    let color = color.into_var();
933    let mut render_size = PxSize::zero();
934    let frame_key = FrameValueKey::new_unique();
935
936    match_node_leaf(move |op| match op {
937        UiNodeOp::Init => {
938            WIDGET.sub_var_render_update(&color);
939        }
940        UiNodeOp::Measure { desired_size, .. } => {
941            *desired_size = LAYOUT.constraints().fill_size();
942        }
943        UiNodeOp::Layout { final_size, .. } => {
944            *final_size = LAYOUT.constraints().fill_size();
945            if *final_size != render_size {
946                render_size = *final_size;
947                WIDGET.render();
948            }
949        }
950        UiNodeOp::Render { frame } => {
951            if !render_size.is_empty() {
952                frame.push_color(PxRect::from_size(render_size), frame_key.bind_var(&color, |&c| c));
953            }
954        }
955        UiNodeOp::RenderUpdate { update } => {
956            if !render_size.is_empty() {
957                update.update_color_opt(frame_key.update_var(&color, |&c| c));
958            }
959        }
960        _ => {}
961    })
962}
963
964fn tile_leftover(tile_size: PxSize, wgt_size: PxSize) -> PxSize {
965    if tile_size.is_empty() || wgt_size.is_empty() {
966        return PxSize::zero();
967    }
968
969    let full_leftover_x = wgt_size.width % tile_size.width;
970    let full_leftover_y = wgt_size.height % tile_size.height;
971    let full_tiles_x = wgt_size.width / tile_size.width;
972    let full_tiles_y = wgt_size.height / tile_size.height;
973    let spaces_x = full_tiles_x - Px(1);
974    let spaces_y = full_tiles_y - Px(1);
975    PxSize::new(
976        if spaces_x > Px(0) { full_leftover_x / spaces_x } else { Px(0) },
977        if spaces_y > Px(0) { full_leftover_y / spaces_y } else { Px(0) },
978    )
979}