lib.rs 10 KB

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