lib.rs 9.7 KB

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