rat_dialog/
window_control.rs

1//! A list of application windows.
2use crate::WindowControl;
3use rat_event::util::mouse_trap;
4use rat_event::{ConsumedEvent, HandleEvent, Outcome, ct_event};
5use ratatui::buffer::Buffer;
6use ratatui::layout::Rect;
7use std::any::{Any, TypeId};
8use std::cell::{Cell, Ref, RefCell, RefMut};
9use std::fmt::{Debug, Formatter};
10use std::mem;
11use std::rc::Rc;
12use try_as::traits::TryAsRef;
13
14pub mod mac_frame;
15pub mod window_frame;
16
17pub trait Window: Any {
18    /// Set as top window.
19    fn set_top(&mut self, top: bool);
20    /// Window area.
21    fn area(&self) -> Rect;
22}
23
24impl dyn Window {
25    pub fn downcast_ref<T: Any>(&self) -> Option<&T> {
26        (self as &dyn Any).downcast_ref::<T>()
27    }
28
29    pub fn downcast_mut<T: Any>(&mut self) -> Option<&mut T> {
30        (self as &mut dyn Any).downcast_mut::<T>()
31    }
32}
33
34#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord)]
35pub enum WindowFrameOutcome {
36    /// The given event was not handled at all.
37    Continue,
38    /// The event was handled, no repaint necessary.
39    Unchanged,
40    /// The event was handled, repaint necessary.
41    Changed,
42    /// Request close.
43    ShouldClose,
44    /// Has moved.
45    Moved,
46    /// Has resized.
47    Resized,
48}
49
50impl ConsumedEvent for WindowFrameOutcome {
51    fn is_consumed(&self) -> bool {
52        *self != WindowFrameOutcome::Continue
53    }
54}
55
56impl From<Outcome> for WindowFrameOutcome {
57    fn from(value: Outcome) -> Self {
58        match value {
59            Outcome::Continue => WindowFrameOutcome::Continue,
60            Outcome::Unchanged => WindowFrameOutcome::Unchanged,
61            Outcome::Changed => WindowFrameOutcome::Changed,
62        }
63    }
64}
65
66impl From<WindowFrameOutcome> for Outcome {
67    fn from(value: WindowFrameOutcome) -> Self {
68        match value {
69            WindowFrameOutcome::Continue => Outcome::Continue,
70            WindowFrameOutcome::Unchanged => Outcome::Unchanged,
71            WindowFrameOutcome::Changed => Outcome::Changed,
72            WindowFrameOutcome::Moved => Outcome::Changed,
73            WindowFrameOutcome::Resized => Outcome::Changed,
74            WindowFrameOutcome::ShouldClose => Outcome::Continue,
75        }
76    }
77}
78
79///
80/// A list of [Window]s.
81///
82pub struct WindowList<Event, Context, Error> {
83    core: Rc<WindowListCore<Event, Context, Error>>,
84}
85
86struct WindowListCore<Event, Context, Error> {
87    len: Cell<usize>,
88    render: RefCell<Vec<Box<dyn Fn(Rect, &mut Buffer, &mut dyn Window, &mut Context) + 'static>>>,
89    event: RefCell<
90        Vec<
91            Box<
92                dyn Fn(&Event, &mut dyn Window, &mut Context) -> Result<WindowControl<Event>, Error>
93                    + 'static,
94            >,
95        >,
96    >,
97    type_id: RefCell<Vec<TypeId>>,
98    state: RefCell<Vec<Option<Box<dyn Window>>>>,
99}
100
101impl<Event, Context, Error> Clone for WindowList<Event, Context, Error> {
102    fn clone(&self) -> Self {
103        Self {
104            core: self.core.clone(),
105        }
106    }
107}
108
109impl<Event, Context, Error> Default for WindowList<Event, Context, Error> {
110    fn default() -> Self {
111        Self {
112            core: Rc::new(WindowListCore {
113                len: Cell::new(0),
114                render: Default::default(),
115                event: Default::default(),
116                type_id: Default::default(),
117                state: Default::default(),
118            }),
119        }
120    }
121}
122
123impl<Event, Context, Error> Debug for WindowList<Event, Context, Error> {
124    fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result {
125        let state = self.core.state.borrow();
126        let is_proxy = state.iter().map(|v| v.is_none()).collect::<Vec<_>>();
127        let type_id = self.core.type_id.borrow();
128
129        f.debug_struct("DialogStackCore")
130            .field("len", &self.core.len.get())
131            .field("type_id", &type_id)
132            .field("is_proxy", &is_proxy)
133            .finish()
134    }
135}
136
137impl<Event, Context, Error> WindowList<Event, Context, Error> {
138    /// Render all dialog-windows in stack-order.
139    pub fn render(self, area: Rect, buf: &mut Buffer, ctx: &mut Context) {
140        for n in 0..self.core.len.get() {
141            let Some(mut state) = self.core.state.borrow_mut()[n].take() else {
142                panic!("state is gone");
143            };
144            let render_fn = mem::replace(
145                &mut self.core.render.borrow_mut()[n],
146                Box::new(|_, _, _, _| {}),
147            );
148
149            render_fn(area, buf, state.as_mut(), ctx);
150
151            self.core.render.borrow_mut()[n] = render_fn;
152            self.core.state.borrow_mut()[n] = Some(state);
153        }
154    }
155}
156
157impl<Event, Context, Error> WindowList<Event, Context, Error> {
158    pub fn new() -> Self {
159        Self::default()
160    }
161
162    /// Show a window.
163    ///
164    /// This shows the window on top of any other.
165    pub fn show(
166        &self,
167        render: impl Fn(Rect, &mut Buffer, &mut dyn Window, &'_ mut Context) + 'static,
168        event: impl Fn(&Event, &mut dyn Window, &'_ mut Context) -> Result<WindowControl<Event>, Error>
169        + 'static,
170        state: impl Window,
171    ) {
172        self.core.len.update(|v| v + 1);
173        self.core.type_id.borrow_mut().push(state.type_id());
174        self.core.state.borrow_mut().push(Some(Box::new(state)));
175        self.core.event.borrow_mut().push(Box::new(event));
176        self.core.render.borrow_mut().push(Box::new(render));
177
178        self.set_top_window();
179    }
180
181    /// Remove the window from the list.
182    ///
183    /// Panic
184    ///
185    /// This function is not reentrant. It will panic when called during
186    /// rendering or event-handling of any dialog-window.
187    /// Return [WindowControl::Close] instead of calling this function.
188    ///
189    /// Panics when out-of-bounds.
190    pub fn close(&self, n: usize) -> Box<dyn Window> {
191        for s in self.core.state.borrow().iter() {
192            if s.is_none() {
193                panic!("state is gone");
194            }
195        }
196
197        self.core.len.update(|v| v - 1);
198        self.core.type_id.borrow_mut().remove(n);
199        _ = self.core.event.borrow_mut().remove(n);
200        _ = self.core.render.borrow_mut().remove(n);
201
202        let r = self
203            .core
204            .state
205            .borrow_mut()
206            .remove(n)
207            .expect("state exists");
208
209        self.set_top_window();
210
211        r
212    }
213
214    /// Move the given window to the top.
215    ///
216    /// Panic
217    ///
218    /// This function is not reentrant. It will panic when called during
219    /// rendering or event-handling of any window. It is not necessary
220    /// to call this during event handling as this happens automatically.
221    ///
222    /// Panics when out-of-bounds.
223    pub fn to_front(&self, n: usize) {
224        for s in self.core.state.borrow().iter() {
225            if s.is_none() {
226                panic!("state is gone");
227            }
228        }
229
230        let type_id = self.core.type_id.borrow_mut().remove(n);
231        let state = self.core.state.borrow_mut().remove(n);
232        let event = self.core.event.borrow_mut().remove(n);
233        let render = self.core.render.borrow_mut().remove(n);
234
235        self.core.type_id.borrow_mut().push(type_id);
236        self.core.state.borrow_mut().push(state);
237        self.core.event.borrow_mut().push(event);
238        self.core.render.borrow_mut().push(render);
239
240        self.set_top_window();
241    }
242
243    /// Move the given window to the bottom.
244    ///
245    /// Panic
246    ///
247    /// This function is not reentrant. It will panic when called during
248    /// rendering or event-handling of any dialog-window.
249    ///
250    /// Panics when out-of-bounds.
251    pub fn to_back(&self, n: usize) {
252        for s in self.core.state.borrow().iter() {
253            if s.is_none() {
254                panic!("state is gone");
255            }
256        }
257
258        let type_id = self.core.type_id.borrow_mut().remove(n);
259        let state = self.core.state.borrow_mut().remove(n);
260        let event = self.core.event.borrow_mut().remove(n);
261        let render = self.core.render.borrow_mut().remove(n);
262
263        self.core.type_id.borrow_mut().insert(0, type_id);
264        self.core.state.borrow_mut().insert(0, state);
265        self.core.event.borrow_mut().insert(0, event);
266        self.core.render.borrow_mut().insert(0, render);
267
268        self.set_top_window();
269    }
270
271    fn set_top_window(&self) {
272        let len = self.len();
273
274        if len > 0 {
275            let mut states = self.core.state.borrow_mut();
276            for i in 0..len - 1 {
277                if let Some(s) = &mut states[i] {
278                    s.set_top(false);
279                } else {
280                    panic!("state is gone");
281                }
282            }
283            if let Some(s) = &mut states[len - 1] {
284                s.set_top(true);
285            } else {
286                panic!("state is gone");
287            }
288        }
289    }
290
291    /// No windows.
292    pub fn is_empty(&self) -> bool {
293        self.core.type_id.borrow().is_empty()
294    }
295
296    /// Number of windows.
297    pub fn len(&self) -> usize {
298        self.core.len.get()
299    }
300
301    /// Typecheck the window.
302    pub fn state_is<S: 'static>(&self, n: usize) -> bool {
303        self.core.type_id.borrow()[n] == TypeId::of::<S>()
304    }
305
306    /// Find top window with this type.
307    #[allow(clippy::manual_find)]
308    pub fn top<S: 'static>(&self) -> Option<usize> {
309        for n in (0..self.core.len.get()).rev() {
310            if self.core.type_id.borrow()[n] == TypeId::of::<S>() {
311                return Some(n);
312            }
313        }
314        None
315    }
316
317    /// Find all windows with this type.
318    pub fn find<S: 'static>(&self) -> Vec<usize> {
319        self.core
320            .type_id
321            .borrow()
322            .iter()
323            .enumerate()
324            .rev()
325            .filter_map(|(n, v)| {
326                if *v == TypeId::of::<S>() {
327                    Some(n)
328                } else {
329                    None
330                }
331            })
332            .collect()
333    }
334
335    /// Get a reference to the window at index n.
336    ///
337    /// Panic
338    ///
339    /// Panics when out-of-bounds.
340    /// Panics when recursively accessing the same state. Accessing a
341    /// *different* window-state is fine.
342    /// Panics when the types don't match.
343    pub fn get<'a, S: 'static>(&'a self, n: usize) -> Ref<'a, S> {
344        self.try_get(n).expect("recursion or wrong type")
345    }
346
347    /// Get a mutable reference to the window at index n.
348    ///
349    /// Panic
350    ///
351    /// Panics when out-of-bounds.
352    /// Panics when recursively accessing the same state. Accessing a
353    /// *different* window-state is fine.
354    /// Panics when the types don't match.
355    pub fn get_mut<'a, S: 'static>(&'a self, n: usize) -> RefMut<'a, S> {
356        self.try_get_mut(n).expect("recursion or wrong type")
357    }
358
359    /// Get a mutable reference to the window at index n.
360    ///
361    /// Panic
362    ///
363    /// Panics when out-of-bounds.
364    ///
365    /// Fails
366    ///
367    /// Fails when recursively accessing the same state. Accessing a
368    /// *different* window-state is fine.
369    /// Fails when the types don't match.
370    pub fn try_get_mut<'a, S: 'static>(&'a self, n: usize) -> Option<RefMut<'a, S>> {
371        let state = self.core.state.borrow_mut();
372
373        RefMut::filter_map(state, |v| {
374            let state = &mut v[n];
375            if let Some(state) = state.as_mut() {
376                if let Some(state) = (state.as_mut() as &mut dyn Any).downcast_mut::<S>() {
377                    Some(state)
378                } else {
379                    None
380                }
381            } else {
382                None
383            }
384        })
385        .ok()
386    }
387
388    /// Get a reference to the window at index n.
389    ///
390    /// Panic
391    ///
392    /// Panics when out-of-bounds.
393    ///
394    /// Fails
395    ///
396    /// Fails when recursively accessing the same state. Accessing a
397    /// *different* window-state is fine.
398    /// Fails when the types don't match.
399    pub fn try_get<'a, S: 'static>(&'a self, n: usize) -> Option<Ref<'a, S>> {
400        let state = self.core.state.borrow();
401
402        Ref::filter_map(state, |v| {
403            let state = &v[n];
404            if let Some(state) = state.as_ref() {
405                if let Some(state) = (state.as_ref() as &dyn Any).downcast_ref::<S>() {
406                    Some(state)
407                } else {
408                    None
409                }
410            } else {
411                None
412            }
413        })
414        .ok()
415    }
416}
417
418/// Handle events from top to bottom.
419///
420/// Panic
421///
422/// This function is not reentrant, it will panic when called from within it's call-stack.
423impl<Event, Context, Error> HandleEvent<Event, &mut Context, Result<WindowControl<Event>, Error>>
424    for WindowList<Event, Context, Error>
425where
426    Event: TryAsRef<crossterm::event::Event>,
427    Error: 'static,
428    Event: 'static,
429{
430    fn handle(&mut self, event: &Event, ctx: &mut Context) -> Result<WindowControl<Event>, Error> {
431        for n in (0..self.core.len.get()).rev() {
432            let (to_front, area) = {
433                let state = self.core.state.borrow();
434                let state = state[n].as_ref().expect("state is gone");
435                match event.try_as_ref() {
436                    Some(ct_event!(mouse down Left for x,y))
437                        if state.area().contains((*x, *y).into()) =>
438                    {
439                        (true, state.area())
440                    }
441                    _ => (false, state.area()),
442                }
443            };
444
445            let Some(mut state) = self.core.state.borrow_mut()[n].take() else {
446                panic!("state is gone");
447            };
448            let event_fn = mem::replace(
449                &mut self.core.event.borrow_mut()[n],
450                Box::new(|_, _, _| Ok(WindowControl::Continue)),
451            );
452
453            let r = event_fn(event, state.as_mut(), ctx);
454
455            self.core.event.borrow_mut()[n] = event_fn;
456            self.core.state.borrow_mut()[n] = Some(state);
457
458            if to_front {
459                self.to_front(n);
460            }
461
462            match r {
463                Ok(r) => match r {
464                    WindowControl::Close(_) => {
465                        self.close(n);
466                        return Ok(r);
467                    }
468                    WindowControl::Event(_) => {
469                        return Ok(r);
470                    }
471                    WindowControl::Unchanged => {
472                        return Ok(r);
473                    }
474                    WindowControl::Changed => {
475                        return Ok(r);
476                    }
477                    WindowControl::Continue => match event.try_as_ref() {
478                        Some(event) => {
479                            if mouse_trap(event, area).is_consumed() {
480                                return Ok(WindowControl::Unchanged);
481                            }
482                        }
483                        _ => {}
484                    },
485                },
486                Err(e) => return Err(e),
487            }
488        }
489
490        Ok(WindowControl::Continue)
491    }
492}
493
494/// Handle events from top to bottom of the stack.
495///
496/// Panic
497///
498/// This function is not reentrant, it will panic when called from within it's call-stack.
499pub fn handle_window_list<Event, Context, Error>(
500    mut window_list: WindowList<Event, Context, Error>,
501    event: &Event,
502    ctx: &mut Context,
503) -> Result<WindowControl<Event>, Error>
504where
505    Event: TryAsRef<crossterm::event::Event>,
506    Error: 'static,
507    Event: 'static,
508    Error: Debug,
509    Event: Debug,
510{
511    window_list.handle(event, ctx)
512}