zng_app/widget/
border.rs

1//! Border and line types.
2
3use std::{fmt, mem, sync::Arc};
4
5use zng_app_context::context_local;
6use zng_color::{Hsla, Hsva, Rgba, colors};
7use zng_layout::{
8    context::{LAYOUT, LayoutMask},
9    unit::{
10        Factor, FactorPercent, FactorSideOffsets, FactorUnits, Layout2d, Length, PxCornerRadius, PxPoint, PxRect, PxSideOffsets, PxSize,
11        Size,
12    },
13};
14use zng_var::{
15    Var,
16    animation::{Transitionable, easing::EasingStep},
17    context_var, impl_from_and_into_var,
18};
19
20pub use zng_view_api::LineOrientation;
21
22use crate::widget::VarLayout;
23
24use super::{WIDGET, WidgetId, info::WidgetBorderInfo};
25
26/// Represents a line style.
27#[derive(Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize)]
28pub enum LineStyle {
29    /// A solid line.
30    Solid,
31    /// Two solid lines in parallel.
32    Double,
33
34    /// Dotted line.
35    Dotted,
36    /// Dashed line.
37    Dashed,
38
39    /// Faux shadow with carved appearance.
40    Groove,
41    /// Faux shadow with extruded appearance.
42    Ridge,
43
44    /// A wavy line, like an error underline.
45    ///
46    /// The wave magnitude is defined by the overall line thickness, the associated value
47    /// here defines the thickness of the wavy line.
48    Wavy(f32),
49
50    /// Fully transparent line.
51    ///
52    /// Note that the line space is still reserved, this is will have the same effect as `Solid` with a fully
53    /// transparent color.
54    Hidden,
55}
56impl fmt::Debug for LineStyle {
57    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
58        if f.alternate() {
59            write!(f, "LineStyle::")?;
60        }
61        match self {
62            LineStyle::Solid => write!(f, "Solid"),
63            LineStyle::Double => write!(f, "Double"),
64            LineStyle::Dotted => write!(f, "Dotted"),
65            LineStyle::Dashed => write!(f, "Dashed"),
66            LineStyle::Groove => write!(f, "Groove"),
67            LineStyle::Ridge => write!(f, "Ridge"),
68            LineStyle::Wavy(t) => write!(f, "Wavy({t})"),
69            LineStyle::Hidden => write!(f, "Hidden"),
70        }
71    }
72}
73impl Transitionable for LineStyle {
74    fn lerp(self, to: &Self, step: EasingStep) -> Self {
75        match (self, *to) {
76            (Self::Wavy(a), Self::Wavy(b)) => Self::Wavy(a.lerp(&b, step)),
77            (a, b) => {
78                if step < 1.fct() {
79                    a
80                } else {
81                    b
82                }
83            }
84        }
85    }
86}
87
88/// The line style for the sides of a widget's border.
89#[repr(u8)]
90#[derive(Clone, Copy, PartialEq, Hash, Eq, serde::Serialize, serde::Deserialize)]
91pub enum BorderStyle {
92    /// Displays a single, straight, solid line.
93    Solid = 1,
94    /// Displays two straight lines that add up to the pixel size defined by the side width.
95    Double = 2,
96
97    /// Displays a series of rounded dots.
98    Dotted = 3,
99    /// Displays a series of short square-ended dashes or line segments.
100    Dashed = 4,
101
102    /// Fully transparent line.
103    Hidden = 5,
104
105    /// Displays a border with a carved appearance.
106    Groove = 6,
107    /// Displays a border with an extruded appearance.
108    Ridge = 7,
109
110    /// Displays a border that makes the widget appear embedded.
111    Inset = 8,
112    /// Displays a border that makes the widget appear embossed.
113    Outset = 9,
114}
115impl fmt::Debug for BorderStyle {
116    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
117        if f.alternate() {
118            write!(f, "BorderStyle::")?;
119        }
120        match self {
121            BorderStyle::Solid => write!(f, "Solid"),
122            BorderStyle::Double => write!(f, "Double"),
123            BorderStyle::Dotted => write!(f, "Dotted"),
124            BorderStyle::Dashed => write!(f, "Dashed"),
125            BorderStyle::Groove => write!(f, "Groove"),
126            BorderStyle::Ridge => write!(f, "Ridge"),
127            BorderStyle::Hidden => write!(f, "Hidden"),
128            BorderStyle::Inset => write!(f, "Inset"),
129            BorderStyle::Outset => write!(f, "Outset"),
130        }
131    }
132}
133impl From<BorderStyle> for zng_view_api::BorderStyle {
134    fn from(s: BorderStyle) -> Self {
135        match s {
136            BorderStyle::Solid => zng_view_api::BorderStyle::Solid,
137            BorderStyle::Double => zng_view_api::BorderStyle::Double,
138            BorderStyle::Dotted => zng_view_api::BorderStyle::Dotted,
139            BorderStyle::Dashed => zng_view_api::BorderStyle::Dashed,
140            BorderStyle::Hidden => zng_view_api::BorderStyle::Hidden,
141            BorderStyle::Groove => zng_view_api::BorderStyle::Groove,
142            BorderStyle::Ridge => zng_view_api::BorderStyle::Ridge,
143            BorderStyle::Inset => zng_view_api::BorderStyle::Inset,
144            BorderStyle::Outset => zng_view_api::BorderStyle::Outset,
145        }
146    }
147}
148impl Transitionable for BorderStyle {
149    /// Returns `self` for `step < 1.fct()` or `to` for `step >= 1.fct()`.
150    fn lerp(self, to: &Self, step: EasingStep) -> Self {
151        if step < 1.fct() { self } else { *to }
152    }
153}
154
155/// The line style and color for the sides of a widget's border.
156#[repr(C)]
157#[derive(Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize, Transitionable)]
158pub struct BorderSide {
159    /// Line color.
160    pub color: Rgba,
161    /// Line style.
162    pub style: BorderStyle,
163}
164impl fmt::Debug for BorderSide {
165    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
166        if f.alternate() {
167            f.debug_struct("BorderSide")
168                .field("color", &self.color)
169                .field("style", &self.style)
170                .finish()
171        } else {
172            if let BorderStyle::Hidden = self.style {
173                if self.color.alpha.abs() < 0.0001 {
174                    return write!(f, "Hidden");
175                }
176            }
177            write!(f, "({:?}, {:?})", self.color, self.style)
178        }
179    }
180}
181impl BorderSide {
182    /// New border side from color and style value.
183    pub fn new<C: Into<Rgba>, S: Into<BorderStyle>>(color: C, style: S) -> Self {
184        BorderSide {
185            color: color.into(),
186            style: style.into(),
187        }
188    }
189
190    /// New border side with [`Solid`](BorderStyle::Solid) style.
191    pub fn solid<C: Into<Rgba>>(color: C) -> Self {
192        Self::new(color, BorderStyle::Solid)
193    }
194    /// New border side with [`Double`](BorderStyle::Double) style.
195    pub fn double<C: Into<Rgba>>(color: C) -> Self {
196        Self::new(color, BorderStyle::Double)
197    }
198
199    /// New border side with [`Dotted`](BorderStyle::Dotted) style.
200    pub fn dotted<C: Into<Rgba>>(color: C) -> Self {
201        Self::new(color, BorderStyle::Dotted)
202    }
203    /// New border side with [`Dashed`](BorderStyle::Dashed) style.
204    pub fn dashed<C: Into<Rgba>>(color: C) -> Self {
205        Self::new(color, BorderStyle::Dashed)
206    }
207
208    /// New border side with [`Groove`](BorderStyle::Groove) style.
209    pub fn groove<C: Into<Rgba>>(color: C) -> Self {
210        Self::new(color, BorderStyle::Groove)
211    }
212    /// New border side with [`Ridge`](BorderStyle::Ridge) style.
213    pub fn ridge<C: Into<Rgba>>(color: C) -> Self {
214        Self::new(color, BorderStyle::Ridge)
215    }
216
217    /// New border side with [`Inset`](BorderStyle::Inset) style.
218    pub fn inset<C: Into<Rgba>>(color: C) -> Self {
219        Self::new(color, BorderStyle::Inset)
220    }
221
222    /// New border side with [`Outset`](BorderStyle::Outset) style.
223    pub fn outset<C: Into<Rgba>>(color: C) -> Self {
224        Self::new(color, BorderStyle::Outset)
225    }
226
227    /// New border side with [`Hidden`](BorderStyle::Hidden) style and transparent color.
228    pub fn hidden() -> Self {
229        Self::new(colors::BLACK.transparent(), BorderStyle::Hidden)
230    }
231}
232impl From<BorderSide> for zng_view_api::BorderSide {
233    fn from(s: BorderSide) -> Self {
234        zng_view_api::BorderSide {
235            color: s.color,
236            style: s.style.into(),
237        }
238    }
239}
240impl Default for BorderSide {
241    /// Returns [`hidden`](BorderSide::hidden).
242    fn default() -> Self {
243        Self::hidden()
244    }
245}
246
247/// Radius of each corner of a border defined from [`Size`] values.
248///
249/// [`Size`]: zng_layout::unit::Size
250#[derive(Clone, Default, PartialEq, serde::Serialize, serde::Deserialize, Transitionable)]
251pub struct CornerRadius {
252    /// Top-left corner.
253    pub top_left: Size,
254    /// Top-right corner.
255    pub top_right: Size,
256    /// Bottom-right corner.
257    pub bottom_right: Size,
258    /// Bottom-left corner.
259    pub bottom_left: Size,
260}
261impl fmt::Debug for CornerRadius {
262    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
263        if f.alternate() {
264            f.debug_struct("BorderRadius")
265                .field("top_left", &self.top_left)
266                .field("top_right", &self.top_right)
267                .field("bottom_right", &self.bottom_right)
268                .field("bottom_left", &self.bottom_left)
269                .finish()
270        } else if self.all_corners_eq() {
271            write!(f, "{:?}", self.top_left)
272        } else {
273            write!(
274                f,
275                "({:?}, {:?}, {:?}, {:?})",
276                self.top_left, self.top_right, self.bottom_right, self.bottom_left
277            )
278        }
279    }
280}
281impl CornerRadius {
282    /// New every corner unique.
283    pub fn new<TL: Into<Size>, TR: Into<Size>, BR: Into<Size>, BL: Into<Size>>(
284        top_left: TL,
285        top_right: TR,
286        bottom_right: BR,
287        bottom_left: BL,
288    ) -> Self {
289        CornerRadius {
290            top_left: top_left.into(),
291            top_right: top_right.into(),
292            bottom_right: bottom_right.into(),
293            bottom_left: bottom_left.into(),
294        }
295    }
296
297    /// New all corners the same.
298    pub fn new_all<E: Into<Size>>(ellipse: E) -> Self {
299        let e = ellipse.into();
300        CornerRadius {
301            top_left: e.clone(),
302            top_right: e.clone(),
303            bottom_left: e.clone(),
304            bottom_right: e,
305        }
306    }
307
308    /// No corner radius.
309    pub fn zero() -> Self {
310        Self::new_all(Size::zero())
311    }
312
313    /// If all corners are the same value.
314    pub fn all_corners_eq(&self) -> bool {
315        self.top_left == self.top_right && self.top_left == self.bottom_right && self.top_left == self.bottom_left
316    }
317}
318impl Layout2d for CornerRadius {
319    type Px = PxCornerRadius;
320
321    fn layout_dft(&self, default: Self::Px) -> Self::Px {
322        PxCornerRadius {
323            top_left: self.top_left.layout_dft(default.top_left),
324            top_right: self.top_right.layout_dft(default.top_right),
325            bottom_left: self.bottom_left.layout_dft(default.bottom_left),
326            bottom_right: self.bottom_right.layout_dft(default.bottom_right),
327        }
328    }
329
330    fn affect_mask(&self) -> LayoutMask {
331        self.top_left.affect_mask() | self.top_right.affect_mask() | self.bottom_left.affect_mask() | self.bottom_right.affect_mask()
332    }
333}
334impl_from_and_into_var! {
335    /// All corners same.
336    fn from(all: Size) -> CornerRadius {
337        CornerRadius::new_all(all)
338    }
339    /// All corners same length.
340    fn from(all: Length) -> CornerRadius {
341        CornerRadius::new_all(all)
342    }
343
344    /// All corners same relative length.
345    fn from(percent: FactorPercent) -> CornerRadius {
346        CornerRadius::new_all(percent)
347    }
348    /// All corners same relative length.
349    fn from(norm: Factor) -> CornerRadius {
350        CornerRadius::new_all(norm)
351    }
352
353    /// All corners same exact length.
354    fn from(f: f32) -> CornerRadius {
355        CornerRadius::new_all(f)
356    }
357    /// All corners same exact length.
358    fn from(i: i32) -> CornerRadius {
359        CornerRadius::new_all(i)
360    }
361
362    /// (top-left, top-right, bottom-left, bottom-right) corners.
363    fn from<TL: Into<Size>, TR: Into<Size>, BR: Into<Size>, BL: Into<Size>>(
364        (top_left, top_right, bottom_right, bottom_left): (TL, TR, BR, BL),
365    ) -> CornerRadius {
366        CornerRadius::new(top_left, top_right, bottom_right, bottom_left)
367    }
368
369    /// From layout corner-radius.
370    fn from(corner_radius: PxCornerRadius) -> CornerRadius {
371        CornerRadius::new(
372            corner_radius.top_left,
373            corner_radius.top_right,
374            corner_radius.bottom_right,
375            corner_radius.bottom_left,
376        )
377    }
378}
379
380/// The line style and color for each side of a widget's border.
381#[derive(Clone, Copy, PartialEq, serde::Serialize, serde::Deserialize, Transitionable)]
382pub struct BorderSides {
383    /// Color and style of the left border.
384    pub left: BorderSide,
385    /// Color and style of the right border.
386    pub right: BorderSide,
387
388    /// Color and style of the top border.
389    pub top: BorderSide,
390    /// Color and style of the bottom border.
391    pub bottom: BorderSide,
392}
393impl fmt::Debug for BorderSides {
394    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
395        if f.alternate() {
396            f.debug_struct("BorderSides")
397                .field("left", &self.left)
398                .field("right", &self.right)
399                .field("top", &self.top)
400                .field("bottom", &self.bottom)
401                .finish()
402        } else if self.all_eq() {
403            write!(f, "{:?}", self.top)
404        } else if self.dimensions_eq() {
405            write!(f, "({:?}, {:?})", self.top, self.left)
406        } else {
407            write!(f, "({:?}, {:?}, {:?}, {:?})", self.top, self.right, self.bottom, self.left)
408        }
409    }
410}
411impl BorderSides {
412    /// All sides equal.
413    pub fn new_all<S: Into<BorderSide>>(side: S) -> Self {
414        let side = side.into();
415        BorderSides {
416            left: side,
417            right: side,
418            top: side,
419            bottom: side,
420        }
421    }
422
423    /// Top-bottom and left-right equal.
424    pub fn new_vh<TB: Into<BorderSide>, LR: Into<BorderSide>>(top_bottom: TB, left_right: LR) -> Self {
425        let top_bottom = top_bottom.into();
426        let left_right = left_right.into();
427        BorderSides {
428            left: left_right,
429            right: left_right,
430            top: top_bottom,
431            bottom: top_bottom,
432        }
433    }
434
435    /// New top, right, bottom left.
436    pub fn new<T: Into<BorderSide>, R: Into<BorderSide>, B: Into<BorderSide>, L: Into<BorderSide>>(
437        top: T,
438        right: R,
439        bottom: B,
440        left: L,
441    ) -> Self {
442        BorderSides {
443            left: left.into(),
444            right: right.into(),
445            top: top.into(),
446            bottom: bottom.into(),
447        }
448    }
449
450    /// New top only, other sides hidden.
451    pub fn new_top<T: Into<BorderSide>>(top: T) -> Self {
452        Self::new(top, BorderSide::hidden(), BorderSide::hidden(), BorderSide::hidden())
453    }
454
455    /// New right only, other sides hidden.
456    pub fn new_right<R: Into<BorderSide>>(right: R) -> Self {
457        Self::new(BorderSide::hidden(), right, BorderSide::hidden(), BorderSide::hidden())
458    }
459
460    /// New bottom only, other sides hidden.
461    pub fn new_bottom<B: Into<BorderSide>>(bottom: B) -> Self {
462        Self::new(BorderSide::hidden(), BorderSide::hidden(), bottom, BorderSide::hidden())
463    }
464
465    /// New left only, other sides hidden.
466    pub fn new_left<L: Into<BorderSide>>(left: L) -> Self {
467        Self::new(BorderSide::hidden(), BorderSide::hidden(), BorderSide::hidden(), left)
468    }
469
470    /// All sides a solid color.
471    pub fn solid<C: Into<Rgba>>(color: C) -> Self {
472        Self::new_all(BorderSide::solid(color))
473    }
474    /// All sides a double line solid color.
475    pub fn double<C: Into<Rgba>>(color: C) -> Self {
476        Self::new_all(BorderSide::double(color))
477    }
478
479    /// All sides a dotted color.
480    pub fn dotted<C: Into<Rgba>>(color: C) -> Self {
481        Self::new_all(BorderSide::dotted(color))
482    }
483    /// All sides a dashed color.
484    pub fn dashed<C: Into<Rgba>>(color: C) -> Self {
485        Self::new_all(BorderSide::dashed(color))
486    }
487
488    /// All sides a grooved color.
489    pub fn groove<C: Into<Rgba>>(color: C) -> Self {
490        Self::new_all(BorderSide::groove(color))
491    }
492    /// All sides a ridged color.
493    pub fn ridge<C: Into<Rgba>>(color: C) -> Self {
494        Self::new_all(BorderSide::ridge(color))
495    }
496
497    /// All sides a inset color.
498    pub fn inset<C: Into<Rgba>>(color: C) -> Self {
499        Self::new_all(BorderSide::inset(color))
500    }
501    /// All sides a outset color.
502    pub fn outset<C: Into<Rgba>>(color: C) -> Self {
503        Self::new_all(BorderSide::outset(color))
504    }
505
506    /// All sides hidden.
507    pub fn hidden() -> Self {
508        Self::new_all(BorderSide::hidden())
509    }
510
511    /// If all sides are equal.
512    pub fn all_eq(&self) -> bool {
513        self.top == self.bottom && self.top == self.left && self.top == self.right
514    }
515
516    /// If top and bottom are equal; and left and right are equal.
517    pub fn dimensions_eq(&self) -> bool {
518        self.top == self.bottom && self.left == self.right
519    }
520}
521impl Default for BorderSides {
522    /// Returns [`hidden`](BorderSides::hidden).
523    fn default() -> Self {
524        Self::hidden()
525    }
526}
527
528impl_from_and_into_var! {
529    /// Solid color.
530    fn from(color: Rgba) -> BorderSide {
531        BorderSide::solid(color)
532    }
533    /// Solid color.
534    fn from(color: Hsva) -> BorderSide {
535        BorderSide::solid(color)
536    }
537    /// Solid color.
538    fn from(color: Hsla) -> BorderSide {
539        BorderSide::solid(color)
540    }
541    /// All sides solid color.
542    fn from(color: Rgba) -> BorderSides {
543        BorderSides::new_all(color)
544    }
545    /// All sides solid color.
546    fn from(color: Hsva) -> BorderSides {
547        BorderSides::new_all(color)
548    }
549    /// All sides solid color.
550    fn from(color: Hsla) -> BorderSides {
551        BorderSides::new_all(color)
552    }
553
554    /// Side transparent black with the style.
555    ///
556    /// This is only useful with [`BorderStyle::Hidden`] variant.
557    fn from(style: BorderStyle) -> BorderSide {
558        BorderSide::new(colors::BLACK.transparent(), style)
559    }
560    /// All sides transparent black with the style.
561    ///
562    /// This is only useful with [`BorderStyle::Hidden`] variant.
563    fn from(style: BorderStyle) -> BorderSides {
564        BorderSides::new_all(style)
565    }
566
567    /// (color, style) side.
568    fn from<C: Into<Rgba>, S: Into<BorderStyle>>((color, style): (C, S)) -> BorderSide {
569        BorderSide::new(color, style)
570    }
571
572    /// (color, style) sides.
573    fn from<C: Into<Rgba>, S: Into<BorderStyle>>((color, style): (C, S)) -> BorderSides {
574        BorderSides::new_all(BorderSide::new(color, style))
575    }
576
577    /// (top, right, bottom, left) sides.
578    fn from<T: Into<BorderSide>, R: Into<BorderSide>, B: Into<BorderSide>, L: Into<BorderSide>>(
579        (top, right, bottom, left): (T, R, B, L),
580    ) -> BorderSides {
581        BorderSides::new(top, right, bottom, left)
582    }
583
584    /// (top-bottom-color, left-right-color, style) sides.
585    fn from<TB: Into<Rgba>, LR: Into<Rgba>, S: Into<BorderStyle>>((top_bottom, left_right, style): (TB, LR, S)) -> BorderSides {
586        let style = style.into();
587        BorderSides::new_vh((top_bottom, style), (left_right, style))
588    }
589
590    /// (top-color, right-color, bottom-color, left-color, style) sides.
591    fn from<T: Into<Rgba>, R: Into<Rgba>, B: Into<Rgba>, L: Into<Rgba>, S: Into<BorderStyle>>(
592        (top, right, bottom, left, style): (T, R, B, L, S),
593    ) -> BorderSides {
594        let style = style.into();
595        BorderSides::new((top, style), (right, style), (bottom, style), (left, style))
596    }
597}
598
599/// Defines how the corner radius is computed for each usage.
600///
601/// Nesting borders with round corners need slightly different radius values to perfectly fit, the [`BORDER`]
602/// coordinator can adjusts the radius inside each border to match the inside curve of the border, this behavior is
603/// controlled by corner radius fit.
604#[derive(Clone, Copy, Default, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
605pub enum CornerRadiusFit {
606    /// Corner radius is computed for each usage.
607    None,
608    /// Corner radius is computed for the first usage in the widget, other usages are [deflated] by the widget border offsets.
609    ///
610    /// [deflated]: PxCornerRadius::deflate
611    Widget,
612    /// Corner radius is computed on the first usage in the window, other usages are [deflated] by the widget border offsets.
613    ///
614    /// This is the default value.
615    ///
616    /// [deflated]: PxCornerRadius::deflate
617    #[default]
618    Tree,
619}
620impl fmt::Debug for CornerRadiusFit {
621    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
622        if f.alternate() {
623            write!(f, "CornerRadiusFit::")?;
624        }
625        match self {
626            Self::None => write!(f, "None"),
627            Self::Widget => write!(f, "Widget"),
628            Self::Tree => write!(f, "Tree"),
629        }
630    }
631}
632
633context_var! {
634    /// How much a widget's border offsets affects the widget's fill content.
635    pub static BORDER_ALIGN_VAR: FactorSideOffsets = FactorSideOffsets::zero();
636
637    /// If the border is rendered over the child nodes.
638    pub static BORDER_OVER_VAR: bool = true;
639
640    /// Corner radius.
641    pub static CORNER_RADIUS_VAR: CornerRadius = CornerRadius::zero();
642
643    /// Corner radius fit.
644    pub static CORNER_RADIUS_FIT_VAR: CornerRadiusFit = CornerRadiusFit::default();
645}
646
647/// Coordinates nested borders and corner-radius.
648pub struct BORDER;
649impl BORDER {
650    /// Gets the accumulated border offsets on the outside of the current border set on the current widget.
651    ///
652    /// This is only valid to call during layout.
653    pub fn border_offsets(&self) -> PxSideOffsets {
654        let data = BORDER_DATA.get();
655        if data.widget_id == WIDGET.try_id() {
656            data.wgt_offsets
657        } else {
658            PxSideOffsets::zero()
659        }
660    }
661
662    /// Gets the accumulated border offsets including the current border.
663    pub fn inner_offsets(&self) -> PxSideOffsets {
664        let data = BORDER_DATA.get();
665        if data.widget_id == WIDGET.try_id() {
666            data.wgt_inner_offsets
667        } else {
668            PxSideOffsets::zero()
669        }
670    }
671
672    /// Gets the corner radius for the border at the current context.
673    ///
674    /// This value is influenced by [`CORNER_RADIUS_VAR`], [`CORNER_RADIUS_FIT_VAR`] and all contextual borders.
675    pub fn border_radius(&self) -> PxCornerRadius {
676        match CORNER_RADIUS_FIT_VAR.get() {
677            CornerRadiusFit::Tree => BORDER_DATA.get().border_radius(),
678            CornerRadiusFit::Widget => {
679                let data = BORDER_DATA.get();
680                if data.widget_id == Some(WIDGET.id()) {
681                    data.border_radius()
682                } else {
683                    CORNER_RADIUS_VAR.layout()
684                }
685            }
686            _ => CORNER_RADIUS_VAR.layout(),
687        }
688    }
689
690    /// Gets the corner radius for the inside of the current border at the current context.
691    pub fn inner_radius(&self) -> PxCornerRadius {
692        match CORNER_RADIUS_FIT_VAR.get() {
693            CornerRadiusFit::Tree => BORDER_DATA.get().inner_radius(),
694            CornerRadiusFit::Widget => {
695                let data = BORDER_DATA.get();
696                if data.widget_id == WIDGET.try_id() {
697                    data.inner_radius()
698                } else {
699                    CORNER_RADIUS_VAR.layout()
700                }
701            }
702            _ => CORNER_RADIUS_VAR.layout(),
703        }
704    }
705
706    /// Gets the corner radius for the outside of the outer border of the current widget.
707    pub fn outer_radius(&self) -> PxCornerRadius {
708        BORDER_DATA.get().corner_radius
709    }
710
711    /// Gets the bounds and corner radius for the widget fill content.
712    ///
713    /// Must be called during layout in FILL nesting group.
714    ///
715    /// This value is influenced by [`CORNER_RADIUS_VAR`], [`CORNER_RADIUS_FIT_VAR`] and [`BORDER_ALIGN_VAR`].
716    pub fn fill_bounds(&self) -> (PxRect, PxCornerRadius) {
717        let align = BORDER_ALIGN_VAR.get();
718
719        let fill_size = LAYOUT.constraints().fill_size();
720        let inner_offsets = self.inner_offsets();
721
722        if align == FactorSideOffsets::zero() {
723            let fill_size = PxSize::new(
724                fill_size.width + inner_offsets.horizontal(),
725                fill_size.height + inner_offsets.vertical(),
726            );
727            return (PxRect::from_size(fill_size), self.outer_radius());
728        } else if align == FactorSideOffsets::new_all(1.0.fct()) {
729            return (
730                PxRect::new(PxPoint::new(inner_offsets.left, inner_offsets.top), fill_size),
731                self.inner_radius(),
732            );
733        }
734
735        let outer = self.outer_radius();
736        let inner = self.inner_radius();
737
738        let b_align = FactorSideOffsets {
739            top: 1.0.fct() - align.top,
740            right: 1.0.fct() - align.right,
741            bottom: 1.0.fct() - align.bottom,
742            left: 1.0.fct() - align.left,
743        };
744        let bounds = PxRect {
745            origin: PxPoint::new(inner_offsets.left * (align.left), inner_offsets.top * align.top),
746            size: PxSize::new(
747                fill_size.width + inner_offsets.left * b_align.left + inner_offsets.right * b_align.right,
748                fill_size.height + inner_offsets.top * b_align.top + inner_offsets.bottom * b_align.bottom,
749            ),
750        };
751
752        let radius = PxCornerRadius {
753            top_left: PxSize::new(
754                outer.top_left.width.lerp(&inner.top_left.width, align.left),
755                outer.top_left.height.lerp(&inner.top_left.height, align.top),
756            ),
757            top_right: PxSize::new(
758                outer.top_right.width.lerp(&inner.top_right.width, align.right),
759                outer.top_right.height.lerp(&inner.top_right.height, align.top),
760            ),
761            bottom_left: PxSize::new(
762                outer.bottom_left.width.lerp(&inner.bottom_left.width, align.left),
763                outer.bottom_left.height.lerp(&inner.bottom_left.height, align.bottom),
764            ),
765            bottom_right: PxSize::new(
766                outer.bottom_right.width.lerp(&inner.bottom_right.width, align.right),
767                outer.bottom_right.height.lerp(&inner.bottom_right.height, align.bottom),
768            ),
769        };
770
771        (bounds, radius)
772    }
773
774    pub(super) fn with_inner(&self, f: impl FnOnce() -> PxSize) -> PxSize {
775        let mut data = BORDER_DATA.get_clone();
776        let border = WIDGET.border();
777        data.add_inner(&border);
778
779        BORDER_DATA.with_context(&mut Some(Arc::new(data)), || {
780            let corner_radius = BORDER.border_radius();
781            border.set_corner_radius(corner_radius);
782            border.set_offsets(PxSideOffsets::zero());
783            f()
784        })
785    }
786
787    /// Measure a border node, adding the `offsets` to the context for the `f` call.
788    pub fn measure_border(&self, offsets: PxSideOffsets, f: impl FnOnce() -> PxSize) -> PxSize {
789        let mut data = BORDER_DATA.get_clone();
790        data.add_offset(None, offsets);
791        BORDER_DATA.with_context(&mut Some(Arc::new(data)), f)
792    }
793
794    /// Measure a border node, adding the `offsets` to the context for the `f` call.
795    pub fn layout_border(&self, offsets: PxSideOffsets, f: impl FnOnce()) {
796        let mut data = BORDER_DATA.get_clone();
797        data.add_offset(Some(&WIDGET.border()), offsets);
798        BORDER_DATA.with_context(&mut Some(Arc::new(data)), f);
799    }
800
801    /// Indicates a boundary point where the [`CORNER_RADIUS_VAR`] backing context changes during layout.
802    ///
803    /// The variable must have been just rebound before this call, the `corner_radius` property implements this method.
804    ///
805    /// Note that the corner radius is not set during [`measure`].
806    ///
807    /// [`measure`]: crate::widget::node::UiNode::measure
808    pub fn with_corner_radius<R>(&self, f: impl FnOnce() -> R) -> R {
809        let mut data = BORDER_DATA.get_clone();
810        data.set_corner_radius();
811        BORDER_DATA.with_context(&mut Some(Arc::new(data)), f)
812    }
813
814    /// Gets the computed border rect and side offsets for the border visual.
815    ///
816    /// This is only valid to call in the border visual node during layout and render.
817    pub fn border_layout(&self) -> (PxRect, PxSideOffsets) {
818        BORDER_LAYOUT.get().unwrap_or_else(|| {
819            #[cfg(debug_assertions)]
820            tracing::error!("the `border_layout` is only available inside the layout and render methods of the border visual node");
821            (PxRect::zero(), PxSideOffsets::zero())
822        })
823    }
824
825    /// Sets the border layout for the context of `f`.
826    pub fn with_border_layout(&self, rect: PxRect, offsets: PxSideOffsets, f: impl FnOnce()) {
827        BORDER_LAYOUT.with_context(&mut Some(Arc::new(Some((rect, offsets)))), f)
828    }
829}
830
831context_local! {
832    static BORDER_DATA: BorderOffsetsData = BorderOffsetsData::default();
833    static BORDER_LAYOUT: Option<(PxRect, PxSideOffsets)> = None;
834}
835
836#[derive(Debug, Clone, Default)]
837struct BorderOffsetsData {
838    widget_id: Option<WidgetId>,
839    wgt_offsets: PxSideOffsets,
840    wgt_inner_offsets: PxSideOffsets,
841
842    eval_cr: bool,
843    corner_radius: PxCornerRadius,
844    cr_offsets: PxSideOffsets,
845    cr_inner_offsets: PxSideOffsets,
846}
847impl BorderOffsetsData {
848    /// Adds to the widget offsets, or start a new one.
849    ///
850    /// Computes a new `corner_radius` if fit is Widget and is in a new one.
851    fn add_offset(&mut self, layout_info: Option<&WidgetBorderInfo>, offset: PxSideOffsets) {
852        let widget_id = Some(WIDGET.id());
853        let is_wgt_start = self.widget_id != widget_id;
854        if is_wgt_start {
855            // changed widget, reset offsets, and maybe corner-radius too.
856            self.widget_id = widget_id;
857            self.wgt_offsets = PxSideOffsets::zero();
858            self.wgt_inner_offsets = PxSideOffsets::zero();
859            self.eval_cr |= layout_info.is_some() && matches!(CORNER_RADIUS_FIT_VAR.get(), CornerRadiusFit::Widget);
860        }
861        self.wgt_offsets = self.wgt_inner_offsets;
862        self.wgt_inner_offsets += offset;
863
864        if mem::take(&mut self.eval_cr) {
865            self.corner_radius = CORNER_RADIUS_VAR.layout();
866            self.cr_offsets = PxSideOffsets::zero();
867            self.cr_inner_offsets = PxSideOffsets::zero();
868        }
869        self.cr_offsets = self.cr_inner_offsets;
870        self.cr_inner_offsets += offset;
871
872        if let Some(border) = layout_info {
873            if is_wgt_start {
874                border.set_corner_radius(self.corner_radius);
875            }
876            border.set_offsets(self.wgt_inner_offsets);
877        }
878    }
879
880    fn add_inner(&mut self, layout_info: &WidgetBorderInfo) {
881        // ensure at least one "border" so that we have an up-to-date corner radius.
882        self.add_offset(Some(layout_info), PxSideOffsets::zero());
883    }
884
885    fn set_corner_radius(&mut self) {
886        self.eval_cr = matches!(CORNER_RADIUS_FIT_VAR.get(), CornerRadiusFit::Tree);
887    }
888
889    fn border_radius(&self) -> PxCornerRadius {
890        self.corner_radius.deflate(self.cr_offsets)
891    }
892
893    fn inner_radius(&self) -> PxCornerRadius {
894        self.corner_radius.deflate(self.cr_inner_offsets)
895    }
896}