kano_tui/
lib.rs

1//! Kano is a work-in-progress GUI application framework written for and in Rust.
2use component::ComponentData;
3use crossterm::{
4    event::{self, DisableMouseCapture, KeyCode, KeyEventKind},
5    terminal::{self, EnterAlternateScreen, LeaveAlternateScreen},
6    ExecutableCommand,
7};
8use kano::Diff;
9use node::{new_node_id, Node, NodeKind, NodeRef};
10use ratatui::prelude::{CrosstermBackend, Terminal};
11use std::{
12    cell::RefCell,
13    io::{self, stdout},
14    panic,
15    rc::Rc,
16};
17
18pub mod component;
19pub mod node;
20
21pub use ratatui;
22
23pub struct Tui;
24
25impl kano::platform::Platform for Tui {
26    type Cursor = TuiCursor;
27
28    fn log(_s: &str) {}
29
30    fn run_app<V: kano::View<Self>, F: (FnOnce() -> V) + 'static>(func: F) -> anyhow::Result<()> {
31        stdout().execute(EnterAlternateScreen)?;
32        terminal::enable_raw_mode()?;
33
34        let panic_hook = panic::take_hook();
35        panic::set_hook(Box::new(move |panic| {
36            reset_terminal().expect("failed to reset the terminal");
37            panic_hook(panic);
38        }));
39
40        let mut terminal = Terminal::new(CrosstermBackend::new(stdout()))?;
41        terminal.clear()?;
42
43        let (mut cursor, empty_root) = TuiCursor::new_root();
44        let state = kano::view::Func(func, ()).init(&mut cursor);
45        std::mem::forget(state);
46
47        let root_node = empty_root.first_child().unwrap();
48
49        loop {
50            terminal.draw(|frame| {
51                let area = frame.size();
52                root_node.clone().render(frame, area);
53            })?;
54
55            if event::poll(std::time::Duration::from_millis(16))? {
56                if let event::Event::Key(key) = event::read()? {
57                    if key.kind == KeyEventKind::Press && key.code == KeyCode::Char('q') {
58                        break;
59                    }
60                }
61            }
62        }
63
64        stdout().execute(LeaveAlternateScreen)?;
65        reset_terminal()?;
66        Ok(())
67    }
68
69    fn spawn_task(_task: impl std::future::Future<Output = ()> + 'static) {
70        todo!();
71    }
72}
73
74fn reset_terminal() -> anyhow::Result<()> {
75    terminal::disable_raw_mode()?;
76    crossterm::execute!(io::stderr(), LeaveAlternateScreen, DisableMouseCapture)?;
77    Ok(())
78}
79
80#[derive(Clone, Debug)]
81pub struct TuiCursor {
82    location: Location,
83    mode: Mode,
84}
85
86#[derive(Clone, Debug)]
87enum Location {
88    Detached,
89    Node(NodeRef),
90    EndOfChildren(NodeRef),
91}
92
93#[derive(Clone, Debug)]
94enum Mode {
95    Append,
96    Diff,
97}
98
99impl TuiCursor {
100    fn new_detached() -> Self {
101        Self {
102            location: Location::Detached,
103            mode: Mode::Append,
104        }
105    }
106
107    fn new_root() -> (Self, NodeRef) {
108        let root = NodeRef(Rc::new(RefCell::new(Node {
109            id: new_node_id(),
110            kind: NodeKind::Empty,
111            parent: None,
112            first_child: None,
113            next_sibling: None,
114        })));
115        (
116            Self {
117                location: Location::EndOfChildren(root.clone()),
118                mode: Mode::Append,
119            },
120            root,
121        )
122    }
123
124    fn set_component(&mut self, component: Rc<ComponentData>) {
125        self.set_node(NodeKind::Component(component));
126    }
127
128    fn set_node(&mut self, kind: NodeKind) {
129        match (&self.mode, &self.location) {
130            (Mode::Append, Location::Detached) => {
131                let node = Rc::new(RefCell::new(Node {
132                    id: new_node_id(),
133                    kind,
134                    parent: None,
135                    first_child: None,
136                    next_sibling: None,
137                }));
138
139                self.location = Location::Node(NodeRef(node));
140            }
141            (Mode::Append, Location::Node(node)) => {
142                node.append_sibling(kind);
143                self.location = Location::Node(node.next_sibling().unwrap());
144            }
145            (Mode::Append, Location::EndOfChildren(parent)) => {
146                let node = Rc::new(RefCell::new(Node {
147                    id: new_node_id(),
148                    kind,
149                    parent: Some(Rc::downgrade(&parent.0)),
150                    first_child: None,
151                    next_sibling: None,
152                }));
153
154                if let Some(mut child) = parent.first_child() {
155                    // This is a little inefficient
156                    while let Some(next) = child.next_sibling() {
157                        child = next;
158                    }
159
160                    child.0.borrow_mut().next_sibling = Some(NodeRef(node.clone()));
161                } else {
162                    parent.0.borrow_mut().first_child = Some(NodeRef(node.clone()));
163                }
164
165                self.location = Location::Node(NodeRef(node));
166            }
167            other => todo!("{other:?}"),
168        }
169    }
170
171    fn current_node(&self) -> NodeRef {
172        match &self.location {
173            Location::Node(node) => node.clone(),
174            _ => panic!(),
175        }
176    }
177}
178
179impl kano::platform::Cursor for TuiCursor {
180    type TextHandle = NodeRef;
181    type EventHandle = ();
182
183    fn from_text_handle(handle: &NodeRef) -> Self {
184        Self {
185            location: Location::Node(handle.clone()),
186            mode: Mode::Append,
187        }
188    }
189
190    fn empty(&mut self) {
191        self.set_node(NodeKind::Empty);
192    }
193
194    fn text(&mut self, text: &str) -> Self::TextHandle {
195        self.set_node(NodeKind::Text(text.into()));
196        self.current_node()
197    }
198
199    fn update_text(&mut self, new_text: &str) {
200        match &mut self.location {
201            Location::Node(node) => {
202                let mut borrow = node.0.borrow_mut();
203                match &mut borrow.kind {
204                    NodeKind::Text(text) => {
205                        *text = new_text.into();
206                    }
207                    _ => {}
208                }
209            }
210            _ => panic!(),
211        }
212    }
213
214    fn on_event(&mut self, _event: kano::On) -> () {}
215
216    fn enter_children(&mut self) {
217        match &self.location {
218            Location::Node(node) => {
219                self.location = match node.first_child() {
220                    Some(first_child) => Location::Node(first_child),
221                    None => Location::EndOfChildren(node.clone()),
222                }
223            }
224            other => panic!("{other:?}"),
225        }
226    }
227
228    fn exit_children(&mut self) {
229        match &self.location {
230            Location::Node(node) => {
231                self.location = Location::Node(node.parent().unwrap());
232            }
233            Location::EndOfChildren(parent) => {
234                self.location = Location::Node(parent.clone());
235            }
236            _ => panic!(),
237        }
238    }
239
240    fn next_sibling(&mut self) {
241        match &self.location {
242            Location::Node(node) => {
243                self.location = match node.next_sibling() {
244                    Some(next) => Location::Node(next),
245                    None => match node.parent() {
246                        Some(parent) => Location::EndOfChildren(parent),
247                        None => Location::Node(node.clone()),
248                    },
249                }
250            }
251            Location::EndOfChildren(_) => {}
252            _ => panic!(),
253        }
254    }
255
256    fn remove(&mut self) {
257        match &self.location {
258            Location::Node(node) => {
259                let id = node.id();
260
261                let mut prev_sibling: Option<NodeRef> = None;
262
263                if let Some(mut child) = node.parent().and_then(|parent| parent.first_child()) {
264                    loop {
265                        if child.id() == id {
266                            if let Some(prev_sibling) = prev_sibling {
267                                prev_sibling.0.borrow_mut().next_sibling = child.next_sibling();
268                            } else {
269                                node.parent().unwrap().0.borrow_mut().first_child =
270                                    child.next_sibling();
271                            }
272                            return;
273                        } else if let Some(next_sibling) = child.next_sibling() {
274                            prev_sibling = Some(child);
275                            child = next_sibling;
276                        } else {
277                            return;
278                        }
279                    }
280                }
281            }
282            Location::EndOfChildren(_) => {}
283            _ => panic!(),
284        }
285    }
286
287    fn enter_diff(&mut self) {
288        self.mode = Mode::Diff;
289    }
290
291    fn exit_diff(&mut self) {
292        self.mode = Mode::Append;
293    }
294
295    fn replace(&mut self, func: impl FnOnce(&mut Self)) {
296        let mut replacement_cursor = Self::new_detached();
297        func(&mut replacement_cursor);
298
299        let Location::Node(node) = replacement_cursor.location else {
300            panic!();
301        };
302
303        let kind = node.0.borrow().kind.clone();
304
305        match &self.location {
306            Location::Node(node) => {
307                node.0.borrow_mut().kind = kind;
308            }
309            _ => panic!(),
310        }
311    }
312}