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