rat_dialog/
window_list.rs

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