use std::{ any::Any, cell::RefCell, io::Write, marker::PhantomData, rc::Rc, sync::mpsc::{channel, Receiver, Sender}, time::Duration, }; use crossterm::{ cursor, event::{Event, KeyCode, KeyEventKind}, execute, queue, terminal, }; use crate::{ container::{Callable, Container, FromContainer, Res, State}, context::ViewContext, view::View, }; use super::input::Keyboard; /// A renderer that can signal a render needs to take place. pub struct Renderer { tx: Sender<()>, } impl Renderer { pub fn render(&self) { let _ = self.tx.send(()); } } /// The app is the core container for the application logic, resources, /// state, and run loop. /// /// Setting up a basic application: /// /// ```no_run /// use arkham::prelude::*; /// /// fn main() { /// App::new(root_view).run(); /// } /// /// fn root_view(ctx: &mut ViewContext) { /// ctx.insert((2,2), "Hello World"); /// } /// ``` pub struct App where F: Callable, Args: FromContainer, { container: Rc>, main_view: View, render_signal: Receiver<()>, render_tx: Sender<()>, root: F, args: PhantomData, } impl App where F: Callable, Args: FromContainer, { /// Constructs a new App objcet. This object uses a builder pattern and /// should be finalized with App::run(). which will start a blocking run /// loop and perform the initial screen setup and render. pub fn new(root: F) -> App { let container = Rc::new(RefCell::new(Container::default())); let size = terminal::size().unwrap(); let main_view = View::new(size); let (render_tx, render_signal) = channel(); App { container, root, main_view, render_tx, render_signal, args: PhantomData, } } /// Returns a renderer that can signal the application to rerender. This /// renderer can be cloned and passed between threads. pub fn get_renderer(&self) -> Renderer { Renderer { tx: self.render_tx.clone(), } } /// Insert a resource which can be injected into component functions. /// /// This resource can only be accessed immutably by reference. /// Interior mutability must be used for anything that requires an internal /// state. /// /// Example: /// ```no_run /// use arkham::prelude::*; /// struct MyResource { /// value: i32 /// } /// /// fn main() { /// App::new(root).insert_resource(MyResource { value: 12 }); /// } /// /// fn root(ctx: &mut ViewContext, thing: Res) { /// ctx.insert(0,format!("Value is {}", thing.value)); /// } /// ```` /// Alternatively, App::insert_state can be used to insert a state object, /// that can be borrowed mutable. pub fn insert_resource(self, v: T) -> Self { self.bind_resource(Res::new(v)) } /// Bind an existing resource to the application /// /// Similar to `App::insert_resource` except it accepts an existing resource. pub fn bind_resource(self, v: Res) -> Self { self.container.borrow_mut().bind(v); self } /// Insert a stateful object that can be injected into component functions /// unlike App::insert_resource, this value can be borrowed mutably and /// is meant to store application state. /// /// Example: /// ```no_run /// use arkham::prelude::*; /// struct MyState { /// value: i32 /// } /// /// fn main() { /// App::new(root).insert_state(MyState { value: 12 }); /// } /// /// fn root(ctx: &mut ViewContext, thing: State) { /// thing.get_mut().value += 1; /// ctx.insert(0,format!("Value is {}", thing.get().value)); /// } /// ```` pub fn insert_state(self, v: T) -> Self { self.bind_state(State::new(v)) } /// Binds an existing state to the application. /// /// Similar to `App::insert_state` but will accept an existing state pub fn bind_state(self, v: State) -> Self { self.container.borrow_mut().bind(v); self } /// Repairs the terminal state so it operates properly. fn teardown(&self) { let mut out = std::io::stdout(); let _ = terminal::disable_raw_mode(); let _ = execute!(out, terminal::LeaveAlternateScreen, cursor::Show); } /// Executes the main run loop. This should be called to start the /// application logic. /// /// This function will block while it reads events and performs render /// cycles. pub fn run(&mut self) -> anyhow::Result<()> { self.container.borrow_mut().bind(Res::new(Terminal)); self.container.borrow_mut().bind(Res::new(Keyboard::new())); let _ = ctrlc::set_handler(|| { let mut out = std::io::stdout(); let _ = terminal::disable_raw_mode(); let _ = execute!(out, terminal::LeaveAlternateScreen, cursor::Show); std::process::exit(0); }); let mut out = std::io::stdout(); execute!(out, terminal::EnterAlternateScreen, cursor::Hide)?; terminal::enable_raw_mode()?; loop { loop { let mut context = ViewContext::new(self.container.clone(), terminal::size().unwrap().into()); self.root .call(&mut context, Args::from_container(&self.container.borrow())); self.main_view.apply((0, 0), &context.view); self.render()?; if !context.rerender { break; } } self.container .borrow() .get::>() .unwrap() .reset(); if crossterm::event::poll(Duration::from_millis(100)).unwrap_or(false) { if let Ok(event) = crossterm::event::read() { match event { Event::FocusGained => self.render()?, Event::FocusLost => {} Event::Key(key_event) if key_event.code == KeyCode::Char('q') => { break; } Event::Key(key_event) if key_event.kind == KeyEventKind::Press => { let container = self.container.borrow(); let kb = container.get::>().unwrap(); kb.set_key(key_event.code); kb.set_modifiers(key_event.modifiers); } Event::Mouse(_) => todo!(), Event::Paste(_) => todo!(), Event::Resize(_, _) => self.render()?, _ => {} } } } if self.render_signal.try_recv().is_ok() { self.render()?; } } self.teardown(); Ok(()) } fn render(&mut self) -> anyhow::Result<()> { let mut out = std::io::stdout(); for (row, line) in self.main_view.iter().enumerate() { for (col, rune) in line.iter().enumerate() { queue!(out, cursor::MoveTo(col as u16, row as u16))?; rune.render(&mut out)?; } } out.flush()?; Ok(()) } } pub struct Terminal; impl Terminal { pub fn set_title(&self, name: &str) { let _ = execute!(std::io::stdout(), terminal::SetTitle(name)); } pub fn size(&self) -> (u16, u16) { crossterm::terminal::size().unwrap_or_default() } } #[cfg(test)] mod tests { #[cfg(feature = "sync")] #[test] fn test_threaded_state() { use crate::prelude::{App, State, ViewContext}; #[derive(Default)] struct S { i: i32, } let root_view = |_: &mut ViewContext| {}; let state = State::new(S::default()); App::new(root_view).bind_state(state.clone()); is_send(state); } #[allow(dead_code)] fn is_send(v: impl Send) {} }