lib.rs 8.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250
  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::{dioxus_native_core_macro::State, real_dom::RealDom, state::*};
  11. use futures::{
  12. channel::mpsc::{UnboundedReceiver, UnboundedSender},
  13. pin_mut, StreamExt,
  14. };
  15. use layout::StretchLayout;
  16. use std::cell::RefCell;
  17. use std::rc::Rc;
  18. use std::{io, time::Duration};
  19. use stretch2::{prelude::Size, Stretch};
  20. use style_attributes::StyleModifier;
  21. use tui::{backend::CrosstermBackend, layout::Rect, Terminal};
  22. mod config;
  23. mod hooks;
  24. mod layout;
  25. mod render;
  26. mod style;
  27. mod style_attributes;
  28. mod widget;
  29. pub use config::*;
  30. pub use hooks::*;
  31. type Dom = RealDom<NodeState>;
  32. type Node = dioxus_native_core::real_dom::Node<NodeState>;
  33. #[derive(Debug, Clone, State, Default)]
  34. struct NodeState {
  35. #[child_dep_state(StretchLayout, RefCell<Stretch>)]
  36. layout: StretchLayout,
  37. // depends on attributes, the C component of it's parent and a u8 context
  38. #[parent_dep_state(StyleModifier)]
  39. style: StyleModifier,
  40. }
  41. #[derive(Clone)]
  42. pub struct TuiContext {
  43. tx: UnboundedSender<InputEvent>,
  44. }
  45. impl TuiContext {
  46. pub fn quit(&self) {
  47. self.tx.unbounded_send(InputEvent::Close).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. // if crossterm::event::poll(timeout).unwrap() {
  65. let evt = crossterm::event::read().unwrap();
  66. if event_tx.unbounded_send(InputEvent::UserInput(evt)).is_err() {
  67. break;
  68. }
  69. }
  70. }
  71. });
  72. }
  73. let cx = dom.base_scope();
  74. cx.provide_root_context(state);
  75. cx.provide_root_context(TuiContext { tx: event_tx_clone });
  76. let mut rdom: Dom = RealDom::new();
  77. let mutations = dom.rebuild();
  78. let to_update = rdom.apply_mutations(vec![mutations]);
  79. let stretch = Rc::new(RefCell::new(Stretch::new()));
  80. let mut any_map = AnyMap::new();
  81. any_map.insert(stretch.clone());
  82. let _to_rerender = rdom.update_state(&dom, to_update, any_map).unwrap();
  83. render_vdom(
  84. &mut dom,
  85. event_rx,
  86. handler,
  87. cfg,
  88. rdom,
  89. stretch,
  90. register_event,
  91. )
  92. .unwrap();
  93. }
  94. fn render_vdom(
  95. vdom: &mut VirtualDom,
  96. mut event_reciever: UnboundedReceiver<InputEvent>,
  97. handler: RinkInputHandler,
  98. cfg: Config,
  99. mut rdom: Dom,
  100. stretch: Rc<RefCell<Stretch>>,
  101. mut register_event: impl FnMut(crossterm::event::Event),
  102. ) -> Result<()> {
  103. tokio::runtime::Builder::new_current_thread()
  104. .enable_all()
  105. .build()?
  106. .block_on(async {
  107. let mut terminal = (!cfg.headless).then(|| {
  108. enable_raw_mode().unwrap();
  109. let mut stdout = std::io::stdout();
  110. execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap();
  111. let backend = CrosstermBackend::new(io::stdout());
  112. Terminal::new(backend).unwrap()
  113. });
  114. if let Some(terminal) = &mut terminal {
  115. terminal.clear().unwrap();
  116. }
  117. let to_rerender: fxhash::FxHashSet<usize> = vec![0].into_iter().collect();
  118. let mut resized = true;
  119. loop {
  120. /*
  121. -> render the nodes in the right place with tui/crossterm
  122. -> wait for changes
  123. -> resolve events
  124. -> lazily update the layout and style based on nodes changed
  125. use simd to compare lines for diffing?
  126. todo: lazy re-rendering
  127. */
  128. if !to_rerender.is_empty() || resized {
  129. resized = false;
  130. fn resize(dims: Rect, stretch: &mut Stretch, rdom: &Dom) {
  131. let width = dims.width;
  132. let height = dims.height;
  133. let root_node = rdom[0].state.layout.node.unwrap();
  134. stretch
  135. .compute_layout(
  136. root_node,
  137. Size {
  138. width: stretch2::prelude::Number::Defined((width - 1) as f32),
  139. height: stretch2::prelude::Number::Defined((height - 1) as f32),
  140. },
  141. )
  142. .unwrap();
  143. }
  144. if let Some(terminal) = &mut terminal {
  145. terminal.draw(|frame| {
  146. // size is guaranteed to not change when rendering
  147. resize(frame.size(), &mut stretch.borrow_mut(), &rdom);
  148. let root = &rdom[0];
  149. render::render_vnode(frame, &stretch.borrow(), &rdom, &root, cfg);
  150. })?;
  151. } else {
  152. resize(
  153. Rect {
  154. x: 0,
  155. y: 0,
  156. width: 300,
  157. height: 300,
  158. },
  159. &mut stretch.borrow_mut(),
  160. &rdom,
  161. );
  162. }
  163. }
  164. use futures::future::{select, Either};
  165. {
  166. let wait = vdom.wait_for_work();
  167. pin_mut!(wait);
  168. match select(wait, event_reciever.next()).await {
  169. Either::Left((_a, _b)) => {
  170. //
  171. }
  172. Either::Right((evt, _o)) => {
  173. match evt.as_ref().unwrap() {
  174. InputEvent::UserInput(event) => match event {
  175. TermEvent::Key(key) => {
  176. if matches!(key.code, KeyCode::Char('C' | 'c'))
  177. && key.modifiers.contains(KeyModifiers::CONTROL)
  178. && cfg.ctrl_c_quit
  179. {
  180. break;
  181. }
  182. }
  183. TermEvent::Resize(_, _) => resized = true,
  184. TermEvent::Mouse(_) => {}
  185. },
  186. InputEvent::Close => break,
  187. };
  188. if let InputEvent::UserInput(evt) = evt.unwrap() {
  189. register_event(evt);
  190. }
  191. }
  192. }
  193. }
  194. {
  195. // resolve events before rendering
  196. let evts = handler.get_events(&stretch.borrow(), &mut rdom);
  197. for e in evts {
  198. vdom.handle_message(SchedulerMsg::Event(e));
  199. }
  200. let mutations = vdom.work_with_deadline(|| false);
  201. // updates the dom's nodes
  202. let to_update = rdom.apply_mutations(mutations);
  203. // update the style and layout
  204. let mut any_map = AnyMap::new();
  205. any_map.insert(stretch.clone());
  206. let _to_rerender = rdom.update_state(&vdom, to_update, any_map).unwrap();
  207. }
  208. }
  209. if let Some(terminal) = &mut terminal {
  210. disable_raw_mode()?;
  211. execute!(
  212. terminal.backend_mut(),
  213. LeaveAlternateScreen,
  214. DisableMouseCapture
  215. )?;
  216. terminal.show_cursor()?;
  217. }
  218. Ok(())
  219. })
  220. }
  221. enum InputEvent {
  222. UserInput(TermEvent),
  223. Close,
  224. }