ratatui_reactive/core/
app.rs1use 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 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 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}