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 mutable reference to the window at index n.
355    ///
356    /// Panic
357    ///
358    /// Panics when out-of-bounds.
359    ///
360    /// Fails
361    ///
362    /// Fails when recursively accessing the same state. Accessing a
363    /// *different* window-state is fine.
364    /// Fails when the types don't match.
365    pub fn get_mut<'a, S: 'static>(&'a self, n: usize) -> Option<RefMut<'a, S>> {
366        let state = self.core.state.borrow_mut();
367
368        RefMut::filter_map(state, |v| {
369            let state = &mut v[n];
370            if let Some(state) = state.as_mut() {
371                if let Some(state) = (state.as_mut() as &mut dyn Any).downcast_mut::<S>() {
372                    Some(state)
373                } else {
374                    None
375                }
376            } else {
377                None
378            }
379        })
380        .ok()
381    }
382
383    /// Get a reference to the window at index n.
384    ///
385    /// Panic
386    ///
387    /// Panics when out-of-bounds.
388    ///
389    /// Fails
390    ///
391    /// Fails when recursively accessing the same state. Accessing a
392    /// *different* window-state is fine.
393    /// Fails when the types don't match.
394    pub fn get<'a, S: 'static>(&'a self, n: usize) -> Option<Ref<'a, S>> {
395        let state = self.core.state.borrow();
396
397        Ref::filter_map(state, |v| {
398            let state = &v[n];
399            if let Some(state) = state.as_ref() {
400                if let Some(state) = (state.as_ref() as &dyn Any).downcast_ref::<S>() {
401                    Some(state)
402                } else {
403                    None
404                }
405            } else {
406                None
407            }
408        })
409        .ok()
410    }
411}
412
413/// Handle events from top to bottom.
414///
415/// Panic
416///
417/// This function is not reentrant, it will panic when called from within it's call-stack.
418impl<Event, Context, Error> HandleEvent<Event, &mut Context, Result<WindowControl<Event>, Error>>
419    for WindowList<Event, Context, Error>
420where
421    Event: TryAsRef<crossterm::event::Event>,
422    Error: 'static,
423    Event: 'static,
424    Context: 'static,
425{
426    fn handle(&mut self, event: &Event, ctx: &mut Context) -> Result<WindowControl<Event>, Error> {
427        let mut r_front = WindowControl::Continue;
428
429        for n in (0..self.core.len.get()).rev() {
430            let (to_front, area) = {
431                let state = self.core.state.borrow();
432                let state = state[n].as_ref().expect("state is gone");
433                match event.try_as_ref() {
434                    Some(ct_event!(mouse down Left for x,y))
435                        if state.area().contains((*x, *y).into()) =>
436                    {
437                        (true, state.area())
438                    }
439                    _ => (false, state.area()),
440                }
441            };
442
443            let Some(mut state) = self.core.state.borrow_mut()[n].take() else {
444                panic!("state is gone");
445            };
446            let event_fn = mem::replace(
447                &mut self.core.event.borrow_mut()[n],
448                Box::new(|_, _, _| Ok(WindowControl::Continue)),
449            );
450
451            let r = event_fn(event, state.as_mut(), ctx);
452
453            self.core.event.borrow_mut()[n] = event_fn;
454            self.core.state.borrow_mut()[n] = Some(state);
455
456            if to_front {
457                self.to_front(n, ctx);
458                r_front = WindowControl::Changed;
459            };
460
461            match r {
462                Ok(r) => match r {
463                    WindowControl::Close(_) => {
464                        self.close(n, ctx);
465                        return Ok(max(r, r_front));
466                    }
467                    WindowControl::Event(_) => {
468                        return Ok(max(r, r_front));
469                    }
470                    WindowControl::Unchanged => {
471                        return Ok(max(r, r_front));
472                    }
473                    WindowControl::Changed => {
474                        return Ok(max(r, r_front));
475                    }
476                    WindowControl::Continue => {
477                        if let Some(event) = event.try_as_ref() {
478                            if mouse_trap(event, area).is_consumed() {
479                                return Ok(max(WindowControl::Unchanged, r_front));
480                            }
481                        }
482                    }
483                },
484                Err(e) => return Err(e),
485            }
486        }
487
488        Ok(r_front)
489    }
490}
491
492fn max<Event>(
493    primary: WindowControl<Event>,
494    secondary: WindowControl<Event>,
495) -> WindowControl<Event> {
496    let s = match &primary {
497        WindowControl::Continue => 0,
498        WindowControl::Unchanged => 1,
499        WindowControl::Changed => 2,
500        WindowControl::Event(_) => 3,
501        WindowControl::Close(_) => 4,
502    };
503    let t = match &secondary {
504        WindowControl::Continue => 0,
505        WindowControl::Unchanged => 1,
506        WindowControl::Changed => 2,
507        WindowControl::Event(_) => 3,
508        WindowControl::Close(_) => 4,
509    };
510    if s > t { primary } else { secondary }
511}
512
513/// Handle events from top to bottom of the stack.
514///
515/// Panic
516///
517/// This function is not reentrant, it will panic when called from within it's call-stack.
518pub fn handle_window_list<Event, Context, Error>(
519    mut window_list: WindowList<Event, Context, Error>,
520    event: &Event,
521    ctx: &mut Context,
522) -> Result<WindowControl<Event>, Error>
523where
524    Event: TryAsRef<crossterm::event::Event>,
525    Error: 'static,
526    Event: 'static,
527    Error: Debug,
528    Event: Debug,
529    Context: 'static,
530{
531    window_list.handle(event, ctx)
532}