lib.rs 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216
  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::exports::futures_channel::mpsc::unbounded;
  8. use dioxus_core::*;
  9. use dioxus_native_core::real_dom::RealDom;
  10. use futures::{
  11. channel::mpsc::{UnboundedReceiver, UnboundedSender},
  12. pin_mut, StreamExt,
  13. };
  14. use layout::StretchLayout;
  15. use std::{io, time::Duration};
  16. use stretch2::{prelude::Size, Stretch};
  17. use style_attributes::StyleModifier;
  18. use tui::{backend::CrosstermBackend, Terminal};
  19. mod config;
  20. mod hooks;
  21. mod layout;
  22. mod render;
  23. mod style;
  24. mod style_attributes;
  25. mod widget;
  26. pub use config::*;
  27. pub use hooks::*;
  28. pub use render::*;
  29. #[derive(Clone)]
  30. pub struct TuiContext {
  31. tx: UnboundedSender<InputEvent>,
  32. }
  33. impl TuiContext {
  34. pub fn quit(&self) {
  35. self.tx.unbounded_send(InputEvent::Close).unwrap();
  36. }
  37. }
  38. pub fn launch(app: Component<()>) {
  39. launch_cfg(app, Config::default())
  40. }
  41. pub fn launch_cfg(app: Component<()>, cfg: Config) {
  42. let mut dom = VirtualDom::new(app);
  43. let (handler, state, register_event) = RinkInputHandler::new();
  44. // Setup input handling
  45. let (event_tx, event_rx) = unbounded();
  46. let event_tx_clone = event_tx.clone();
  47. std::thread::spawn(move || {
  48. let tick_rate = Duration::from_millis(1000);
  49. loop {
  50. if crossterm::event::poll(tick_rate).unwrap() {
  51. // if crossterm::event::poll(timeout).unwrap() {
  52. let evt = crossterm::event::read().unwrap();
  53. if event_tx.unbounded_send(InputEvent::UserInput(evt)).is_err() {
  54. break;
  55. }
  56. }
  57. }
  58. });
  59. let cx = dom.base_scope();
  60. cx.provide_root_context(state);
  61. cx.provide_root_context(TuiContext { tx: event_tx_clone });
  62. let mut rdom: RealDom<StretchLayout, StyleModifier> = RealDom::new();
  63. let mutations = dom.rebuild();
  64. let to_update = rdom.apply_mutations(vec![mutations]);
  65. let mut stretch = Stretch::new();
  66. let _to_rerender = rdom
  67. .update_state(&dom, to_update, &mut stretch, &mut ())
  68. .unwrap();
  69. render_vdom(
  70. &mut dom,
  71. event_rx,
  72. handler,
  73. cfg,
  74. rdom,
  75. stretch,
  76. register_event,
  77. )
  78. .unwrap();
  79. }
  80. fn render_vdom(
  81. vdom: &mut VirtualDom,
  82. mut event_reciever: UnboundedReceiver<InputEvent>,
  83. handler: RinkInputHandler,
  84. cfg: Config,
  85. mut rdom: RealDom<StretchLayout, StyleModifier>,
  86. mut stretch: Stretch,
  87. mut register_event: impl FnMut(crossterm::event::Event),
  88. ) -> Result<()> {
  89. tokio::runtime::Builder::new_current_thread()
  90. .enable_all()
  91. .build()?
  92. .block_on(async {
  93. enable_raw_mode().unwrap();
  94. let mut stdout = std::io::stdout();
  95. execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap();
  96. let backend = CrosstermBackend::new(io::stdout());
  97. let mut terminal = Terminal::new(backend).unwrap();
  98. terminal.clear().unwrap();
  99. let mut to_rerender: fxhash::FxHashSet<usize> = vec![0].into_iter().collect();
  100. let mut resized = true;
  101. loop {
  102. /*
  103. -> render the nodes in the right place with tui/crossterm
  104. -> wait for changes
  105. -> resolve events
  106. -> lazily update the layout and style based on nodes changed
  107. use simd to compare lines for diffing?
  108. todo: lazy re-rendering
  109. */
  110. if !to_rerender.is_empty() || resized {
  111. resized = false;
  112. terminal.draw(|frame| {
  113. // size is guaranteed to not change when rendering
  114. let dims = frame.size();
  115. let width = dims.width;
  116. let height = dims.height;
  117. let root_id = rdom.root_id();
  118. let root_node = rdom[root_id].up_state.node.unwrap();
  119. stretch
  120. .compute_layout(
  121. root_node,
  122. Size {
  123. width: stretch2::prelude::Number::Defined((width - 1) as f32),
  124. height: stretch2::prelude::Number::Defined((height - 1) as f32),
  125. },
  126. )
  127. .unwrap();
  128. let root = &rdom[rdom.root_id()];
  129. render::render_vnode(frame, &stretch, &rdom, &root, cfg);
  130. })?;
  131. }
  132. use futures::future::{select, Either};
  133. {
  134. let wait = vdom.wait_for_work();
  135. pin_mut!(wait);
  136. match select(wait, event_reciever.next()).await {
  137. Either::Left((_a, _b)) => {
  138. //
  139. }
  140. Either::Right((evt, _o)) => {
  141. match evt.as_ref().unwrap() {
  142. InputEvent::UserInput(event) => match event {
  143. TermEvent::Key(key) => {
  144. if matches!(key.code, KeyCode::Char('C' | 'c'))
  145. && key.modifiers.contains(KeyModifiers::CONTROL)
  146. && cfg.ctrl_c_quit
  147. {
  148. break;
  149. }
  150. }
  151. TermEvent::Resize(_, _) => resized = true,
  152. TermEvent::Mouse(_) => {}
  153. },
  154. InputEvent::Close => break,
  155. };
  156. if let InputEvent::UserInput(evt) = evt.unwrap() {
  157. register_event(evt);
  158. }
  159. }
  160. }
  161. }
  162. {
  163. // resolve events before rendering
  164. let evts = handler.get_events(&stretch, &mut rdom);
  165. for e in evts {
  166. vdom.handle_message(SchedulerMsg::Event(e));
  167. }
  168. let mutations = vdom.work_with_deadline(|| false);
  169. // updates the dom's nodes
  170. let to_update = rdom.apply_mutations(mutations);
  171. // update the style and layout
  172. to_rerender.extend(
  173. rdom.update_state(vdom, to_update, &mut stretch, &mut ())
  174. .unwrap()
  175. .iter(),
  176. )
  177. }
  178. }
  179. disable_raw_mode()?;
  180. execute!(
  181. terminal.backend_mut(),
  182. LeaveAlternateScreen,
  183. DisableMouseCapture
  184. )?;
  185. terminal.show_cursor()?;
  186. Ok(())
  187. })
  188. }
  189. enum InputEvent {
  190. UserInput(TermEvent),
  191. Close,
  192. }