zng_wgt/border_props.rs
1use zng_app::widget::border::{BORDER, BORDER_ALIGN_VAR, BORDER_OVER_VAR, CORNER_RADIUS_FIT_VAR, CORNER_RADIUS_VAR};
2
3use crate::prelude::*;
4
5/// Corner radius of widget and inner widgets.
6///
7/// The [`Default`] value is calculated to fit inside the parent widget corner curve, see [`corner_radius_fit`] for more details.
8///
9/// [`Default`]: zng_layout::unit::Length::Default
10/// [`corner_radius_fit`]: fn@corner_radius_fit
11#[property(CONTEXT, default(CORNER_RADIUS_VAR))]
12pub fn corner_radius(child: impl UiNode, radius: impl IntoVar<CornerRadius>) -> impl UiNode {
13 let child = match_node(child, move |child, op| {
14 if let UiNodeOp::Layout { wl, final_size } = op {
15 *final_size = BORDER.with_corner_radius(|| child.layout(wl));
16 }
17 });
18 with_context_var(child, CORNER_RADIUS_VAR, radius)
19}
20
21/// Defines how the [`corner_radius`] is computed for each usage.
22///
23/// Nesting borders with round corners need slightly different radius values to perfectly fit, the [`BORDER`]
24/// coordinator can adjusts the radius inside each border to match the inside curve of the border.
25///
26/// Sets the [`CORNER_RADIUS_FIT_VAR`].
27///
28/// [`corner_radius`]: fn@corner_radius
29/// [`BORDER`]: zng_app::widget::border::BORDER
30/// [`CORNER_RADIUS_FIT_VAR`]: zng_app::widget::border::CORNER_RADIUS_FIT_VAR
31#[property(CONTEXT, default(CORNER_RADIUS_FIT_VAR))]
32pub fn corner_radius_fit(child: impl UiNode, fit: impl IntoVar<CornerRadiusFit>) -> impl UiNode {
33 with_context_var(child, CORNER_RADIUS_FIT_VAR, fit)
34}
35
36/// Position of a widget borders in relation to the widget fill.
37///
38/// This property defines how much the widget's border offsets affect the layout of the fill content, by default
39/// (0%) the fill content stretchers *under* the borders and is clipped by the [`corner_radius`], in the other end
40/// of the scale (100%), the fill content is positioned *inside* the borders and clipped by the adjusted [`corner_radius`]
41/// that fits the insider of the inner most border.
42///
43/// Note that widget's child is always inside the borders, this property only affects the fill properties, like the background.
44///
45/// Fill property implementers, see [`fill_node`], a helper function for quickly implementing support for `border_align`.
46///
47/// Sets the [`BORDER_ALIGN_VAR`].
48///
49/// [`corner_radius`]: fn@corner_radius
50/// [`BORDER_ALIGN_VAR`]: zng_app::widget::border::BORDER_ALIGN_VAR
51#[property(CONTEXT, default(BORDER_ALIGN_VAR))]
52pub fn border_align(child: impl UiNode, align: impl IntoVar<FactorSideOffsets>) -> impl UiNode {
53 with_context_var(child, BORDER_ALIGN_VAR, align)
54}
55
56/// If the border is rendered over the fill and child visuals.
57///
58/// Is `true` by default, if set to `false` the borders will render under the fill. Note that
59/// this means the border will be occluded by the background if [`border_align`] is not set to `1.fct()`.
60///
61/// Sets the [`BORDER_OVER_VAR`].
62///
63/// [`border_align`]: fn@border_align
64/// [`BORDER_OVER_VAR`]: zng_app::widget::border::BORDER_OVER_VAR
65#[property(CONTEXT, default(BORDER_OVER_VAR))]
66pub fn border_over(child: impl UiNode, over: impl IntoVar<bool>) -> impl UiNode {
67 with_context_var(child, BORDER_OVER_VAR, over)
68}
69
70/// Border widths, color and style.
71///
72/// This property coordinates with any other border in the widget to fit inside or outside the
73/// other borders, it also works with the [`corner_radius`] property drawing round corners if configured.
74///
75/// This property disables inline layout for the widget.
76///
77/// # Examples
78///
79/// A border of width `1.dip()`, solid color `BLUE` in all border sides and corner radius `4.dip()`.
80///
81/// ```
82/// # zng_wgt::enable_widget_macros!();
83/// # use zng_wgt::Wgt;
84/// # use zng_wgt::{corner_radius, border};
85/// # use zng_color::colors;
86/// # fn main() {
87/// # let _ =
88/// Wgt! {
89/// border = 1, colors::BLUE;
90/// corner_radius = 4;
91/// }
92/// # ; }
93/// ```
94///
95/// A border that sets each border line to a different width `top: 1, right: 2, bottom: 3, left: 4`, each corner
96/// radius to a different size `top_left: 1x1, top_right: 2x2, bottom_right: 3x3, bottom_left: 4x4` and each border
97/// line to a different style and color.
98///
99/// ```
100/// # zng_wgt::enable_widget_macros!();
101/// # use zng_wgt::Wgt;
102/// # use zng_wgt::{corner_radius, border};
103/// # use zng_app::widget::border::{BorderSide, BorderSides};
104/// # use zng_color::colors;
105/// # fn main() {
106/// # let _ =
107/// Wgt! {
108/// border = {
109/// widths: (1, 2, 3, 4),
110/// sides: BorderSides::new(
111/// BorderSide::solid(colors::RED),
112/// BorderSide::dashed(colors::GREEN),
113/// BorderSide::dotted(colors::BLUE),
114/// BorderSide::double(colors::YELLOW),
115/// ),
116/// };
117/// corner_radius = (1, 2, 3, 4);
118/// }
119/// # ; }
120/// ```
121///
122/// ## Multiple Borders
123///
124/// The border fits in with other borders in the widget, in this example we declare a
125/// new border property by copying the signature of this one:
126///
127/// ```
128/// # use zng_wgt::prelude::*;
129/// #
130/// /// Another border property.
131/// #[property(BORDER, default(0, BorderStyle::Hidden))]
132/// pub fn my_border(
133/// child: impl UiNode,
134/// widths: impl IntoVar<SideOffsets>,
135/// sides: impl IntoVar<BorderSides>
136/// ) -> impl UiNode {
137/// zng_wgt::border(child, widths, sides)
138/// }
139/// #
140/// # fn main() { }
141/// ```
142///
143/// Now we can set two borders in the same widget:
144///
145/// ```
146/// # zng_wgt::enable_widget_macros!();
147/// # use zng_wgt::Wgt;
148/// # use zng_wgt::{corner_radius, border};
149/// # use zng_color::colors;
150/// # use zng_wgt::prelude::*;
151/// #
152/// # #[property(BORDER, default(0, BorderStyle::Hidden))]
153/// # pub fn my_border(child: impl UiNode, widths: impl IntoVar<SideOffsets>, sides: impl IntoVar<BorderSides>) -> impl UiNode {
154/// # zng_wgt::border(child, widths, sides)
155/// # }
156/// #
157/// # fn main() {
158/// # let _ =
159/// Wgt! {
160/// border = 4, colors::RED;
161/// my_border = 4, colors::GREEN;
162/// corner_radius = 8;
163/// }
164/// # ; }
165/// ```
166///
167/// This will render a `RED` border around a `GREEN` one, the inner border will fit perfectly inside the outer one,
168/// the `corner_radius` defines the outer radius, the inner radius is computed automatically to fit.
169///
170/// Note that because both borders have the same [`NestGroup::BORDER`] the position they are declared in the widget matters:
171///
172/// ```
173/// # zng_wgt::enable_widget_macros!();
174/// # use zng_wgt::Wgt;
175/// # use zng_wgt::{corner_radius, border};
176/// # use zng_color::colors;
177/// # use zng_wgt::prelude::*;
178/// #
179/// # #[property(BORDER, default(0, BorderStyle::Hidden))]
180/// # pub fn my_border(child: impl UiNode, widths: impl IntoVar<SideOffsets>, sides: impl IntoVar<BorderSides>) -> impl UiNode {
181/// # zng_wgt::border(child, widths, sides)
182/// # }
183/// #
184/// # fn main() {
185/// # let _ =
186/// Wgt! {
187/// my_border = 4, colors::GREEN;
188/// border = 4, colors::RED;
189/// corner_radius = 8;
190/// }
191/// # ; }
192/// ```
193///
194/// Now the `GREEN` border is around the `RED`.
195///
196/// You can adjust the nest group to cause a border to always be outside or inside:
197///
198/// ```
199/// # use zng_wgt::prelude::*;
200/// #
201/// /// Border that is always around the other borders.
202/// #[property(BORDER-1, default(0, BorderStyle::Hidden))]
203/// pub fn outside_border(child: impl UiNode, widths: impl IntoVar<SideOffsets>, sides: impl IntoVar<BorderSides>) -> impl UiNode {
204/// zng_wgt::border(child, widths, sides)
205/// }
206///
207/// /// Border that is always inside the other borders.
208/// #[property(BORDER+1, default(0, BorderStyle::Hidden))]
209/// pub fn inside_border(child: impl UiNode, widths: impl IntoVar<SideOffsets>, sides: impl IntoVar<BorderSides>) -> impl UiNode {
210/// zng_wgt::border(child, widths, sides)
211/// }
212/// #
213/// # fn main() { }
214/// ```
215///
216/// [`corner_radius`]: fn@corner_radius
217/// [`NestGroup::BORDER`]: zng_app::widget::builder::NestGroup::BORDER
218#[property(BORDER, default(0, BorderStyle::Hidden))]
219pub fn border(child: impl UiNode, widths: impl IntoVar<SideOffsets>, sides: impl IntoVar<BorderSides>) -> impl UiNode {
220 let sides = sides.into_var();
221 let mut corners = PxCornerRadius::zero();
222
223 border_node(
224 child,
225 widths,
226 match_node_leaf(move |op| match op {
227 UiNodeOp::Init => {
228 WIDGET.sub_var_render(&sides);
229 }
230 UiNodeOp::Measure { desired_size, .. } => {
231 *desired_size = LAYOUT.constraints().fill_size();
232 }
233 UiNodeOp::Layout { final_size, .. } => {
234 corners = BORDER.border_radius();
235 *final_size = LAYOUT.constraints().fill_size();
236 }
237 UiNodeOp::Render { frame } => {
238 let (rect, offsets) = BORDER.border_layout();
239 if !rect.size.is_empty() {
240 frame.push_border(rect, offsets, sides.get(), corners);
241 }
242 }
243 _ => {}
244 }),
245 )
246}