lib.rs 9.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277
  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(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: rustc_hash::FxHashSet<GlobalNodeId> =
  124. vec![GlobalNodeId::VNodeId(ElementId(0))]
  125. .into_iter()
  126. .collect();
  127. let mut updated = true;
  128. loop {
  129. /*
  130. -> render the nodes in the right place with tui/crossterm
  131. -> wait for changes
  132. -> resolve events
  133. -> lazily update the layout and style based on nodes changed
  134. use simd to compare lines for diffing?
  135. todo: lazy re-rendering
  136. */
  137. if !to_rerender.is_empty() || updated {
  138. updated = false;
  139. fn resize(dims: Rect, taffy: &mut Taffy, rdom: &Dom) {
  140. let width = dims.width;
  141. let height = dims.height;
  142. let root_node = rdom[ElementId(0)].state.layout.node.unwrap();
  143. taffy
  144. .compute_layout(
  145. root_node,
  146. Size {
  147. width: taffy::prelude::Number::Defined((width - 1) as f32),
  148. height: taffy::prelude::Number::Defined((height - 1) as f32),
  149. },
  150. )
  151. .unwrap();
  152. }
  153. if let Some(terminal) = &mut terminal {
  154. terminal.draw(|frame| {
  155. let rdom = rdom.borrow();
  156. // size is guaranteed to not change when rendering
  157. resize(frame.size(), &mut taffy.borrow_mut(), &rdom);
  158. let root = &rdom[ElementId(0)];
  159. render::render_vnode(
  160. frame,
  161. &taffy.borrow(),
  162. &rdom,
  163. root,
  164. cfg,
  165. Point::zero(),
  166. );
  167. })?;
  168. } else {
  169. let rdom = rdom.borrow();
  170. resize(
  171. Rect {
  172. x: 0,
  173. y: 0,
  174. width: 100,
  175. height: 100,
  176. },
  177. &mut taffy.borrow_mut(),
  178. &rdom,
  179. );
  180. }
  181. }
  182. use futures::future::{select, Either};
  183. {
  184. let wait = vdom.wait_for_work();
  185. pin_mut!(wait);
  186. match select(wait, event_reciever.next()).await {
  187. Either::Left((_a, _b)) => {
  188. //
  189. }
  190. Either::Right((evt, _o)) => {
  191. match evt.as_ref().unwrap() {
  192. InputEvent::UserInput(event) => match event {
  193. TermEvent::Key(key) => {
  194. if matches!(key.code, KeyCode::Char('C' | 'c'))
  195. && key.modifiers.contains(KeyModifiers::CONTROL)
  196. && cfg.ctrl_c_quit
  197. {
  198. break;
  199. }
  200. }
  201. TermEvent::Resize(_, _) => updated = true,
  202. TermEvent::Mouse(_) => {}
  203. },
  204. InputEvent::Close => break,
  205. };
  206. if let InputEvent::UserInput(evt) = evt.unwrap() {
  207. register_event(evt);
  208. }
  209. }
  210. }
  211. }
  212. {
  213. let evts = {
  214. let mut rdom = rdom.borrow_mut();
  215. handler.get_events(&taffy.borrow(), &mut rdom)
  216. };
  217. {
  218. updated |= handler.state().focus_state.clean();
  219. }
  220. for e in evts {
  221. vdom.handle_message(SchedulerMsg::Event(e));
  222. }
  223. let mut rdom = rdom.borrow_mut();
  224. let mutations = vdom.work_with_deadline(|| false);
  225. for m in &mutations {
  226. handler.prune(m, &rdom);
  227. }
  228. // updates the dom's nodes
  229. let to_update = rdom.apply_mutations(mutations);
  230. // update the style and layout
  231. let mut any_map = AnyMap::new();
  232. any_map.insert(taffy.clone());
  233. to_rerender = rdom.update_state(to_update, any_map);
  234. }
  235. }
  236. if let Some(terminal) = &mut terminal {
  237. disable_raw_mode()?;
  238. execute!(
  239. terminal.backend_mut(),
  240. LeaveAlternateScreen,
  241. DisableMouseCapture
  242. )?;
  243. terminal.show_cursor()?;
  244. }
  245. Ok(())
  246. })
  247. }
  248. #[derive(Debug)]
  249. enum InputEvent {
  250. UserInput(TermEvent),
  251. Close,
  252. }