lib.rs 11 KB

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