rat_dialog/
dialog_control.rs

1//! A stack of dialog windows.
2use crate::WindowControl;
3use rat_event::HandleEvent;
4use ratatui::buffer::Buffer;
5use ratatui::layout::Rect;
6use std::any::{Any, TypeId};
7use std::cell::{Cell, Ref, RefCell, RefMut};
8use std::fmt::{Debug, Formatter};
9use std::mem;
10use std::rc::Rc;
11use try_as::traits::TryAsRef;
12
13/// Hold a stack of widgets.
14///
15/// Renders the widgets and can handle events.
16///
17/// Hold the dialog-stack in your global state,
18/// call render() at the very end of rendering and
19/// handle() near the start of event-handling.
20///
21/// This will not handle modality, so make sure
22/// to consume all events you don't want to propagate.
23///
24pub struct DialogStack<Event, Context, Error> {
25    core: Rc<DialogStackCore<Event, Context, Error>>,
26}
27
28struct DialogStackCore<Event, Context, Error> {
29    len: Cell<usize>,
30    render: RefCell<Vec<Box<dyn Fn(Rect, &mut Buffer, &mut dyn Any, &mut Context) + 'static>>>,
31    event: RefCell<
32        Vec<
33            Box<
34                dyn Fn(&Event, &mut dyn Any, &mut Context) -> Result<WindowControl<Event>, Error>
35                    + 'static,
36            >,
37        >,
38    >,
39    type_id: RefCell<Vec<TypeId>>,
40    state: RefCell<Vec<Option<Box<dyn Any>>>>,
41}
42
43impl<Event, Context, Error> Clone for DialogStack<Event, Context, Error> {
44    fn clone(&self) -> Self {
45        Self {
46            core: self.core.clone(),
47        }
48    }
49}
50
51impl<Event, Context, Error> Default for DialogStack<Event, Context, Error> {
52    fn default() -> Self {
53        Self {
54            core: Rc::new(DialogStackCore {
55                len: Cell::new(0),
56                render: Default::default(),
57                event: Default::default(),
58                type_id: Default::default(),
59                state: Default::default(),
60            }),
61        }
62    }
63}
64
65impl<Event, Context, Error> Debug for DialogStack<Event, Context, Error> {
66    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
67        let state = self.core.state.borrow();
68        let is_proxy = state.iter().map(|v| v.is_none()).collect::<Vec<_>>();
69        let type_id = self.core.type_id.borrow();
70
71        f.debug_struct("DialogStackCore")
72            .field("len", &self.core.len.get())
73            .field("type_id", &type_id)
74            .field("is_proxy", &is_proxy)
75            .finish()
76    }
77}
78
79impl<Event, Context, Error> DialogStack<Event, Context, Error> {
80    /// Render all dialog-windows in stack-order.
81    pub fn render(self, area: Rect, buf: &mut Buffer, ctx: &mut Context) {
82        for n in 0..self.core.len.get() {
83            let Some(mut state) = self.core.state.borrow_mut()[n].take() else {
84                panic!("state is gone");
85            };
86            let render_fn = mem::replace(
87                &mut self.core.render.borrow_mut()[n],
88                Box::new(|_, _, _, _| {}),
89            );
90
91            render_fn(area, buf, state.as_mut(), ctx);
92
93            self.core.render.borrow_mut()[n] = render_fn;
94            self.core.state.borrow_mut()[n] = Some(state);
95        }
96    }
97}
98
99impl<Event, Context, Error> DialogStack<Event, Context, Error> {
100    pub fn new() -> Self {
101        Self::default()
102    }
103
104    /// Push a dialog-window on the stack.
105    /// - render is called in reverse stack order, to render bottom to top.
106    /// - event is called in stack-order to handle events.
107    ///   if you don't want events to propagate to dialog-windows in the
108    ///   background, you must consume them by returning StackControl::Unchanged.
109    /// - state as Any
110    pub fn push(
111        &self,
112        render: impl Fn(Rect, &mut Buffer, &mut dyn Any, &'_ mut Context) + 'static,
113        event: impl Fn(&Event, &mut dyn Any, &'_ mut Context) -> Result<WindowControl<Event>, Error>
114        + 'static,
115        state: impl Any,
116    ) {
117        self.core.len.update(|v| v + 1);
118        self.core.type_id.borrow_mut().push(state.type_id());
119        self.core.state.borrow_mut().push(Some(Box::new(state)));
120        self.core.event.borrow_mut().push(Box::new(event));
121        self.core.render.borrow_mut().push(Box::new(render));
122    }
123
124    /// Pop the top dialog-window from the stack.
125    ///
126    /// It will return None if the stack is empty.
127    ///
128    /// Panic
129    ///
130    /// This function is partially reentrant. When called during rendering/event-handling
131    /// it will panic when trying to pop your current dialog-window.
132    /// Return [WindowControl::Close] instead of calling this function.
133    pub fn pop(&self) -> Option<Box<dyn Any>> {
134        self.core.len.update(|v| v - 1);
135        self.core.type_id.borrow_mut().pop();
136        self.core.event.borrow_mut().pop();
137        self.core.render.borrow_mut().pop();
138        let Some(s) = self.core.state.borrow_mut().pop() else {
139            return None;
140        };
141        if s.is_none() {
142            panic!("state is gone");
143        }
144        s
145    }
146
147    /// Remove some dialog-window.
148    ///
149    /// Panic
150    ///
151    /// This function is not reentrant. It will panic when called during
152    /// rendering or event-handling of any dialog-window.
153    /// Return [WindowControl::Close] instead of calling this function.
154    ///
155    /// Panics when out-of-bounds.
156    pub fn remove(&self, n: usize) -> Box<dyn Any> {
157        for s in self.core.state.borrow().iter() {
158            if s.is_none() {
159                panic!("state is gone");
160            }
161        }
162
163        self.core.len.update(|v| v - 1);
164        self.core.type_id.borrow_mut().remove(n);
165        _ = self.core.event.borrow_mut().remove(n);
166        _ = self.core.render.borrow_mut().remove(n);
167
168        self.core
169            .state
170            .borrow_mut()
171            .remove(n)
172            .expect("state exists")
173    }
174
175    /// No windows.
176    pub fn is_empty(&self) -> bool {
177        self.core.type_id.borrow().is_empty()
178    }
179
180    /// Number of dialog-windows.
181    pub fn len(&self) -> usize {
182        self.core.len.get()
183    }
184
185    /// Typecheck the state.
186    pub fn state_is<S: 'static>(&self, n: usize) -> bool {
187        self.core.type_id.borrow()[n] == TypeId::of::<S>()
188    }
189
190    /// Find top state with this type.
191    #[allow(clippy::manual_find)]
192    pub fn top<S: 'static>(&self) -> Option<usize> {
193        for n in (0..self.core.len.get()).rev() {
194            if self.core.type_id.borrow()[n] == TypeId::of::<S>() {
195                return Some(n);
196            }
197        }
198        None
199    }
200
201    /// Find all states with this type.
202    pub fn find<S: 'static>(&self) -> Vec<usize> {
203        self.core
204            .type_id
205            .borrow()
206            .iter()
207            .enumerate()
208            .rev()
209            .filter_map(|(n, v)| {
210                if *v == TypeId::of::<S>() {
211                    Some(n)
212                } else {
213                    None
214                }
215            })
216            .collect()
217    }
218
219    /// Get a reference to the state at index n.
220    ///
221    /// Panic
222    ///
223    /// Panics when out-of-bounds.
224    /// Panics when recursively accessing the same state. Accessing a
225    /// *different* window-state is fine.
226    /// Panics when the types don't match.
227    pub fn get<'a, S: 'static>(&'a self, n: usize) -> Ref<'a, S> {
228        self.try_get(n).expect("recursion or wrong type")
229    }
230
231    /// Get a mutable reference to the state at index n.
232    ///
233    /// Panic
234    ///
235    /// Panics when out-of-bounds.
236    /// Panics when recursively accessing the same state. Accessing a
237    /// *different* window-state is fine.
238    /// Panics when the types don't match.
239    pub fn get_mut<'a, S: 'static>(&'a self, n: usize) -> RefMut<'a, S> {
240        self.try_get_mut(n).expect("recursion or wrong type")
241    }
242
243    /// Get a mutable reference to the state at index n.
244    ///
245    /// Panic
246    ///
247    /// Panics when out-of-bounds.
248    ///
249    /// Fails
250    ///
251    /// Fails when recursively accessing the same state. Accessing a
252    /// *different* window-state is fine.
253    /// Fails when the types don't match.
254    pub fn try_get_mut<'a, S: 'static>(&'a self, n: usize) -> Option<RefMut<'a, S>> {
255        let state = self.core.state.borrow_mut();
256
257        RefMut::filter_map(state, |v| {
258            let state = &mut v[n];
259            if let Some(state) = state.as_mut() {
260                if let Some(state) = state.downcast_mut::<S>() {
261                    Some(state)
262                } else {
263                    None
264                }
265            } else {
266                None
267            }
268        })
269        .ok()
270    }
271
272    /// Get a reference to the state at index n.
273    ///
274    /// Panic
275    ///
276    /// Panics when out-of-bounds.
277    ///
278    /// Fails
279    ///
280    /// Fails when recursively accessing the same state. Accessing a
281    /// *different* window-state is fine.
282    /// Fails when the types don't match.
283    pub fn try_get<'a, S: 'static>(&'a self, n: usize) -> Option<Ref<'a, S>> {
284        let state = self.core.state.borrow();
285
286        Ref::filter_map(state, |v| {
287            let state = &v[n];
288            if let Some(state) = state.as_ref() {
289                if let Some(state) = state.downcast_ref::<S>() {
290                    Some(state)
291                } else {
292                    None
293                }
294            } else {
295                None
296            }
297        })
298        .ok()
299    }
300}
301
302/// Handle events from top to bottom of the stack.
303///
304/// Panic
305///
306/// This function is not reentrant, it will panic when called from within it's call-stack.
307impl<Event, Context, Error> HandleEvent<Event, &mut Context, Result<WindowControl<Event>, Error>>
308    for DialogStack<Event, Context, Error>
309where
310    Event: TryAsRef<crossterm::event::Event>,
311    Error: 'static,
312    Event: 'static,
313{
314    fn handle(&mut self, event: &Event, ctx: &mut Context) -> Result<WindowControl<Event>, Error> {
315        for n in (0..self.core.len.get()).rev() {
316            let Some(mut state) = self.core.state.borrow_mut()[n].take() else {
317                panic!("state is gone");
318            };
319            let event_fn = mem::replace(
320                &mut self.core.event.borrow_mut()[n],
321                Box::new(|_, _, _| Ok(WindowControl::Continue)),
322            );
323
324            let r = event_fn(event, state.as_mut(), ctx);
325
326            self.core.event.borrow_mut()[n] = event_fn;
327            self.core.state.borrow_mut()[n] = Some(state);
328
329            match r {
330                Ok(r) => match r {
331                    WindowControl::Close(_) => {
332                        self.remove(n);
333                        return Ok(r);
334                    }
335                    WindowControl::Event(_) => {
336                        return Ok(r);
337                    }
338                    WindowControl::Unchanged => {
339                        return Ok(r);
340                    }
341                    WindowControl::Changed => {
342                        return Ok(r);
343                    }
344                    WindowControl::Continue => {
345                        // next
346                    }
347                },
348                Err(e) => return Err(e),
349            }
350        }
351
352        if self.len() > 0 {
353            // block all crossterm events.
354            let event: Option<&crossterm::event::Event> = event.try_as_ref();
355            if event.is_some() {
356                Ok(WindowControl::Unchanged)
357            } else {
358                Ok(WindowControl::Continue)
359            }
360        } else {
361            Ok(WindowControl::Continue)
362        }
363    }
364}
365
366/// Handle events from top to bottom of the stack.
367///
368/// Panic
369///
370/// This function is not reentrant, it will panic when called from within it's call-stack.
371pub fn handle_dialog_stack<Event, Context, Error>(
372    mut dialog_stack: DialogStack<Event, Context, Error>,
373    event: &Event,
374    ctx: &mut Context,
375) -> Result<WindowControl<Event>, Error>
376where
377    Event: TryAsRef<crossterm::event::Event>,
378    Error: 'static,
379    Event: 'static,
380{
381    dialog_stack.handle(event, ctx)
382}