Skip to main content

ratatui_reactive/core/
app.rs

1use crate::{Component, Render};
2use async_local_channel::watch;
3use futures_lite::FutureExt;
4use ratatui::Frame;
5use std::mem;
6use sycamore_reactive::{
7    RootHandle, Signal, create_effect, create_root, create_signal, provide_context,
8};
9
10#[derive(Debug, Clone, Copy)]
11pub struct Runtime {
12    request_draw: Signal<()>,
13    quit: Signal<()>,
14}
15
16impl Runtime {
17    #[inline]
18    pub fn quit(&self) {
19        self.quit.set(());
20    }
21
22    #[inline]
23    pub fn request_draw(&self) {
24        self.request_draw.set(());
25    }
26}
27
28pub struct ReactiveApp {
29    root: RootHandle,
30    request_draw_rx: watch::Receiver<()>,
31    quit_rx: watch::Receiver<()>,
32    current_frame: Signal<Option<*mut Frame<'static>>>,
33}
34
35impl ReactiveApp {
36    #[inline]
37    pub fn new<R: Render + 'static, C: Component<R>>(component: C) -> ReactiveApp {
38        let (request_draw_tx, request_draw_rx) = watch::channel();
39        let (quit_tx, quit_rx) = watch::channel();
40        let root = create_root(move || ());
41
42        let runtime = root.run_in(move || {
43            let request_draw = create_signal(());
44            create_effect(move || {
45                request_draw.track();
46                request_draw_tx.send(()).unwrap();
47            });
48
49            let quit = create_signal(());
50            create_effect(move || {
51                quit.track();
52                quit_tx.send(()).unwrap();
53            });
54
55            Runtime { request_draw, quit }
56        });
57
58        let request_draw_rx = request_draw_rx.activate();
59        let quit_rx = quit_rx.activate();
60
61        let current_frame = root.run_in(move || {
62            let current_frame: Signal<Option<*mut Frame>> = create_signal(None);
63            provide_context(runtime);
64            let app = component.create();
65            create_effect(move || {
66                current_frame.track();
67                if let Some(current_frame) = current_frame.take_silent() {
68                    // SAFETY: we set this frame once every `draw`
69                    let frame = unsafe { &mut *current_frame };
70                    app.render(frame.area(), frame.buffer_mut())
71                } else {
72                    runtime.request_draw();
73                }
74            });
75            current_frame
76        });
77
78        ReactiveApp {
79            root,
80            request_draw_rx,
81            quit_rx,
82            current_frame,
83        }
84    }
85
86    #[inline]
87    pub fn draw(&self, frame: &mut Frame) {
88        // SAFETY: this will trigger exactly one `render` call
89        let frame = unsafe { mem::transmute(frame) };
90        self.root
91            .run_in(move || self.current_frame.set(Some(frame)))
92    }
93
94    async fn on_quit(&self) -> bool {
95        self.quit_rx.recv().await.unwrap();
96        self.root.dispose();
97        false
98    }
99
100    async fn on_draw_requested(&self) -> bool {
101        self.request_draw_rx.recv().await.unwrap();
102        true
103    }
104
105    #[inline]
106    pub async fn draw_requested(&self) -> bool {
107        self.on_quit().or(self.on_draw_requested()).await
108    }
109}
110
111impl Drop for ReactiveApp {
112    #[inline]
113    fn drop(&mut self) {
114        self.root.dispose();
115    }
116}