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