makepad_widgets/
modal.rs

1use crate::{
2    makepad_derive_widget::*,
3    makepad_draw::*,
4    makepad_platform::{KeyCode, KeyEvent},
5    view::*,
6    widget::*
7};
8
9live_design!{
10    link widgets;
11    use link::widgets::*;
12    use link::theme::*;
13    use makepad_draw::shader::std::*;
14    
15    pub ModalBase = {{Modal}} {}
16    pub Modal = <ModalBase> {
17        width: Fill
18        height: Fill
19        flow: Overlay
20        align: {x: 0.5, y: 0.5}
21        
22        draw_bg: {
23            fn pixel(self) -> vec4 {
24                return vec4(0., 0., 0., 0.0)
25            }
26        }
27        
28        bg_view: <View> {
29            width: Fill
30            height: Fill
31            show_bg: true
32            draw_bg: {
33                fn pixel(self) -> vec4 {
34                    return vec4(0., 0., 0., 0.7)
35                }
36            }
37        }
38        
39        content: <View> {
40            flow: Overlay
41            width: Fit
42            height: Fit
43        }
44    }
45}
46
47#[derive(Clone, Debug, DefaultNone)]
48pub enum ModalAction {
49    None,
50    Dismissed,
51}
52
53#[derive(Live, Widget)]
54pub struct Modal {
55    #[live]
56    #[find]
57    content: View,
58    #[live] #[area]
59    bg_view: View,
60
61    #[redraw]
62    #[rust(DrawList2d::new(cx))]
63    draw_list: DrawList2d,
64
65    #[live]
66    draw_bg: DrawQuad,
67    #[layout]
68    layout: Layout,
69    #[walk]
70    walk: Walk,
71
72    #[rust]
73    opened: bool,
74}
75
76impl LiveHook for Modal {
77    fn after_apply(&mut self, cx: &mut Cx, _apply: &mut Apply, _index: usize, _nodes: &[LiveNode]) {
78        self.draw_list.redraw(cx);
79    }
80}
81
82impl Widget for Modal {
83    fn handle_event(&mut self, cx: &mut Cx, event: &Event, scope: &mut Scope) {
84        if !self.opened {
85            return;
86        }
87
88        let area = self.draw_bg.area();
89
90        // When passing down events to the inner `content` view,
91        // we must temporarily suspend the sweep lock to allow the overlaid `content` View
92        // to correctly respond to events/hits.
93        cx.sweep_unlock(area);
94        self.content.handle_event(cx, event, scope);
95        cx.sweep_lock(area);
96
97        // Consume any hit that occurred in the bg area, which prevents the hit
98        // from being handled by any views underneath this modal.
99        let consumed_hit = event.hits_with_sweep_area(cx, area, area);
100
101        // Close the modal if any of the following conditions occur:
102        // * If the back navigational action/gesture was triggered (e.g., on Android)
103        // * If the Escape key was pressed
104        // * If there was a click/press in the background area, outside of the inner `content` view
105        let should_close = event.back_pressed()
106            || matches!(event, Event::KeyUp(KeyEvent { key_code: KeyCode::Escape, .. }))
107            || match consumed_hit {
108                Hit::FingerUp(fe) => !self.content.area().rect(cx).contains(fe.abs),
109                _ => false,
110            };
111        if should_close {
112            let widget_uid = self.content.widget_uid();
113            cx.widget_action(widget_uid, &scope.path, ModalAction::Dismissed);
114            self.close(cx);
115        }
116    }
117
118    fn draw_walk(&mut self, cx: &mut Cx2d, scope: &mut Scope, walk: Walk) -> DrawStep {
119        self.draw_list.begin_overlay_reuse(cx);
120
121        cx.begin_turtle(walk, self.layout);
122        self.draw_bg.begin(cx, self.walk, self.layout);
123
124        if self.opened {
125            let _ = self
126                .bg_view
127                .draw_walk(cx, scope, walk);
128            let _ = self.content.draw_all(cx, scope);
129        }
130
131        self.draw_bg.end(cx);
132        cx.end_turtle();
133        self.draw_list.end(cx);
134        DrawStep::done()
135    }
136}
137
138impl Modal {
139    pub fn open(&mut self, cx: &mut Cx) {
140        self.opened = true;
141        self.draw_bg.redraw(cx);
142        cx.sweep_lock(self.draw_bg.area());
143    }
144
145    pub fn close(&mut self, cx: &mut Cx) {
146        // Inform the inner modal content that its modal is being dismissed.
147        self.content.handle_event(
148            cx,
149            &Event::Actions(vec![Box::new(ModalAction::Dismissed)]),
150            &mut Scope::empty(),
151        );
152        self.opened = false;
153        self.draw_bg.redraw(cx);
154        cx.sweep_unlock(self.draw_bg.area());
155    }
156
157    pub fn dismissed(&self, actions: &Actions) -> bool {
158        matches!(
159            actions.find_widget_action(self.widget_uid()).cast(),
160            ModalAction::Dismissed
161        )
162    }
163}
164
165impl ModalRef {
166    pub fn is_open(&self) -> bool {
167        if let Some(inner) = self.borrow() {
168            inner.opened
169        } else {
170            false
171        }
172    }
173
174    pub fn open(&self, cx: &mut Cx) {
175        if let Some(mut inner) = self.borrow_mut() {
176            inner.open(cx);
177        }
178    }
179
180    pub fn close(&self, cx: &mut Cx) {
181        if let Some(mut inner) = self.borrow_mut() {
182            inner.close(cx);
183        }
184    }
185
186    pub fn dismissed(&self, actions: &Actions) -> bool {
187        if let Some(inner) = self.borrow() {
188            inner.dismissed(actions)
189        } else {
190            false
191        }
192    }
193}