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