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/clipped.rs
1use pathfinder_geometry::vector::Vector2F;
2 
3use super::Point;
4use crate::{
5 event::DispatchedEvent, AfterLayoutContext, AppContext, ClipBounds, Element, EventContext,
6 LayoutContext, PaintContext, SizeConstraint,
7};
8use std::any::Any;
9 
10/// Element that clips a child to its bounds
11pub struct Clipped {
12 origin: Option<Point>,
13 size: Option<Vector2F>,
14 child: Box<dyn Element>,
15}
16 
17impl Clipped {
18 pub fn new(child: Box<dyn Element>) -> Self {
19 Self {
20 origin: None,
21 size: None,
22 child,
23 }
24 }
25 
26 pub fn sized(child: Box<dyn Element>, size: Vector2F) -> Self {
27 Self {
28 origin: None,
29 size: Some(size),
30 child,
31 }
32 }
33}
34 
35impl Element for Clipped {
36 fn layout(
37 &mut self,
38 constraint: SizeConstraint,
39 ctx: &mut LayoutContext,
40 app: &AppContext,
41 ) -> Vector2F {
42 self.child.layout(constraint, ctx, app)
43 }
44 
45 fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &AppContext) {
46 self.child.after_layout(ctx, app);
47 }
48 
49 fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) {
50 let origin_point = Point::from_vec2f(origin, ctx.scene.z_index());
51 self.origin = Some(origin_point);
52 
53 // Get current clip bounds (if any) to ensure that the next layer respects them.
54 let current_bounds = ctx.scene.visible_rect(
55 origin_point,
56 self.size()
57 .expect("Clipped element should have a size at time of paint"),
58 );
59 
60 // Clipping works by creating a separate layer for an element with clip bounds.
61 // If current_bounds is None, this means that we shouldn't paint anything.
62 if let Some(bounds) = current_bounds {
63 ctx.scene.start_layer(ClipBounds::BoundedBy(bounds));
64 self.child.paint(origin, ctx, app);
65 ctx.scene.stop_layer();
66 }
67 }
68 
69 fn dispatch_event(
70 &mut self,
71 event: &DispatchedEvent,
72 ctx: &mut EventContext,
73 app: &AppContext,
74 ) -> bool {
75 // Only dispatch the event to the child if it has been painted.
76 self.child.origin().is_some() && self.child.dispatch_event(event, ctx, app)
77 }
78 
79 fn size(&self) -> Option<Vector2F> {
80 self.size.or_else(|| self.child.size())
81 }
82 
83 fn origin(&self) -> Option<Point> {
84 self.origin
85 }
86 
87 fn parent_data(&self) -> Option<&dyn Any> {
88 self.child.parent_data()
89 }
90 
91 #[cfg(any(test, feature = "test-util"))]
92 fn debug_text_content(&self) -> Option<String> {
93 self.child.debug_text_content()
94 }
95}
96 
97#[cfg(test)]
98#[path = "clipped_test.rs"]
99mod tests;
100