Skip to main content

telar_ui_core/
border.rs

1//! The two box properties whose edges can be named by role rather than by side, resolved against the active
2//! writing direction.
3//!
4//! `start`/`end` mean here what `padding_start` means in layout: the edge the text comes from, and the one it
5//! runs towards. Layout resolves its own in a pass it already had; paint has no such pass, so the flip happens
6//! inside the style closure the renderer re-runs — which is also what makes a live LTR/RTL switch repaint
7//! instead of needing the tree rebuilt.
8
9use renderer_core::{BorderRadius, BorderWidths};
10
11use crate::context::use_direction;
12
13/// Per-side border widths where `start`/`end`, when given, land on left or right according to the writing
14/// direction.
15///
16/// A logical side overrides the physical one it lands on: an author who wrote both named this edge twice, and
17/// the name that describes its *role* is the one that was talking about the current layout.
18pub fn logical_border_widths(
19    top: f32,
20    right: f32,
21    bottom: f32,
22    left: f32,
23    start: Option<f32>,
24    end: Option<f32>,
25) -> BorderWidths {
26    let (mut left, mut right) = (left, right);
27    let rtl = use_direction().is_rtl();
28    if let Some(w) = start {
29        if rtl { right = w } else { left = w }
30    }
31    if let Some(w) = end {
32        if rtl { left = w } else { right = w }
33    }
34    BorderWidths::per_side(top, right, bottom, left)
35}
36
37/// Corner radii where `start`/`end`, when given, round the two corners on that edge.
38///
39/// A side rather than a corner, because that is the shape the property is actually for: a panel tucked against
40/// the rail is rounded on the two corners facing away from it, and under RTL it has to tuck against the other
41/// rail without the author writing the layout twice.
42pub fn logical_border_radius(
43    top_left: f32,
44    top_right: f32,
45    bottom_right: f32,
46    bottom_left: f32,
47    start: Option<f32>,
48    end: Option<f32>,
49) -> BorderRadius {
50    let mut r = BorderRadius {
51        top_left,
52        top_right,
53        bottom_right,
54        bottom_left,
55    };
56    let rtl = use_direction().is_rtl();
57    if let Some(v) = start {
58        if rtl {
59            (r.top_right, r.bottom_right) = (v, v);
60        } else {
61            (r.top_left, r.bottom_left) = (v, v);
62        }
63    }
64    if let Some(v) = end {
65        if rtl {
66            (r.top_left, r.bottom_left) = (v, v);
67        } else {
68            (r.top_right, r.bottom_right) = (v, v);
69        }
70    }
71    r
72}
73
74#[cfg(test)]
75mod tests {
76    use layout_core::Direction;
77
78    use super::*;
79    use crate::context::set_direction;
80
81    #[test]
82    fn a_logical_side_lands_on_the_edge_the_text_comes_from() {
83        set_direction(Direction::Ltr);
84        assert_eq!(
85            logical_border_widths(0.0, 0.0, 0.0, 0.0, Some(2.0), None),
86            BorderWidths::per_side(0.0, 0.0, 0.0, 2.0)
87        );
88        set_direction(Direction::Rtl);
89        assert_eq!(
90            logical_border_widths(0.0, 0.0, 0.0, 0.0, Some(2.0), None),
91            BorderWidths::per_side(0.0, 2.0, 0.0, 0.0)
92        );
93        set_direction(Direction::Ltr);
94    }
95
96    #[test]
97    fn a_physical_side_stays_where_it_was_put() {
98        for direction in [Direction::Ltr, Direction::Rtl] {
99            set_direction(direction);
100            assert_eq!(
101                logical_border_widths(0.0, 0.0, 0.0, 1.0, None, None),
102                BorderWidths::per_side(0.0, 0.0, 0.0, 1.0),
103                "{direction:?}"
104            );
105        }
106        set_direction(Direction::Ltr);
107    }
108
109    /// Both names for one edge: the role wins, since that is the one that knew which layout it was in.
110    #[test]
111    fn a_logical_side_overrides_the_physical_one_it_lands_on() {
112        set_direction(Direction::Ltr);
113        assert_eq!(
114            logical_border_widths(0.0, 0.0, 0.0, 1.0, Some(2.0), None),
115            BorderWidths::per_side(0.0, 0.0, 0.0, 2.0)
116        );
117        set_direction(Direction::Rtl);
118        assert_eq!(
119            logical_border_widths(0.0, 0.0, 0.0, 1.0, Some(2.0), None),
120            BorderWidths::per_side(0.0, 2.0, 0.0, 1.0),
121            "the left keeps what it was given: start went to the other edge"
122        );
123        set_direction(Direction::Ltr);
124    }
125
126    #[test]
127    fn a_logical_radius_rounds_both_corners_of_its_edge() {
128        set_direction(Direction::Ltr);
129        assert_eq!(
130            logical_border_radius(0.0, 0.0, 0.0, 0.0, Some(8.0), None),
131            BorderRadius {
132                top_left: 8.0,
133                top_right: 0.0,
134                bottom_right: 0.0,
135                bottom_left: 8.0,
136            }
137        );
138        set_direction(Direction::Rtl);
139        assert_eq!(
140            logical_border_radius(0.0, 0.0, 0.0, 0.0, Some(8.0), None),
141            BorderRadius {
142                top_left: 0.0,
143                top_right: 8.0,
144                bottom_right: 8.0,
145                bottom_left: 0.0,
146            }
147        );
148        set_direction(Direction::Ltr);
149    }
150}