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