lib.rs 11 KB

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