ribir_core/builtin_widgets/
box_decoration.rs1use crate::prelude::*;
2
3#[derive(SingleChild, Default, Clone, Query)]
5pub struct BoxDecoration {
6 pub background: Option<Brush>,
8 pub border: Option<Border>,
10 pub border_radius: Option<Radius>,
13}
14
15impl Declare for BoxDecoration {
16 type Builder = FatObj<()>;
17 #[inline]
18 fn declarer() -> Self::Builder { FatObj::new(()) }
19}
20
21#[derive(Debug, Default, Clone, PartialEq)]
22pub struct Border {
23 pub left: BorderSide,
24 pub right: BorderSide,
25 pub top: BorderSide,
26 pub bottom: BorderSide,
27}
28
29#[derive(Debug, Default, Clone, PartialEq, Lerp)]
30pub struct BorderSide {
31 pub color: Brush,
32 pub width: f32,
33}
34
35impl BorderSide {
36 #[inline]
37 pub fn new(width: f32, color: Brush) -> Self { Self { width, color } }
38}
39
40impl Render for BoxDecoration {
41 #[inline]
42 fn perform_layout(&self, clamp: BoxClamp, ctx: &mut LayoutCtx) -> Size {
43 ctx.assert_perform_single_child_layout(clamp)
44 }
45
46 fn paint(&self, ctx: &mut PaintingCtx) {
47 let size = ctx.box_size().unwrap();
48 if !size.is_empty() {
49 let rect = Rect::from_size(size);
50 let painter = ctx.painter();
51 if let Some(ref background) = self.background {
52 painter.set_brush(background.clone());
53 if let Some(radius) = &self.border_radius {
54 painter.rect_round(&rect, radius);
55 } else {
56 painter.rect(&rect);
57 }
58 painter.fill();
59 }
60 self.paint_border(painter, &rect);
61 }
62 }
63}
64
65impl BoxDecoration {
66 fn paint_border(&self, painter: &mut Painter, rect: &Rect) {
67 if self.border.is_none() {
68 return;
69 }
70 let border = self.border.as_ref().unwrap();
71 if let Some(radius) = &self.border_radius {
72 self.paint_round_border(painter, radius, border, rect);
73 } else {
74 self.paint_rect_border(painter, border, rect);
75 }
76 }
77
78 fn is_border_uniform(&self) -> bool {
79 self.border.as_ref().map_or(true, |border| {
80 border.top == border.left && border.top == border.right && border.top == border.bottom
81 })
82 }
83
84 fn paint_round_border(
85 &self, painter: &mut Painter, radius: &Radius, border: &Border, content_rect: &Rect,
86 ) {
87 assert!(self.is_border_uniform(), "radius can't be setted with different border");
88 let width_half = border.left.width / 2.;
89 let min_x = content_rect.min_x() + width_half;
90 let max_x = content_rect.max_x() - width_half;
91 let min_y = content_rect.min_y() + width_half;
92 let max_y = content_rect.max_y() - width_half;
93 let radius = Radius::new(
94 radius.top_left + width_half,
95 radius.top_right + width_half,
96 radius.bottom_left + width_half,
97 radius.bottom_right + width_half,
98 );
99
100 painter
101 .set_line_width(border.top.width)
102 .set_brush(border.top.color.clone());
103 painter.rect_round(
104 &Rect::new(Point::new(min_x, min_y), Size::new(max_x - min_x, max_y - min_y)),
105 &radius,
106 );
107 painter.stroke();
108 }
109
110 fn paint_rect_border(&self, painter: &mut Painter, border: &Border, content_rect: &Rect) {
111 let min_x = content_rect.min_x() - border.left.width;
112 let max_x = content_rect.max_x() + border.right.width;
113 let min_y = content_rect.min_y() - border.top.width;
114 let max_y = content_rect.max_y() + border.bottom.width;
115 let vertexs = [
116 Point::new(min_x, min_y), Point::new(max_x, min_y), Point::new(max_x, max_y), Point::new(min_x, max_y), ];
121 let edges = [(0, 1), (1, 2), (2, 3), (3, 0)];
122 let borders = [&border.top, &border.right, &border.bottom, &border.left];
123 let borders_offset = [
124 Size::new(0., border.top.width / 2.),
125 Size::new(-border.right.width / 2., 0.),
126 Size::new(0., -border.bottom.width / 2.),
127 Size::new(border.left.width / 2., 0.),
128 ];
129 edges
130 .iter()
131 .zip(borders.iter())
132 .zip(borders_offset.iter())
133 .for_each(|((edge, border), offset)| {
134 if border.is_visible() {
135 painter
136 .set_line_width(border.width)
137 .set_brush(border.color.clone());
138 painter.begin_path(vertexs[edge.0] + *offset);
139 painter.line_to(vertexs[edge.1] + *offset);
140 painter.end_path(false).stroke();
141 }
142 });
143 }
144}
145
146impl BorderSide {
147 fn is_visible(&self) -> bool {
148 match self.color {
149 Brush::Color(color) => color.alpha > 0 && self.width > 0.,
150 _ => true,
151 }
152 }
153}
154
155impl Border {
156 #[inline]
157 pub fn all(side: BorderSide) -> Self {
158 Self { left: side.clone(), right: side.clone(), top: side.clone(), bottom: side }
159 }
160
161 #[inline]
162 pub fn only_left(left: BorderSide) -> Self { Self { left, ..Default::default() } }
163
164 #[inline]
165 pub fn only_right(right: BorderSide) -> Self { Self { right, ..Default::default() } }
166
167 #[inline]
168 pub fn only_bottom(bottom: BorderSide) -> Self { Self { bottom, ..Default::default() } }
169
170 #[inline]
171 pub fn only_top(top: BorderSide) -> Self { Self { top, ..Default::default() } }
172
173 #[inline]
174 pub fn none() -> Self { Self { ..Default::default() } }
175}
176#[cfg(test)]
177mod tests {
178 use ribir_dev_helper::*;
179
180 use super::*;
181 use crate::test_helper::*;
182
183 #[test]
184 fn default_value_is_none() {
185 let dummy = std::mem::MaybeUninit::uninit();
186 let ctx: BuildCtx<'static> = unsafe { dummy.assume_init() };
188 let mut w = BoxDecoration::declarer().finish(&ctx);
189 let w = w.get_box_decoration_widget();
190
191 assert_eq!(w.read().border, None);
192 assert_eq!(w.read().border_radius, None);
193 assert_eq!(w.read().background, None);
194
195 std::mem::forget(ctx);
196 }
197
198 const SIZE: Size = Size::new(100., 100.);
199 fn with_border() -> impl WidgetBuilder {
200 fn_widget! {
201 @MockBox {
202 size: SIZE,
203 border: Border {
204 left: BorderSide::new(1., Color::BLACK.into()),
205 right: BorderSide::new(2., Color::BLACK.into()),
206 top: BorderSide::new(3., Color::BLACK.into()),
207 bottom: BorderSide::new(4., Color::BLACK.into()),
208 },
209 }
210 }
211 }
212 widget_layout_test!(
213 with_border,
214 { path = [0], width == 100., height == 100., }
215 { path = [0, 0], rect == ribir_geom::rect(0., 0., 100., 100.), }
216 );
217}