lib.rs 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275
  1. use anyhow::Result;
  2. use anymap::AnyMap;
  3. use crossterm::{
  4. event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers},
  5. execute,
  6. terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
  7. };
  8. use dioxus_core::exports::futures_channel::mpsc::unbounded;
  9. use dioxus_core::*;
  10. use dioxus_native_core::real_dom::RealDom;
  11. use focus::FocusState;
  12. use futures::{
  13. channel::mpsc::{UnboundedReceiver, UnboundedSender},
  14. pin_mut, StreamExt,
  15. };
  16. use query::Query;
  17. use std::cell::RefCell;
  18. use std::rc::Rc;
  19. use std::{io, time::Duration};
  20. use taffy::Taffy;
  21. pub use taffy::{geometry::Point, prelude::Size};
  22. use tui::{backend::CrosstermBackend, layout::Rect, Terminal};
  23. mod config;
  24. mod focus;
  25. mod hooks;
  26. mod layout;
  27. mod node;
  28. pub mod query;
  29. mod render;
  30. mod style;
  31. mod style_attributes;
  32. mod widget;
  33. pub use config::*;
  34. pub use hooks::*;
  35. pub(crate) use node::*;
  36. #[derive(Clone)]
  37. pub struct TuiContext {
  38. tx: UnboundedSender<InputEvent>,
  39. }
  40. impl TuiContext {
  41. pub fn quit(&self) {
  42. self.tx.unbounded_send(InputEvent::Close).unwrap();
  43. }
  44. pub fn inject_event(&self, event: crossterm::event::Event) {
  45. self.tx
  46. .unbounded_send(InputEvent::UserInput(event))
  47. .unwrap();
  48. }
  49. }
  50. pub fn launch(app: Component<()>) {
  51. launch_cfg(app, Config::default())
  52. }
  53. pub fn launch_cfg(app: Component<()>, cfg: Config) {
  54. let mut dom = VirtualDom::new(app);
  55. let (handler, state, register_event) = RinkInputHandler::new();
  56. // Setup input handling
  57. let (event_tx, event_rx) = unbounded();
  58. let event_tx_clone = event_tx.clone();
  59. if !cfg.headless {
  60. std::thread::spawn(move || {
  61. let tick_rate = Duration::from_millis(1000);
  62. loop {
  63. if crossterm::event::poll(tick_rate).unwrap() {
  64. let evt = crossterm::event::read().unwrap();
  65. if event_tx.unbounded_send(InputEvent::UserInput(evt)).is_err() {
  66. break;
  67. }
  68. }
  69. }
  70. });
  71. }
  72. let cx = dom.base_scope();
  73. let rdom = Rc::new(RefCell::new(RealDom::new()));
  74. let taffy = Rc::new(RefCell::new(Taffy::new()));
  75. cx.provide_root_context(state);
  76. cx.provide_root_context(TuiContext { tx: event_tx_clone });
  77. cx.provide_root_context(Query {
  78. rdom: rdom.clone(),
  79. stretch: taffy.clone(),
  80. });
  81. {
  82. let mut rdom = rdom.borrow_mut();
  83. let mutations = dom.rebuild();
  84. let to_update = rdom.apply_mutations(vec![mutations]);
  85. let mut any_map = AnyMap::new();
  86. any_map.insert(taffy.clone());
  87. let _to_rerender = rdom.update_state(&dom, to_update, any_map);
  88. }
  89. render_vdom(
  90. &mut dom,
  91. event_rx,
  92. handler,
  93. cfg,
  94. rdom,
  95. taffy,
  96. register_event,
  97. )
  98. .unwrap();
  99. }
  100. fn render_vdom(
  101. vdom: &mut VirtualDom,
  102. mut event_reciever: UnboundedReceiver<InputEvent>,
  103. handler: RinkInputHandler,
  104. cfg: Config,
  105. rdom: Rc<RefCell<Dom>>,
  106. taffy: Rc<RefCell<Taffy>>,
  107. mut register_event: impl FnMut(crossterm::event::Event),
  108. ) -> Result<()> {
  109. tokio::runtime::Builder::new_current_thread()
  110. .enable_all()
  111. .build()?
  112. .block_on(async {
  113. let mut terminal = (!cfg.headless).then(|| {
  114. enable_raw_mode().unwrap();
  115. let mut stdout = std::io::stdout();
  116. execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap();
  117. let backend = CrosstermBackend::new(io::stdout());
  118. Terminal::new(backend).unwrap()
  119. });
  120. if let Some(terminal) = &mut terminal {
  121. terminal.clear().unwrap();
  122. }
  123. let mut to_rerender: fxhash::FxHashSet<ElementId> =
  124. vec![ElementId(0)].into_iter().collect();
  125. let mut updated = true;
  126. loop {
  127. /*
  128. -> render the nodes in the right place with tui/crossterm
  129. -> wait for changes
  130. -> resolve events
  131. -> lazily update the layout and style based on nodes changed
  132. use simd to compare lines for diffing?
  133. todo: lazy re-rendering
  134. */
  135. if !to_rerender.is_empty() || updated {
  136. updated = false;
  137. fn resize(dims: Rect, taffy: &mut Taffy, rdom: &Dom) {
  138. let width = dims.width;
  139. let height = dims.height;
  140. let root_node = rdom[ElementId(0)].state.layout.node.unwrap();
  141. taffy
  142. .compute_layout(
  143. root_node,
  144. Size {
  145. width: taffy::prelude::Number::Defined((width - 1) as f32),
  146. height: taffy::prelude::Number::Defined((height - 1) as f32),
  147. },
  148. )
  149. .unwrap();
  150. }
  151. if let Some(terminal) = &mut terminal {
  152. terminal.draw(|frame| {
  153. let rdom = rdom.borrow();
  154. // size is guaranteed to not change when rendering
  155. resize(frame.size(), &mut taffy.borrow_mut(), &rdom);
  156. let root = &rdom[ElementId(0)];
  157. render::render_vnode(
  158. frame,
  159. &taffy.borrow(),
  160. &rdom,
  161. root,
  162. cfg,
  163. Point::zero(),
  164. );
  165. })?;
  166. } else {
  167. let rdom = rdom.borrow();
  168. resize(
  169. Rect {
  170. x: 0,
  171. y: 0,
  172. width: 100,
  173. height: 100,
  174. },
  175. &mut taffy.borrow_mut(),
  176. &rdom,
  177. );
  178. }
  179. }
  180. use futures::future::{select, Either};
  181. {
  182. let wait = vdom.wait_for_work();
  183. pin_mut!(wait);
  184. match select(wait, event_reciever.next()).await {
  185. Either::Left((_a, _b)) => {
  186. //
  187. }
  188. Either::Right((evt, _o)) => {
  189. match evt.as_ref().unwrap() {
  190. InputEvent::UserInput(event) => match event {
  191. TermEvent::Key(key) => {
  192. if matches!(key.code, KeyCode::Char('C' | 'c'))
  193. && key.modifiers.contains(KeyModifiers::CONTROL)
  194. && cfg.ctrl_c_quit
  195. {
  196. break;
  197. }
  198. }
  199. TermEvent::Resize(_, _) => updated = true,
  200. TermEvent::Mouse(_) => {}
  201. },
  202. InputEvent::Close => break,
  203. };
  204. if let InputEvent::UserInput(evt) = evt.unwrap() {
  205. register_event(evt);
  206. }
  207. }
  208. }
  209. }
  210. {
  211. let evts = {
  212. let mut rdom = rdom.borrow_mut();
  213. handler.get_events(&taffy.borrow(), &mut rdom)
  214. };
  215. {
  216. updated |= handler.state().focus_state.clean();
  217. }
  218. for e in evts {
  219. vdom.handle_message(SchedulerMsg::Event(e));
  220. }
  221. let mut rdom = rdom.borrow_mut();
  222. let mutations = vdom.work_with_deadline(|| false);
  223. for m in &mutations {
  224. handler.prune(m, &rdom);
  225. }
  226. // updates the dom's nodes
  227. let to_update = rdom.apply_mutations(mutations);
  228. // update the style and layout
  229. let mut any_map = AnyMap::new();
  230. any_map.insert(taffy.clone());
  231. to_rerender = rdom.update_state(vdom, to_update, any_map);
  232. }
  233. }
  234. if let Some(terminal) = &mut terminal {
  235. disable_raw_mode()?;
  236. execute!(
  237. terminal.backend_mut(),
  238. LeaveAlternateScreen,
  239. DisableMouseCapture
  240. )?;
  241. terminal.show_cursor()?;
  242. }
  243. Ok(())
  244. })
  245. }
  246. #[derive(Debug)]
  247. enum InputEvent {
  248. UserInput(TermEvent),
  249. Close,
  250. }