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 IntoUiNode, radius: impl IntoVar<CornerRadius>) -> 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 IntoUiNode, fit: impl IntoVar<CornerRadiusFit>) -> 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 IntoUiNode, align: impl IntoVar<FactorSideOffsets>) -> 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 IntoUiNode, over: impl IntoVar<bool>) -> 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(child: impl IntoUiNode, widths: impl IntoVar<SideOffsets>, sides: impl IntoVar<BorderSides>) -> UiNode {
133///     zng_wgt::border(child, widths, sides)
134/// }
135/// #
136/// # fn main() { }
137/// ```
138///
139/// Now we can set two borders in the same widget:
140///
141/// ```
142/// # zng_wgt::enable_widget_macros!();
143/// # use zng_wgt::Wgt;
144/// # use zng_wgt::{corner_radius, border};
145/// # use zng_color::colors;
146/// # use zng_wgt::prelude::*;
147/// #
148/// # #[property(BORDER, default(0, BorderStyle::Hidden))]
149/// # pub fn my_border(child: impl IntoUiNode, widths: impl IntoVar<SideOffsets>, sides: impl IntoVar<BorderSides>) -> UiNode {
150/// # zng_wgt::border(child, widths, sides)
151/// # }
152/// #
153/// # fn main() {
154/// # let _ =
155/// Wgt! {
156///     border = 4, colors::RED;
157///     my_border = 4, colors::GREEN;
158///     corner_radius = 8;
159/// }
160/// # ; }
161/// ```
162///
163/// This will render a `RED` border around a `GREEN` one, the inner border will fit perfectly inside the outer one,
164/// the `corner_radius` defines the outer radius, the inner radius is computed automatically to fit.
165///
166/// Note that because both borders have the same [`NestGroup::BORDER`] the position they are declared in the widget matters:
167///
168/// ```
169/// # zng_wgt::enable_widget_macros!();
170/// # use zng_wgt::Wgt;
171/// # use zng_wgt::{corner_radius, border};
172/// # use zng_color::colors;
173/// # use zng_wgt::prelude::*;
174/// #
175/// # #[property(BORDER, default(0, BorderStyle::Hidden))]
176/// # pub fn my_border(child: impl IntoUiNode, widths: impl IntoVar<SideOffsets>, sides: impl IntoVar<BorderSides>) -> UiNode {
177/// # zng_wgt::border(child, widths, sides)
178/// # }
179/// #
180/// # fn main() {
181/// # let _ =
182/// Wgt! {
183///     my_border = 4, colors::GREEN;
184///     border = 4, colors::RED;
185///     corner_radius = 8;
186/// }
187/// # ; }
188/// ```
189///
190/// Now the `GREEN` border is around the `RED`.
191///
192/// You can adjust the nest group to cause a border to always be outside or inside:
193///
194/// ```
195/// # use zng_wgt::prelude::*;
196/// #
197/// /// Border that is always around the other borders.
198/// #[property(BORDER-1, default(0, BorderStyle::Hidden))]
199/// pub fn outside_border(child: impl IntoUiNode, widths: impl IntoVar<SideOffsets>, sides: impl IntoVar<BorderSides>) -> UiNode {
200///     zng_wgt::border(child, widths, sides)
201/// }
202///
203/// /// Border that is always inside the other borders.
204/// #[property(BORDER+1, default(0, BorderStyle::Hidden))]
205/// pub fn inside_border(child: impl IntoUiNode, widths: impl IntoVar<SideOffsets>, sides: impl IntoVar<BorderSides>) -> UiNode {
206///     zng_wgt::border(child, widths, sides)
207/// }
208/// #
209/// # fn main() { }
210/// ```
211///
212/// [`corner_radius`]: fn@corner_radius
213/// [`NestGroup::BORDER`]: zng_app::widget::builder::NestGroup::BORDER
214#[property(BORDER, default(0, BorderStyle::Hidden))]
215pub fn border(child: impl IntoUiNode, widths: impl IntoVar<SideOffsets>, sides: impl IntoVar<BorderSides>) -> UiNode {
216    let sides = sides.into_var();
217    let mut corners = PxCornerRadius::zero();
218
219    border_node(
220        child,
221        widths,
222        match_node_leaf(move |op| match op {
223            UiNodeOp::Init => {
224                WIDGET.sub_var_render(&sides);
225            }
226            UiNodeOp::Measure { desired_size, .. } => {
227                *desired_size = LAYOUT.constraints().fill_size();
228            }
229            UiNodeOp::Layout { final_size, .. } => {
230                corners = BORDER.border_radius();
231                *final_size = LAYOUT.constraints().fill_size();
232            }
233            UiNodeOp::Render { frame } => {
234                let (rect, offsets) = BORDER.border_layout();
235                if !rect.size.is_empty() {
236                    frame.push_border(rect, offsets, sides.get(), corners);
237                }
238            }
239            _ => {}
240        }),
241    )
242}