Seregon/StratoSDK

StratoSDK is a framework with a declarative approach similar to Flutter/React, written and designed entirely for Rust.

Rust/27.3 KB/No license
crates/strato-ui-core/src/elements/container.rs
StratoSDK / crates / strato-ui-core / src / elements / container.rs
1use super::{
2 AfterLayoutContext, AppContext, DropShadow, Element, EventContext, Fill, LayoutContext, Margin,
3 Overdraw, Padding, PaintContext, Point, SelectableElement, Selection, SelectionFragment,
4 SizeConstraint,
5};
6pub use crate::scene::{Border, CornerRadius, Radius};
7use crate::{
8 event::DispatchedEvent,
9 text::{word_boundaries::WordBoundariesPolicy, IsRect, SelectionDirection, SelectionType},
10 ClipBounds, Gradient,
11};
12use pathfinder_color::ColorU;
13use pathfinder_geometry::{
14 rect::RectF,
15 vector::{vec2f, Vector2F},
16};
17 
18pub struct Container {
19 margin: Margin,
20 padding: Padding,
21 overdraw: Overdraw,
22 background: Fill,
23 border: Border,
24 corner_radius: CornerRadius,
25 drop_shadow: Option<DropShadow>,
26 foreground_overlay: Option<Fill>,
27 child: Box<dyn Element>,
28 size: Option<Vector2F>,
29 origin: Option<Point>,
30 #[cfg(debug_assertions)]
31 /// Captures the location of the constructor call site. This is used for debugging purposes.
32 construction_location: Option<&'static std::panic::Location<'static>>,
33}
34 
35impl Container {
36 #[cfg_attr(debug_assertions, track_caller)]
37 pub fn new(child: Box<dyn Element>) -> Self {
38 Self {
39 margin: Margin::default(),
40 padding: Padding::default(),
41 overdraw: Overdraw::default(),
42 background: Fill::None,
43 border: Border::default(),
44 corner_radius: CornerRadius::default(),
45 foreground_overlay: None,
46 drop_shadow: None,
47 child,
48 size: None,
49 origin: None,
50 #[cfg(debug_assertions)]
51 construction_location: Some(std::panic::Location::caller()),
52 }
53 }
54 
55 pub fn with_drop_shadow(mut self, drop_shadow: DropShadow) -> Self {
56 self.drop_shadow = Some(drop_shadow);
57 self
58 }
59 
60 pub fn with_foreground_overlay<F>(mut self, overlay: F) -> Self
61 where
62 F: Into<Fill>,
63 {
64 self.foreground_overlay = Some(overlay.into());
65 self
66 }
67 
68 pub fn with_margin_top(mut self, margin: f32) -> Self {
69 self.margin.top = margin;
70 self
71 }
72 
73 pub fn with_margin_bottom(mut self, margin: f32) -> Self {
74 self.margin.bottom = margin;
75 self
76 }
77 
78 pub fn with_margin_left(mut self, margin: f32) -> Self {
79 self.margin.left = margin;
80 self
81 }
82 
83 pub fn with_margin_right(mut self, margin: f32) -> Self {
84 self.margin.right = margin;
85 self
86 }
87 
88 pub fn with_uniform_margin(mut self, margin: f32) -> Self {
89 self.margin = Margin {
90 top: margin,
91 left: margin,
92 bottom: margin,
93 right: margin,
94 };
95 self
96 }
97 
98 pub fn with_uniform_padding(mut self, padding: f32) -> Self {
99 self.padding = Padding {
100 top: padding,
101 left: padding,
102 bottom: padding,
103 right: padding,
104 };
105 self
106 }
107 
108 pub fn with_padding_right(mut self, padding: f32) -> Self {
109 self.padding.right = padding;
110 self
111 }
112 
113 /// Sets the horizontal margin (`margin_left` and `margin_right`) to that of `margin`.
114 pub fn with_horizontal_margin(mut self, margin: f32) -> Self {
115 self.margin.left = margin;
116 self.margin.right = margin;
117 self
118 }
119 
120 /// Sets the vertical margin (`margin_top` and `margin_bottom`) to that of `margin`.
121 pub fn with_vertical_margin(mut self, margin: f32) -> Self {
122 self.margin.top = margin;
123 self.margin.bottom = margin;
124 self
125 }
126 
127 /// Sets the horizontal padding (`padding_left` and `padding_right`) to that of `padding`.
128 pub fn with_horizontal_padding(mut self, padding: f32) -> Self {
129 self.padding.left = padding;
130 self.padding.right = padding;
131 self
132 }
133 
134 /// Sets the vertical padding (`padding_top` and `padding_bottom`) to that of `padding`.
135 pub fn with_vertical_padding(mut self, padding: f32) -> Self {
136 self.padding.top = padding;
137 self.padding.bottom = padding;
138 self
139 }
140 
141 pub fn with_padding_left(mut self, padding: f32) -> Self {
142 self.padding.left = padding;
143 self
144 }
145 
146 pub fn with_padding_bottom(mut self, padding: f32) -> Self {
147 self.padding.bottom = padding;
148 self
149 }
150 
151 pub fn with_padding_top(mut self, padding: f32) -> Self {
152 self.padding.top = padding;
153 self
154 }
155 
156 pub fn with_padding(mut self, padding: Padding) -> Self {
157 self.padding = padding;
158 self
159 }
160 
161 pub fn with_background<F>(mut self, fill: F) -> Self
162 where
163 F: Into<Fill>,
164 {
165 self.background = fill.into();
166 self
167 }
168 
169 pub fn with_background_color(mut self, color: ColorU) -> Self {
170 self.background = Fill::Solid(color);
171 self
172 }
173 
174 pub fn with_horizontal_background_gradient(
175 mut self,
176 start_color: ColorU,
177 end_color: ColorU,
178 ) -> Self {
179 self.background = Fill::Gradient {
180 start: vec2f(0.0, 0.0),
181 end: vec2f(1.0, 0.0),
182 start_color,
183 end_color,
184 };
185 self
186 }
187 
188 pub fn with_background_gradient(
189 mut self,
190 start: Vector2F,
191 end: Vector2F,
192 gradient: Gradient,
193 ) -> Self {
194 self.background = Fill::Gradient {
195 start,
196 end,
197 start_color: gradient.start,
198 end_color: gradient.end,
199 };
200 self
201 }
202 
203 pub fn with_border(mut self, border: impl Into<Border>) -> Self {
204 self.border = border.into();
205 self
206 }
207 
208 pub fn with_overdraw_bottom(mut self, overdraw: f32) -> Self {
209 self.overdraw.bottom = overdraw;
210 self
211 }
212 
213 pub fn with_overdraw_left(mut self, overdraw: f32) -> Self {
214 self.overdraw.left = overdraw;
215 self
216 }
217 
218 pub fn with_vertical_overdraw(mut self, overdraw: f32) -> Self {
219 self.overdraw.top = overdraw;
220 self.overdraw.bottom = overdraw;
221 self
222 }
223 
224 pub fn with_corner_radius(mut self, radius: CornerRadius) -> Self {
225 self.corner_radius = radius;
226 self
227 }
228 
229 fn margin_size(&self) -> Vector2F {
230 vec2f(
231 self.margin.left + self.margin.right,
232 self.margin.top + self.margin.bottom,
233 )
234 }
235 
236 fn padding_size(&self) -> Vector2F {
237 vec2f(
238 self.padding.left + self.padding.right,
239 self.padding.top + self.padding.bottom,
240 )
241 }
242 
243 fn border_size(&self) -> Vector2F {
244 let mut x = 0.0;
245 if self.border.left {
246 x += self.border.width;
247 }
248 if self.border.right {
249 x += self.border.width;
250 }
251 
252 let mut y = 0.0;
253 if self.border.top {
254 y += self.border.width;
255 }
256 if self.border.bottom {
257 y += self.border.width;
258 }
259 
260 vec2f(x, y)
261 }
262}
263 
264impl Element for Container {
265 fn layout(
266 &mut self,
267 constraint: SizeConstraint,
268 ctx: &mut LayoutContext,
269 app: &AppContext,
270 ) -> Vector2F {
271 let size_buffer = self.margin_size() + self.padding_size() + self.border_size();
272 
273 let child_constraint = SizeConstraint {
274 min: (constraint.min - size_buffer).max(Vector2F::zero()),
275 max: (constraint.max - size_buffer).max(Vector2F::zero()),
276 };
277 let child_size = self.child.layout(child_constraint, ctx, app);
278 let size = child_size + size_buffer;
279 self.size = Some(size);
280 size
281 }
282 
283 fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &AppContext) {
284 self.child.after_layout(ctx, app);
285 }
286 
287 fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) {
288 self.origin = Some(Point::from_vec2f(origin, ctx.scene.z_index()));
289 let size = self.size.unwrap() - self.margin_size()
290 + vec2f(self.overdraw.right, self.overdraw.bottom);
291 let origin = origin + vec2f(self.margin.left, self.margin.top)
292 - vec2f(self.overdraw.left, self.overdraw.top);
293 
294 #[cfg(debug_assertions)]
295 ctx.scene
296 .set_location_for_panic_logging(self.construction_location);
297 
298 let rect = ctx
299 .scene
300 .draw_rect_with_hit_recording(RectF::new(origin, size))
301 .with_background(self.background)
302 .with_border(self.border)
303 .with_corner_radius(self.corner_radius);
304 if let Some(drop_shadow) = self.drop_shadow {
305 rect.with_drop_shadow(drop_shadow);
306 }
307 
308 let mut child_origin = origin
309 + vec2f(self.overdraw.left, self.overdraw.top)
310 + vec2f(self.padding.left, self.padding.top);
311 if self.border.left {
312 child_origin.set_x(child_origin.x() + self.border.width);
313 }
314 if self.border.top {
315 child_origin.set_y(child_origin.y() + self.border.width);
316 }
317 self.child.paint(child_origin, ctx, app);
318 
319 // Start a new layer on top of the current container to render the foreground overlay.
320 if let Some(overlay) = self.foreground_overlay {
321 ctx.scene.start_layer(ClipBounds::ActiveLayer);
322 ctx.scene.set_active_layer_click_through();
323 
324 #[cfg(debug_assertions)]
325 ctx.scene
326 .set_location_for_panic_logging(self.construction_location);
327 
328 ctx.scene
329 .draw_rect_with_hit_recording(RectF::new(origin, size))
330 .with_background(overlay)
331 .with_corner_radius(self.corner_radius);
332 ctx.scene.stop_layer();
333 }
334 }
335 
336 fn dispatch_event(
337 &mut self,
338 event: &DispatchedEvent,
339 ctx: &mut EventContext,
340 app: &AppContext,
341 ) -> bool {
342 self.child.dispatch_event(event, ctx, app)
343 }
344 
345 fn size(&self) -> Option<Vector2F> {
346 self.size
347 }
348 
349 fn origin(&self) -> Option<Point> {
350 self.origin
351 }
352 
353 fn as_selectable_element(&self) -> Option<&dyn SelectableElement> {
354 Some(self as &dyn SelectableElement)
355 }
356 
357 #[cfg(any(test, feature = "test-util"))]
358 fn debug_text_content(&self) -> Option<String> {
359 self.child.debug_text_content()
360 }
361}
362 
363impl SelectableElement for Container {
364 fn get_selection(
365 &self,
366 selection_start: Vector2F,
367 selection_end: Vector2F,
368 is_rect: IsRect,
369 ) -> Option<Vec<SelectionFragment>> {
370 self.child
371 .as_selectable_element()
372 .and_then(|selectable_child| {
373 selectable_child.get_selection(selection_start, selection_end, is_rect)
374 })
375 }
376 
377 fn expand_selection(
378 &self,
379 point: Vector2F,
380 direction: SelectionDirection,
381 unit: SelectionType,
382 word_boundaries_policy: &WordBoundariesPolicy,
383 ) -> Option<Vector2F> {
384 self.child
385 .as_selectable_element()
386 .and_then(|selectable_child| {
387 selectable_child.expand_selection(point, direction, unit, word_boundaries_policy)
388 })
389 }
390 
391 fn is_point_semantically_before(
392 &self,
393 absolute_point: Vector2F,
394 absolute_point_other: Vector2F,
395 ) -> Option<bool> {
396 self.child
397 .as_selectable_element()
398 .and_then(|selectable_child| {
399 selectable_child.is_point_semantically_before(absolute_point, absolute_point_other)
400 })
401 }
402 
403 fn smart_select(
404 &self,
405 absolute_point: Vector2F,
406 smart_select_fn: crate::elements::SmartSelectFn,
407 ) -> Option<(Vector2F, Vector2F)> {
408 self.child
409 .as_selectable_element()
410 .and_then(|selectable_child| {
411 selectable_child.smart_select(absolute_point, smart_select_fn)
412 })
413 }
414 
415 fn calculate_clickable_bounds(&self, current_selection: Option<Selection>) -> Vec<RectF> {
416 self.child
417 .as_selectable_element()
418 .map(|selectable_child| selectable_child.calculate_clickable_bounds(current_selection))
419 .unwrap_or_default()
420 }
421}
422 
423#[cfg(test)]
424#[path = "container_test.rs"]
425mod tests;
426