1use pathfinder_geometry::rect::RectF;
2use serde::Deserialize;
3use serde_json::json;
4
5use crate::{
6 color::Color,
7 geometry::{
8 deserialize_vec2f,
9 vector::{vec2f, Vector2F},
10 },
11 json::ToJson,
12 scene::{self, Border, Quad},
13 Element, ElementBox, Event, EventContext, LayoutContext, PaintContext, SizeConstraint,
14};
15
16#[derive(Clone, Debug, Default, Deserialize)]
17pub struct ContainerStyle {
18 #[serde(default)]
19 pub margin: Margin,
20 #[serde(default)]
21 pub padding: Padding,
22 #[serde(rename = "background")]
23 pub background_color: Option<Color>,
24 #[serde(default)]
25 pub border: Border,
26 #[serde(default)]
27 pub corner_radius: f32,
28 #[serde(default)]
29 pub shadow: Option<Shadow>,
30}
31
32pub struct Container {
33 child: ElementBox,
34 style: ContainerStyle,
35}
36
37impl Container {
38 pub fn new(child: ElementBox) -> Self {
39 Self {
40 child,
41 style: Default::default(),
42 }
43 }
44
45 pub fn with_style(mut self, style: &ContainerStyle) -> Self {
46 self.style = style.clone();
47 self
48 }
49
50 pub fn with_margin_top(mut self, margin: f32) -> Self {
51 self.style.margin.top = margin;
52 self
53 }
54
55 pub fn with_margin_left(mut self, margin: f32) -> Self {
56 self.style.margin.left = margin;
57 self
58 }
59
60 pub fn with_horizontal_padding(mut self, padding: f32) -> Self {
61 self.style.padding.left = padding;
62 self.style.padding.right = padding;
63 self
64 }
65
66 pub fn with_vertical_padding(mut self, padding: f32) -> Self {
67 self.style.padding.top = padding;
68 self.style.padding.bottom = padding;
69 self
70 }
71
72 pub fn with_uniform_padding(mut self, padding: f32) -> Self {
73 self.style.padding = Padding {
74 top: padding,
75 left: padding,
76 bottom: padding,
77 right: padding,
78 };
79 self
80 }
81
82 pub fn with_padding_right(mut self, padding: f32) -> Self {
83 self.style.padding.right = padding;
84 self
85 }
86
87 pub fn with_padding_bottom(mut self, padding: f32) -> Self {
88 self.style.padding.bottom = padding;
89 self
90 }
91
92 pub fn with_background_color(mut self, color: Color) -> Self {
93 self.style.background_color = Some(color);
94 self
95 }
96
97 pub fn with_border(mut self, border: Border) -> Self {
98 self.style.border = border;
99 self
100 }
101
102 pub fn with_corner_radius(mut self, radius: f32) -> Self {
103 self.style.corner_radius = radius;
104 self
105 }
106
107 pub fn with_shadow(mut self, offset: Vector2F, blur: f32, color: Color) -> Self {
108 self.style.shadow = Some(Shadow {
109 offset,
110 blur,
111 color,
112 });
113 self
114 }
115
116 fn margin_size(&self) -> Vector2F {
117 vec2f(
118 self.style.margin.left + self.style.margin.right,
119 self.style.margin.top + self.style.margin.bottom,
120 )
121 }
122
123 fn padding_size(&self) -> Vector2F {
124 vec2f(
125 self.style.padding.left + self.style.padding.right,
126 self.style.padding.top + self.style.padding.bottom,
127 )
128 }
129
130 fn border_size(&self) -> Vector2F {
131 let mut x = 0.0;
132 if self.style.border.left {
133 x += self.style.border.width;
134 }
135 if self.style.border.right {
136 x += self.style.border.width;
137 }
138
139 let mut y = 0.0;
140 if self.style.border.top {
141 y += self.style.border.width;
142 }
143 if self.style.border.bottom {
144 y += self.style.border.width;
145 }
146
147 vec2f(x, y)
148 }
149}
150
151impl Element for Container {
152 type LayoutState = ();
153 type PaintState = ();
154
155 fn layout(
156 &mut self,
157 constraint: SizeConstraint,
158 cx: &mut LayoutContext,
159 ) -> (Vector2F, Self::LayoutState) {
160 let size_buffer = self.margin_size() + self.padding_size() + self.border_size();
161 let child_constraint = SizeConstraint {
162 min: (constraint.min - size_buffer).max(Vector2F::zero()),
163 max: (constraint.max - size_buffer).max(Vector2F::zero()),
164 };
165 let child_size = self.child.layout(child_constraint, cx);
166 (child_size + size_buffer, ())
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, Deserialize)]
245pub struct Margin {
246 #[serde(default)]
247 top: f32,
248 #[serde(default)]
249 left: f32,
250 #[serde(default)]
251 bottom: f32,
252 #[serde(default)]
253 right: f32,
254}
255
256impl ToJson for Margin {
257 fn to_json(&self) -> serde_json::Value {
258 let mut value = json!({});
259 if self.top > 0. {
260 value["top"] = json!(self.top);
261 }
262 if self.right > 0. {
263 value["right"] = json!(self.right);
264 }
265 if self.bottom > 0. {
266 value["bottom"] = json!(self.bottom);
267 }
268 if self.left > 0. {
269 value["left"] = json!(self.left);
270 }
271 value
272 }
273}
274
275#[derive(Clone, Debug, Default, Deserialize)]
276pub struct Padding {
277 #[serde(default)]
278 top: f32,
279 #[serde(default)]
280 left: f32,
281 #[serde(default)]
282 bottom: f32,
283 #[serde(default)]
284 right: f32,
285}
286
287impl ToJson for Padding {
288 fn to_json(&self) -> serde_json::Value {
289 let mut value = json!({});
290 if self.top > 0. {
291 value["top"] = json!(self.top);
292 }
293 if self.right > 0. {
294 value["right"] = json!(self.right);
295 }
296 if self.bottom > 0. {
297 value["bottom"] = json!(self.bottom);
298 }
299 if self.left > 0. {
300 value["left"] = json!(self.left);
301 }
302 value
303 }
304}
305
306#[derive(Clone, Debug, Default, Deserialize)]
307pub struct Shadow {
308 #[serde(default, deserialize_with = "deserialize_vec2f")]
309 offset: Vector2F,
310 #[serde(default)]
311 blur: f32,
312 #[serde(default)]
313 color: Color,
314}
315
316impl ToJson for Shadow {
317 fn to_json(&self) -> serde_json::Value {
318 json!({
319 "offset": self.offset.to_json(),
320 "blur": self.blur,
321 "color": self.color.to_json()
322 })
323 }
324}