lib.rs 6.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215
  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 futures::{channel::mpsc::UnboundedSender, pin_mut, StreamExt};
  10. use std::{
  11. collections::HashMap,
  12. io,
  13. time::{Duration, Instant},
  14. };
  15. use stretch2::{prelude::Size, Stretch};
  16. use style::RinkStyle;
  17. use tui::{backend::CrosstermBackend, Terminal};
  18. mod attributes;
  19. mod config;
  20. mod hooks;
  21. mod layout;
  22. mod render;
  23. mod style;
  24. mod widget;
  25. pub use attributes::*;
  26. pub use config::*;
  27. pub use hooks::*;
  28. pub use layout::*;
  29. pub use render::*;
  30. pub fn launch(app: Component<()>) {
  31. launch_cfg(app, Config::default())
  32. }
  33. pub fn launch_cfg(app: Component<()>, cfg: Config) {
  34. let mut dom = VirtualDom::new(app);
  35. let (tx, rx) = unbounded();
  36. let cx = dom.base_scope();
  37. let (handler, state) = RinkInputHandler::new(rx, cx);
  38. cx.provide_root_context(state);
  39. dom.rebuild();
  40. render_vdom(&mut dom, tx, handler, cfg).unwrap();
  41. }
  42. pub struct TuiNode<'a> {
  43. pub layout: stretch2::node::Node,
  44. pub block_style: RinkStyle,
  45. pub tui_modifier: TuiModifier,
  46. pub node: &'a VNode<'a>,
  47. }
  48. pub fn render_vdom(
  49. vdom: &mut VirtualDom,
  50. ctx: UnboundedSender<TermEvent>,
  51. handler: RinkInputHandler,
  52. cfg: Config,
  53. ) -> Result<()> {
  54. // Setup input handling
  55. let (tx, mut rx) = unbounded();
  56. std::thread::spawn(move || {
  57. let tick_rate = Duration::from_millis(100);
  58. let mut last_tick = Instant::now();
  59. loop {
  60. // poll for tick rate duration, if no events, sent tick event.
  61. let timeout = tick_rate
  62. .checked_sub(last_tick.elapsed())
  63. .unwrap_or_else(|| Duration::from_secs(0));
  64. if crossterm::event::poll(timeout).unwrap() {
  65. let evt = crossterm::event::read().unwrap();
  66. tx.unbounded_send(InputEvent::UserInput(evt)).unwrap();
  67. }
  68. if last_tick.elapsed() >= tick_rate {
  69. tx.unbounded_send(InputEvent::Tick).unwrap();
  70. last_tick = Instant::now();
  71. }
  72. }
  73. });
  74. tokio::runtime::Builder::new_current_thread()
  75. .enable_all()
  76. .build()?
  77. .block_on(async {
  78. /*
  79. Get the terminal to calcualte the layout from
  80. */
  81. enable_raw_mode().unwrap();
  82. let mut stdout = std::io::stdout();
  83. execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap();
  84. let backend = CrosstermBackend::new(io::stdout());
  85. let mut terminal = Terminal::new(backend).unwrap();
  86. terminal.clear().unwrap();
  87. loop {
  88. /*
  89. -> collect all the nodes with their layout
  90. -> solve their layout
  91. -> resolve events
  92. -> render the nodes in the right place with tui/crosstream
  93. -> while rendering, apply styling
  94. use simd to compare lines for diffing?
  95. todo: reuse the layout and node objects.
  96. our work_with_deadline method can tell us which nodes are dirty.
  97. */
  98. let mut layout = Stretch::new();
  99. let mut nodes = HashMap::new();
  100. let root_node = vdom.base_scope().root_node();
  101. layout::collect_layout(&mut layout, &mut nodes, vdom, root_node);
  102. /*
  103. Compute the layout given the terminal size
  104. */
  105. let node_id = root_node.try_mounted_id().unwrap();
  106. let root_layout = nodes[&node_id].layout;
  107. let mut events = Vec::new();
  108. terminal.draw(|frame| {
  109. // size is guaranteed to not change when rendering
  110. let dims = frame.size();
  111. let width = dims.width;
  112. let height = dims.height;
  113. layout
  114. .compute_layout(
  115. root_layout,
  116. Size {
  117. width: stretch2::prelude::Number::Defined(width as f32),
  118. height: stretch2::prelude::Number::Defined(height as f32),
  119. },
  120. )
  121. .unwrap();
  122. // resolve events before rendering
  123. events = handler.get_events(vdom, &layout, &mut nodes, root_node);
  124. render::render_vnode(
  125. frame,
  126. &layout,
  127. &mut nodes,
  128. vdom,
  129. root_node,
  130. &RinkStyle::default(),
  131. cfg,
  132. );
  133. assert!(nodes.is_empty());
  134. })?;
  135. for e in events {
  136. vdom.handle_message(SchedulerMsg::Event(e));
  137. }
  138. use futures::future::{select, Either};
  139. {
  140. let wait = vdom.wait_for_work();
  141. pin_mut!(wait);
  142. match select(wait, rx.next()).await {
  143. Either::Left((_a, _b)) => {
  144. //
  145. }
  146. Either::Right((evt, _o)) => {
  147. match evt.as_ref().unwrap() {
  148. InputEvent::UserInput(event) => match event {
  149. TermEvent::Key(key) => {
  150. if matches!(key.code, KeyCode::Char('C' | 'c'))
  151. && key.modifiers.contains(KeyModifiers::CONTROL)
  152. {
  153. break;
  154. }
  155. }
  156. TermEvent::Resize(_, _) | TermEvent::Mouse(_) => {}
  157. },
  158. InputEvent::Tick => {} // tick
  159. InputEvent::Close => break,
  160. };
  161. if let InputEvent::UserInput(evt) = evt.unwrap() {
  162. ctx.unbounded_send(evt).unwrap();
  163. }
  164. }
  165. }
  166. }
  167. vdom.work_with_deadline(|| false);
  168. }
  169. disable_raw_mode()?;
  170. execute!(
  171. terminal.backend_mut(),
  172. LeaveAlternateScreen,
  173. DisableMouseCapture
  174. )?;
  175. terminal.show_cursor()?;
  176. Ok(())
  177. })
  178. }
  179. enum InputEvent {
  180. UserInput(TermEvent),
  181. Tick,
  182. #[allow(dead_code)]
  183. Close,
  184. }