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/dismiss.rs
1use pathfinder_geometry::{rect::RectF, vector::Vector2F};
2 
3use super::Point;
4use crate::{
5 event::DispatchedEvent, AfterLayoutContext, AppContext, ClipBounds, Element, Event,
6 EventContext, LayoutContext, PaintContext, SizeConstraint,
7};
8 
9type DismissCallback = Box<dyn FnMut(&mut EventContext, &AppContext)>;
10 
11/// Element that is used to dismiss its child element.
12/// Clicking on this element is equivalent to clicking outside the child element.
13pub struct Dismiss {
14 child: Box<dyn Element>,
15 dismiss_handler: Option<DismissCallback>,
16 origin: Option<Point>,
17 /// Whether or not the element should make the rest of the window unresponsive. All mouse events
18 /// are handled by the [`Dismiss`] rather than being propagated further down in the element
19 /// hierarchy.
20 prevent_interaction_with_other_elements: bool,
21}
22 
23impl Dismiss {
24 pub fn new(child: Box<dyn Element>) -> Self {
25 Self {
26 child,
27 dismiss_handler: None,
28 origin: None,
29 prevent_interaction_with_other_elements: false,
30 }
31 }
32 
33 /// Attach a handler for when the dismiss is clicked
34 pub fn on_dismiss<F>(mut self, handler: F) -> Self
35 where
36 F: 'static + FnMut(&mut EventContext, &AppContext),
37 {
38 self.dismiss_handler = Some(Box::new(handler));
39 self
40 }
41 
42 /// Prevents interactions with any other elements outside of the [`Dismiss`]. All events are
43 /// handled by this element and are _not_ propagated further down the element hierarchy.
44 pub fn prevent_interaction_with_other_elements(mut self) -> Self {
45 self.prevent_interaction_with_other_elements = true;
46 self
47 }
48}
49 
50impl Element for Dismiss {
51 fn layout(
52 &mut self,
53 constraint: SizeConstraint,
54 ctx: &mut LayoutContext,
55 app: &AppContext,
56 ) -> Vector2F {
57 self.child.layout(constraint, ctx, app)
58 }
59 
60 fn after_layout(&mut self, ctx: &mut AfterLayoutContext, app: &AppContext) {
61 self.child.after_layout(ctx, app);
62 }
63 
64 fn paint(&mut self, origin: Vector2F, ctx: &mut PaintContext, app: &AppContext) {
65 self.origin = Some(Point::from_vec2f(origin, ctx.scene.z_index()));
66 
67 if !self.prevent_interaction_with_other_elements {
68 // Create a new layer for the contents so that we can distinguish click events that happen
69 // outside of the child element we want to dismiss
70 ctx.scene.start_layer(ClipBounds::ActiveLayer);
71 self.child.paint(origin, ctx, app);
72 ctx.scene.stop_layer();
73 } else {
74 // Create an invisible rect underneath the child that spans the window and prevents all
75 // underlayed elements from responding to events, until a click occurs.
76 ctx.scene
77 .draw_rect_with_hit_recording(RectF::new(Vector2F::zero(), ctx.window_size));
78 self.child.paint(origin, ctx, app);
79 }
80 }
81 
82 fn dispatch_event(
83 &mut self,
84 event: &DispatchedEvent,
85 ctx: &mut EventContext,
86 app: &AppContext,
87 ) -> bool {
88 if self.child.dispatch_event(event, ctx, app) {
89 return true;
90 }
91 
92 let z_index = self.z_index().unwrap();
93 
94 match (
95 self.dismiss_handler.as_mut(),
96 event.at_z_index(z_index, ctx),
97 ) {
98 // If the event is available at the root z-index, that means it isn't covered by the
99 // child element, which means the user is clicking outside of the child element
100 (Some(handler), Some(Event::LeftMouseDown { .. })) => {
101 handler(ctx, app);
102 }
103 (None, Some(Event::LeftMouseDown { .. })) => {
104 log::warn!("Dismiss underlay was clicked but no handler was set!");
105 }
106 _ => {}
107 };
108 
109 false
110 }
111 
112 fn size(&self) -> Option<Vector2F> {
113 self.child.size()
114 }
115 
116 fn origin(&self) -> Option<Point> {
117 self.origin
118 }
119 
120 #[cfg(any(test, feature = "test-util"))]
121 fn debug_text_content(&self) -> Option<String> {
122 self.child.debug_text_content()
123 }
124}
125