zng_wgt_fill/
lib.rs

1#![doc(html_favicon_url = "https://zng-ui.github.io/res/zng-logo-icon.png")]
2#![doc(html_logo_url = "https://zng-ui.github.io/res/zng-logo.png")]
3//!
4//! Properties that fill the widget inner bounds and nodes that fill the available space.
5//!
6//! # Crate
7//!
8#![doc = include_str!(concat!("../", std::env!("CARGO_PKG_README")))]
9#![warn(unused_extern_crates)]
10#![warn(missing_docs)]
11
12use zng_wgt::prelude::gradient::{GradientRadius, GradientStops, LinearGradientAxis, stops};
13use zng_wgt::{HitTestMode, hit_test_mode, node::interactive_node, prelude::*};
14
15pub mod node;
16
17/// Custom background. Allows using any other UI node as a background.
18///
19/// The `background` is not interactive, it is hit-testable only as a visual of the widget. The background
20/// is layout to fill the widget, it does not affect the size of the widget.
21///
22/// Note that nodes can only exist in a single place in the UI tree at a time, so if you set this property
23/// in a style the background node will only appear in the last widget that uses the style, the
24/// [`background_fn`](fn@background_fn) property does not have this issue.
25#[property(FILL)]
26pub fn background(child: impl IntoUiNode, background: impl IntoUiNode) -> UiNode {
27    let background = interactive_node(background, false);
28    let background = fill_node(background);
29
30    match_node(ui_vec![background, child], |children, op| match op {
31        UiNodeOp::Measure { wm, desired_size } => {
32            children.delegated();
33            *desired_size = children.node().with_child(1, |n| n.measure(wm));
34        }
35        UiNodeOp::Layout { wl, final_size } => {
36            children.delegated();
37            let size = children.node().with_child(1, |n| n.layout(wl));
38
39            LAYOUT.with_constraints(PxConstraints2d::new_exact_size(size), || {
40                children.node().with_child(0, |n| n.layout(wl));
41            });
42            *final_size = size;
43        }
44        _ => {}
45    })
46}
47
48/// Custom background generated using a [`WidgetFn<()>`].
49///
50/// This is the equivalent of setting [`background`] to the [`presenter`] node, but if the property is
51/// set in a style that is used by multiple widgets at the same time the `wgt_fn` will be called for each widget
52/// to create duplicates of the background nodes instead of moving the node to the last widget.
53///
54/// [`WidgetFn<()>`]: WidgetFn
55/// [`background`]: fn@background
56/// [`presenter`]: zng_wgt::prelude::presenter
57#[property(FILL, default(WidgetFn::nil()))]
58pub fn background_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<()>>) -> UiNode {
59    background(child, presenter((), wgt_fn))
60}
61
62/// Fill color background.
63///
64/// This property applies a [`node::flood`] as [`background`].
65///
66/// [`background`]: fn@background
67#[property(FILL, default(colors::BLACK.transparent()))]
68pub fn background_color(child: impl IntoUiNode, color: impl IntoVar<Rgba>) -> UiNode {
69    background(child, node::flood(color))
70}
71
72/// Linear gradient background.
73///
74/// This property applies a [`node::linear_gradient`] as [`background`].
75///
76/// [`background`]: fn@background
77#[property(FILL, default(0.deg(), {
78    let c = colors::BLACK.transparent();
79    stops![c, c]
80}))]
81pub fn background_gradient(child: impl IntoUiNode, axis: impl IntoVar<LinearGradientAxis>, stops: impl IntoVar<GradientStops>) -> UiNode {
82    background(child, node::linear_gradient(axis, stops))
83}
84
85/// Radial gradient background.
86///
87/// This property applies a [`node::radial_gradient`] as [`background`].
88///
89/// [`background`]: fn@background
90#[property(FILL, default((50.pct(), 50.pct()), 100.pct(), {
91    let c = colors::BLACK.transparent();
92    stops![c, c]
93}))]
94pub fn background_radial(
95    child: impl IntoUiNode,
96    center: impl IntoVar<Point>,
97    radius: impl IntoVar<GradientRadius>,
98    stops: impl IntoVar<GradientStops>,
99) -> UiNode {
100    background(child, node::radial_gradient(center, radius, stops))
101}
102
103/// Conic gradient background.
104///
105/// This property applies a [`node::conic_gradient`] as [`background`].
106///
107/// [`background`]: fn@background
108#[property(FILL, default((50.pct(), 50.pct()), 0.deg(), {
109    let c = colors::BLACK.transparent();
110    stops![c, c]
111}))]
112pub fn background_conic(
113    child: impl IntoUiNode,
114    center: impl IntoVar<Point>,
115    angle: impl IntoVar<AngleRadian>,
116    stops: impl IntoVar<GradientStops>,
117) -> UiNode {
118    background(child, node::conic_gradient(center, angle, stops))
119}
120
121/// Custom foreground fill. Allows using any other UI node as a foreground overlay.
122///
123/// The `foreground` is not interactive and not hit-testable.
124/// The foreground is layout to fill the widget, it does not affect the size of the widget. It is rendered over
125/// the widget child and background, it is rendered under borders by default.
126///
127/// Note that nodes can only exist in a single place in the UI tree at a time, so if you set this property in a style
128/// the foreground node will only appear in the last widget that uses the style, the [`foreground_fn`] property does not have this issue.
129///
130/// [`foreground_fn`]: fn@foreground_fn
131#[property(FILL, default(UiNode::nil()))]
132pub fn foreground(child: impl IntoUiNode, foreground: impl IntoUiNode) -> UiNode {
133    let foreground = interactive_node(foreground, false);
134    let foreground = fill_node(foreground);
135    let foreground = hit_test_mode(foreground, HitTestMode::Disabled);
136
137    match_node(ui_vec![child, foreground], |children, op| match op {
138        UiNodeOp::Measure { wm, desired_size } => {
139            children.delegated();
140            *desired_size = children.node().with_child(0, |n| n.measure(wm));
141        }
142        UiNodeOp::Layout { wl, final_size } => {
143            children.delegated();
144            let size = children.node().with_child(0, |n| n.layout(wl));
145            LAYOUT.with_constraints(PxConstraints2d::new_exact_size(size), || {
146                children.node().with_child(1, |n| n.layout(wl));
147            });
148            *final_size = size;
149        }
150        _ => {}
151    })
152}
153
154/// Custom foreground generated using a [`WidgetFn<()>`].
155///
156/// This is the equivalent of setting [`foreground`] to the [`presenter`] node, but if the property is set in a style that is used
157/// by multiple widgets at the same time the `wgt_fn` will be called for each widget to create duplicates of the foreground nodes
158/// instead of moving the node to the last widget.
159///
160/// [`WidgetFn<()>`]: WidgetFn
161/// [`foreground`]: fn@foreground
162/// [`presenter`]: zng_wgt::prelude::presenter
163#[property(FILL, default(WidgetFn::nil()))]
164pub fn foreground_fn(child: impl IntoUiNode, wgt_fn: impl IntoVar<WidgetFn<()>>) -> UiNode {
165    foreground(child, presenter((), wgt_fn))
166}
167
168/// Foreground highlight border overlay.
169///
170/// This property draws a border contour overlay that can be positioned using `offsets`.
171#[property(FILL, default(0, 0, BorderStyle::Hidden))]
172pub fn foreground_highlight(
173    child: impl IntoUiNode,
174    offsets: impl IntoVar<SideOffsets>,
175    widths: impl IntoVar<SideOffsets>,
176    sides: impl IntoVar<BorderSides>,
177) -> UiNode {
178    let offsets = offsets.into_var();
179    let widths = widths.into_var();
180    let sides = sides.into_var();
181
182    let mut render_bounds = PxRect::zero();
183    let mut render_widths = PxSideOffsets::zero();
184    let mut render_radius = PxCornerRadius::zero();
185
186    match_node(child, move |child, op| match op {
187        UiNodeOp::Init => {
188            WIDGET.sub_var_layout(&offsets).sub_var_layout(&widths).sub_var_render(&sides);
189        }
190        UiNodeOp::Layout { wl, final_size } => {
191            let size = child.layout(wl);
192
193            let radius = BORDER.inner_radius();
194            let offsets = offsets.layout();
195            let radius = radius.deflate(offsets);
196
197            let mut bounds = PxRect::zero();
198            if let Some(inline) = wl.inline()
199                && let Some(first) = inline.rows.iter().find(|r| !r.size.is_empty())
200            {
201                bounds = *first;
202            }
203            if bounds.size.is_empty() {
204                let border_offsets = BORDER.inner_offsets();
205
206                bounds = PxRect::new(
207                    PxPoint::new(offsets.left + border_offsets.left, offsets.top + border_offsets.top),
208                    size - PxSize::new(offsets.horizontal(), offsets.vertical()),
209                );
210            }
211
212            let widths = LAYOUT.with_constraints(PxConstraints2d::new_exact_size(size), || widths.layout());
213
214            if render_bounds != bounds || render_widths != widths || render_radius != radius {
215                render_bounds = bounds;
216                render_widths = widths;
217                render_radius = radius;
218                WIDGET.render();
219            }
220
221            *final_size = size;
222        }
223        UiNodeOp::Render { frame } => {
224            child.render(frame);
225            frame.push_border(render_bounds, render_widths, sides.get(), render_radius);
226        }
227        _ => {}
228    })
229}
230
231/// Fill color overlay.
232///
233/// This property applies a [`node::flood`] as [`foreground`].
234///
235/// [`foreground`]: fn@foreground
236#[property(FILL, default(colors::BLACK.transparent()))]
237pub fn foreground_color(child: impl IntoUiNode, color: impl IntoVar<Rgba>) -> UiNode {
238    foreground(child, node::flood(color))
239}
240
241/// Linear gradient overlay.
242///
243/// This property applies a [`node::linear_gradient`] as [`foreground`].
244///
245/// [`foreground`]: fn@foreground
246#[property(FILL, default(0.deg(), {
247    let c = colors::BLACK.transparent();
248    stops![c, c]
249}))]
250pub fn foreground_gradient(child: impl IntoUiNode, axis: impl IntoVar<LinearGradientAxis>, stops: impl IntoVar<GradientStops>) -> UiNode {
251    foreground(child, node::linear_gradient(axis, stops))
252}
253
254/// Radial gradient foreground.
255///
256/// This property applies a [`node::radial_gradient`] as [`foreground`].
257///
258/// [`foreground`]: fn@foreground
259#[property(FILL, default((50.pct(), 50.pct()), 100.pct(), {
260    let c = colors::BLACK.transparent();
261    stops![c, c]
262}))]
263pub fn foreground_radial(
264    child: impl IntoUiNode,
265    center: impl IntoVar<Point>,
266    radius: impl IntoVar<GradientRadius>,
267    stops: impl IntoVar<GradientStops>,
268) -> UiNode {
269    foreground(child, node::radial_gradient(center, radius, stops))
270}
271
272/// Conic gradient foreground.
273///
274/// This property applies a [`node::conic_gradient`] as [`foreground`].
275///
276/// [`foreground`]: fn@foreground
277#[property(FILL, default((50.pct(), 50.pct()), 0.deg(), {
278    let c = colors::BLACK.transparent();
279    stops![c, c]
280}))]
281pub fn foreground_conic(
282    child: impl IntoUiNode,
283    center: impl IntoVar<Point>,
284    angle: impl IntoVar<AngleRadian>,
285    stops: impl IntoVar<GradientStops>,
286) -> UiNode {
287    foreground(child, node::conic_gradient(center, angle, stops))
288}