1use pathfinder_geometry::rect::RectF;
2use serde_json::json;
3
4use crate::{
5 color::ColorU,
6 geometry::vector::{vec2f, Vector2F},
7 json::ToJson,
8 scene::{self, Border, Quad},
9 AfterLayoutContext, Element, ElementBox, Event, EventContext, LayoutContext, PaintContext,
10 SizeConstraint,
11};
12
13#[derive(Clone, Debug, Default)]
14pub struct ContainerStyle {
15 margin: Margin,
16 padding: Padding,
17 background_color: Option<ColorU>,
18 border: Border,
19 corner_radius: f32,
20 shadow: Option<Shadow>,
21}
22
23pub struct Container {
24 child: ElementBox,
25 style: ContainerStyle,
26}
27
28impl Container {
29 pub fn new(child: ElementBox) -> Self {
30 Self {
31 child,
32 style: Default::default(),
33 }
34 }
35
36 pub fn with_style(mut self, style: &ContainerStyle) -> Self {
37 self.style = style.clone();
38 self
39 }
40
41 pub fn with_margin_top(mut self, margin: f32) -> Self {
42 self.style.margin.top = margin;
43 self
44 }
45
46 pub fn with_margin_left(mut self, margin: f32) -> Self {
47 self.style.margin.left = margin;
48 self
49 }
50
51 pub fn with_horizontal_padding(mut self, padding: f32) -> Self {
52 self.style.padding.left = padding;
53 self.style.padding.right = padding;
54 self
55 }
56
57 pub fn with_vertical_padding(mut self, padding: f32) -> Self {
58 self.style.padding.top = padding;
59 self.style.padding.bottom = padding;
60 self
61 }
62
63 pub fn with_uniform_padding(mut self, padding: f32) -> Self {
64 self.style.padding = Padding {
65 top: padding,
66 left: padding,
67 bottom: padding,
68 right: padding,
69 };
70 self
71 }
72
73 pub fn with_padding_right(mut self, padding: f32) -> Self {
74 self.style.padding.right = padding;
75 self
76 }
77
78 pub fn with_padding_bottom(mut self, padding: f32) -> Self {
79 self.style.padding.bottom = padding;
80 self
81 }
82
83 pub fn with_background_color(mut self, color: impl Into<ColorU>) -> Self {
84 self.style.background_color = Some(color.into());
85 self
86 }
87
88 pub fn with_border(mut self, border: Border) -> Self {
89 self.style.border = border;
90 self
91 }
92
93 pub fn with_corner_radius(mut self, radius: f32) -> Self {
94 self.style.corner_radius = radius;
95 self
96 }
97
98 pub fn with_shadow(mut self, offset: Vector2F, blur: f32, color: impl Into<ColorU>) -> Self {
99 self.style.shadow = Some(Shadow {
100 offset,
101 blur,
102 color: color.into(),
103 });
104 self
105 }
106
107 fn margin_size(&self) -> Vector2F {
108 vec2f(
109 self.style.margin.left + self.style.margin.right,
110 self.style.margin.top + self.style.margin.bottom,
111 )
112 }
113
114 fn padding_size(&self) -> Vector2F {
115 vec2f(
116 self.style.padding.left + self.style.padding.right,
117 self.style.padding.top + self.style.padding.bottom,
118 )
119 }
120
121 fn border_size(&self) -> Vector2F {
122 let mut x = 0.0;
123 if self.style.border.left {
124 x += self.style.border.width;
125 }
126 if self.style.border.right {
127 x += self.style.border.width;
128 }
129
130 let mut y = 0.0;
131 if self.style.border.top {
132 y += self.style.border.width;
133 }
134 if self.style.border.bottom {
135 y += self.style.border.width;
136 }
137
138 vec2f(x, y)
139 }
140}
141
142impl Element for Container {
143 type LayoutState = ();
144 type PaintState = ();
145
146 fn layout(
147 &mut self,
148 constraint: SizeConstraint,
149 cx: &mut LayoutContext,
150 ) -> (Vector2F, Self::LayoutState) {
151 let size_buffer = self.margin_size() + self.padding_size() + self.border_size();
152 let child_constraint = SizeConstraint {
153 min: (constraint.min - size_buffer).max(Vector2F::zero()),
154 max: (constraint.max - size_buffer).max(Vector2F::zero()),
155 };
156 let child_size = self.child.layout(child_constraint, cx);
157 (child_size + size_buffer, ())
158 }
159
160 fn after_layout(
161 &mut self,
162 _: Vector2F,
163 _: &mut Self::LayoutState,
164 cx: &mut AfterLayoutContext,
165 ) {
166 self.child.after_layout(cx);
167 }
168
169 fn paint(
170 &mut self,
171 bounds: RectF,
172 _: &mut Self::LayoutState,
173 cx: &mut PaintContext,
174 ) -> Self::PaintState {
175 let quad_bounds = RectF::from_points(
176 bounds.origin() + vec2f(self.style.margin.left, self.style.margin.top),
177 bounds.lower_right() - vec2f(self.style.margin.right, self.style.margin.bottom),
178 );
179
180 if let Some(shadow) = self.style.shadow.as_ref() {
181 cx.scene.push_shadow(scene::Shadow {
182 bounds: quad_bounds + shadow.offset,
183 corner_radius: self.style.corner_radius,
184 sigma: shadow.blur,
185 color: shadow.color,
186 });
187 }
188 cx.scene.push_quad(Quad {
189 bounds: quad_bounds,
190 background: self.style.background_color,
191 border: self.style.border,
192 corner_radius: self.style.corner_radius,
193 });
194
195 let child_origin = quad_bounds.origin()
196 + vec2f(self.style.padding.left, self.style.padding.top)
197 + vec2f(
198 self.style.border.left_width(),
199 self.style.border.top_width(),
200 );
201 self.child.paint(child_origin, cx);
202 }
203
204 fn dispatch_event(
205 &mut self,
206 event: &Event,
207 _: RectF,
208 _: &mut Self::LayoutState,
209 _: &mut Self::PaintState,
210 cx: &mut EventContext,
211 ) -> bool {
212 self.child.dispatch_event(event, cx)
213 }
214
215 fn debug(
216 &self,
217 bounds: RectF,
218 _: &Self::LayoutState,
219 _: &Self::PaintState,
220 cx: &crate::DebugContext,
221 ) -> serde_json::Value {
222 json!({
223 "type": "Container",
224 "bounds": bounds.to_json(),
225 "details": self.style.to_json(),
226 "child": self.child.debug(cx),
227 })
228 }
229}
230
231impl ToJson for ContainerStyle {
232 fn to_json(&self) -> serde_json::Value {
233 json!({
234 "margin": self.margin.to_json(),
235 "padding": self.padding.to_json(),
236 "background_color": self.background_color.to_json(),
237 "border": self.border.to_json(),
238 "corner_radius": self.corner_radius,
239 "shadow": self.shadow.to_json(),
240 })
241 }
242}
243
244#[derive(Clone, Debug, Default)]
245pub struct Margin {
246 top: f32,
247 left: f32,
248 bottom: f32,
249 right: f32,
250}
251
252impl ToJson for Margin {
253 fn to_json(&self) -> serde_json::Value {
254 let mut value = json!({});
255 if self.top > 0. {
256 value["top"] = json!(self.top);
257 }
258 if self.right > 0. {
259 value["right"] = json!(self.right);
260 }
261 if self.bottom > 0. {
262 value["bottom"] = json!(self.bottom);
263 }
264 if self.left > 0. {
265 value["left"] = json!(self.left);
266 }
267 value
268 }
269}
270
271#[derive(Clone, Debug, Default)]
272pub struct Padding {
273 top: f32,
274 left: f32,
275 bottom: f32,
276 right: f32,
277}
278
279impl ToJson for Padding {
280 fn to_json(&self) -> serde_json::Value {
281 let mut value = json!({});
282 if self.top > 0. {
283 value["top"] = json!(self.top);
284 }
285 if self.right > 0. {
286 value["right"] = json!(self.right);
287 }
288 if self.bottom > 0. {
289 value["bottom"] = json!(self.bottom);
290 }
291 if self.left > 0. {
292 value["left"] = json!(self.left);
293 }
294 value
295 }
296}
297
298#[derive(Clone, Debug, Default)]
299pub struct Shadow {
300 offset: Vector2F,
301 blur: f32,
302 color: ColorU,
303}
304
305impl ToJson for Shadow {
306 fn to_json(&self) -> serde_json::Value {
307 json!({
308 "offset": self.offset.to_json(),
309 "blur": self.blur,
310 "color": self.color.to_json()
311 })
312 }
313}