diff --git a/packages/native-core/src/lib.rs b/packages/native-core/src/lib.rs index f5d3bab56..9e8dbe7a1 100644 --- a/packages/native-core/src/lib.rs +++ b/packages/native-core/src/lib.rs @@ -38,5 +38,5 @@ pub type SendAnyMap = anymap::Map; pub trait Renderer { fn render(&mut self, root: NodeMut); fn handle_event(&mut self, node: NodeMut, event: &str, value: E, bubbles: bool); - fn poll_async(&mut self) -> Pin + Send>>; + fn poll_async(&mut self) -> Pin + '_>>; } diff --git a/packages/tui/src/dioxus.rs b/packages/tui/src/dioxus.rs index c45918493..cd4eccf1f 100644 --- a/packages/tui/src/dioxus.rs +++ b/packages/tui/src/dioxus.rs @@ -1,3 +1,15 @@ +use std::{ops::Deref, rc::Rc}; + +use dioxus_core::{Component, VirtualDom}; +use dioxus_html::EventData; +use dioxus_native_core::{ + dioxus::{DioxusState, NodeImmutableDioxusExt}, + Renderer, +}; +use tokio::select; + +use crate::{query::Query, render, Config, TuiContext}; + pub fn launch(app: Component<()>) { launch_cfg(app, Config::default()) } @@ -7,60 +19,92 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) { } pub fn launch_cfg_with_props(app: Component, props: Props, cfg: Config) { - let mut dom = VirtualDom::new_with_props(app, props); - let mut rdom = RealDom::new(Box::new([ - TaffyLayout::to_type_erased(), - Focus::to_type_erased(), - StyleModifier::to_type_erased(), - PreventDefault::to_type_erased(), - ])); + render(cfg, |rdom, taffy, event_tx| { + let mut vdom = VirtualDom::new_with_props(app, props) + .with_root_context(TuiContext { tx: event_tx }) + .with_root_context(Query { + rdom: rdom.clone(), + stretch: taffy.clone(), + }); + let muts = vdom.rebuild(); + let mut rdom = rdom.write().unwrap(); + let mut dioxus_state = DioxusState::create(&mut rdom); + dioxus_state.apply_mutations(&mut rdom, muts); + DioxusRenderer { + vdom, + dioxus_state, + #[cfg(all(feature = "hot-reload", debug_assertions))] + hot_reload_rx: { + let (hot_reload_tx, hot_reload_rx) = + tokio::sync::mpsc::unbounded_channel::(); + dioxus_hot_reload::connect(move |msg| { + let _ = hot_reload_tx.send(msg); + }); + hot_reload_rx + }, + } + }) + .unwrap(); +} - let (handler, state, register_event) = RinkInputHandler::craete(&mut rdom); +struct DioxusRenderer { + vdom: VirtualDom, + dioxus_state: DioxusState, + #[cfg(all(feature = "hot-reload", debug_assertions))] + hot_reload_rx: tokio::sync::mpsc::UnboundedReceiver, +} - // Setup input handling - let (event_tx, event_rx) = unbounded(); - let event_tx_clone = event_tx.clone(); - if !cfg.headless { - std::thread::spawn(move || { - let tick_rate = Duration::from_millis(1000); - loop { - if crossterm::event::poll(tick_rate).unwrap() { - let evt = crossterm::event::read().unwrap(); - if event_tx.unbounded_send(InputEvent::UserInput(evt)).is_err() { - break; +impl Renderer> for DioxusRenderer { + fn render(&mut self, mut root: dioxus_native_core::NodeMut<()>) { + let rdom = root.real_dom_mut(); + let muts = self.vdom.render_immediate(); + self.dioxus_state.apply_mutations(rdom, muts); + } + + fn handle_event( + &mut self, + node: dioxus_native_core::NodeMut<()>, + event: &str, + value: Rc, + bubbles: bool, + ) { + if let Some(id) = node.mounted_id() { + self.vdom + .handle_event(event, value.deref().clone().into_any(), id, bubbles); + } + } + + fn poll_async(&mut self) -> std::pin::Pin + '_>> { + #[cfg(all(feature = "hot-reload", debug_assertions))] + return Box::pin(async { + let hot_reload_wait = self.hot_reload_rx.recv(); + let mut hot_reload_msg = None; + let wait_for_work = self.vdom.wait_for_work(); + select! { + Some(msg) = hot_reload_wait => { + #[cfg(all(feature = "hot-reload", debug_assertions))] + { + hot_reload_msg = Some(msg); + } + #[cfg(not(all(feature = "hot-reload", debug_assertions)))] + let () = msg; + } + _ = wait_for_work => {} + } + // if we have a new template, replace the old one + if let Some(msg) = hot_reload_msg { + match msg { + dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => { + self.vdom.replace_template(template); + } + dioxus_hot_reload::HotReloadMsg::Shutdown => { + std::process::exit(0); } } } }); + + #[cfg(not(all(feature = "hot-reload", debug_assertions)))] + Box::pin(self.vdom.wait_for_work()) } - - let cx = dom.base_scope(); - let rdom = Rc::new(RefCell::new(rdom)); - let taffy = Arc::new(Mutex::new(Taffy::new())); - cx.provide_context(state); - cx.provide_context(TuiContext { tx: event_tx_clone }); - cx.provide_context(Query { - rdom: rdom.clone(), - stretch: taffy.clone(), - }); - - { - let mut rdom = rdom.borrow_mut(); - let mutations = dom.rebuild(); - rdom.apply_mutations(mutations); - let mut any_map = SendAnyMap::new(); - any_map.insert(taffy.clone()); - let _ = rdom.update_state(any_map, false); - } - - render_vdom( - &mut dom, - event_rx, - handler, - cfg, - rdom, - taffy, - register_event, - ) - .unwrap(); } diff --git a/packages/tui/src/focus.rs b/packages/tui/src/focus.rs index e2958df61..8379444d8 100644 --- a/packages/tui/src/focus.rs +++ b/packages/tui/src/focus.rs @@ -147,7 +147,7 @@ pub(crate) struct FocusState { impl FocusState { pub fn create(rdom: &mut RealDom) -> Self { - let mut focus_iter = PersistantElementIter::create(rdom); + let focus_iter = PersistantElementIter::create(rdom); Self { focus_iter, last_focused_id: Default::default(), @@ -271,12 +271,12 @@ impl FocusState { pub(crate) fn set_focus(&mut self, rdom: &mut RealDom, id: NodeId) { if let Some(old) = self.last_focused_id.replace(id) { - let mut focused = rdom.get_state_mut_raw::(old).unwrap(); + let focused = rdom.get_state_mut_raw::(old).unwrap(); *focused = Focused(false); } - let mut focused = rdom.get_state_mut_raw::(id).unwrap(); + let focused = rdom.get_state_mut_raw::(id).unwrap(); *focused = Focused(true); - let mut node = rdom.get(id).unwrap(); + let node = rdom.get(id).unwrap(); self.focus_level = node.get::().unwrap().level; // reset the position to the currently focused element while self.focus_iter.next(rdom).id() != id {} diff --git a/packages/tui/src/hooks.rs b/packages/tui/src/hooks.rs index 8c509f592..73d101bd5 100644 --- a/packages/tui/src/hooks.rs +++ b/packages/tui/src/hooks.rs @@ -32,41 +32,6 @@ pub(crate) struct Event { pub bubbles: bool, } -// a wrapper around the input state for easier access -// todo: fix loop -// pub struct InputState(Rc>>); -// impl InputState { -// pub fn get(cx: &ScopeState) -> InputState { -// let inner = cx -// .consume_context::>>() -// .expect("Rink InputState can only be used in Rink apps!"); -// (**inner).borrow_mut().subscribe(cx.schedule_update()); -// InputState(inner) -// } - -// pub fn mouse(&self) -> Option { -// let data = (**self.0).borrow(); -// mouse.as_ref().map(|m| m.clone()) -// } - -// pub fn wheel(&self) -> Option { -// let data = (**self.0).borrow(); -// wheel.as_ref().map(|w| w.clone()) -// } - -// pub fn screen(&self) -> Option<(u16, u16)> { -// let data = (**self.0).borrow(); -// screen.as_ref().map(|m| m.clone()) -// } - -// pub fn last_key_pressed(&self) -> Option { -// let data = (**self.0).borrow(); -// last_key_pressed -// .as_ref() -// .map(|k| &k.0.clone()) -// } -// } - type EventCore = (&'static str, EventData); const MAX_REPEAT_TIME: Duration = Duration::from_millis(100); @@ -606,13 +571,7 @@ pub struct RinkInputHandler { impl RinkInputHandler { /// global context that handles events /// limitations: GUI key modifier is never detected, key up events are not detected, and only two mouse buttons may be pressed at once - pub fn craete( - rdom: &mut RealDom, - ) -> ( - Self, - Rc>, - impl FnMut(crossterm::event::Event), - ) { + pub fn create(rdom: &mut RealDom) -> (Self, impl FnMut(crossterm::event::Event)) { let queued_events = Rc::new(RefCell::new(Vec::new())); let queued_events2 = Rc::downgrade(&queued_events); @@ -628,10 +587,9 @@ impl RinkInputHandler { ( Self { - state: state.clone(), + state, queued_events, }, - state, regester_event, ) } diff --git a/packages/tui/src/lib.rs b/packages/tui/src/lib.rs index efcd4ed64..1de52893b 100644 --- a/packages/tui/src/lib.rs +++ b/packages/tui/src/lib.rs @@ -24,6 +24,8 @@ use tokio::select; use tui::{backend::CrosstermBackend, layout::Rect, Terminal}; mod config; +#[cfg(feature = "dioxus")] +pub mod dioxus; mod focus; mod hooks; mod layout; @@ -36,6 +38,8 @@ mod style_attributes; mod widget; // mod widgets; +#[cfg(feature = "dioxus")] +pub use crate::dioxus::*; pub use config::*; pub use hooks::*; @@ -56,6 +60,7 @@ pub(crate) fn layout_to_screen_space(layout: f32) -> f32 { pub struct TuiContext { tx: UnboundedSender, } + impl TuiContext { pub fn quit(&self) { self.tx.unbounded_send(InputEvent::Close).unwrap(); @@ -79,7 +84,7 @@ pub fn render>>( PreventDefault::to_type_erased(), ])); - let (handler, state, mut register_event) = RinkInputHandler::craete(&mut rdom); + let (handler, mut register_event) = RinkInputHandler::create(&mut rdom); // Setup input handling let (event_tx, mut event_reciever) = unbounded(); @@ -115,15 +120,6 @@ pub fn render>>( .enable_all() .build()? .block_on(async { - #[cfg(all(feature = "hot-reload", debug_assertions))] - let mut hot_reload_rx = { - let (hot_reload_tx, hot_reload_rx) = - tokio::sync::mpsc::unbounded_channel::(); - dioxus_hot_reload::connect(move |msg| { - let _ = hot_reload_tx.send(msg); - }); - hot_reload_rx - }; let mut terminal = (!cfg.headless).then(|| { enable_raw_mode().unwrap(); let mut stdout = std::io::stdout(); @@ -209,13 +205,8 @@ pub fn render>>( } } - // let mut hot_reload_msg = None; { let wait = renderer.poll_async(); - // #[cfg(all(feature = "hot-reload", debug_assertions))] - // let hot_reload_wait = hot_reload_rx.recv(); - // #[cfg(not(all(feature = "hot-reload", debug_assertions)))] - // let hot_reload_wait: std::future::Pending> = std::future::pending(); pin_mut!(wait); @@ -228,8 +219,8 @@ pub fn render>>( InputEvent::UserInput(event) => match event { TermEvent::Key(key) => { if matches!(key.code, KeyCode::Char('C' | 'c')) - && key.modifiers.contains(KeyModifiers::CONTROL) - && cfg.ctrl_c_quit + && key.modifiers.contains(KeyModifiers::CONTROL) + && cfg.ctrl_c_quit { break; } @@ -244,30 +235,9 @@ pub fn render>>( register_event(evt); } }, - // Some(msg) = hot_reload_wait => { - // #[cfg(all(feature = "hot-reload", debug_assertions))] - // { - // hot_reload_msg = Some(msg); - // } - // #[cfg(not(all(feature = "hot-reload", debug_assertions)))] - // let () = msg; - // } } } - // // if we have a new template, replace the old one - // #[cfg(all(feature = "hot-reload", debug_assertions))] - // if let Some(msg) = hot_reload_msg { - // match msg { - // dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => { - // vdom.replace_template(template); - // } - // dioxus_hot_reload::HotReloadMsg::Shutdown => { - // break; - // } - // } - // } - { { let mut rdom = rdom.write().unwrap(); diff --git a/packages/tui/src/query.rs b/packages/tui/src/query.rs index 4bf89516d..40606e743 100644 --- a/packages/tui/src/query.rs +++ b/packages/tui/src/query.rs @@ -1,8 +1,4 @@ -use std::{ - cell::{Ref, RefCell}, - rc::Rc, - sync::{Arc, Mutex, MutexGuard}, -}; +use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard}; use dioxus_native_core::{real_dom::NodeImmutable, NodeId, RealDom}; use taffy::{ @@ -46,14 +42,14 @@ use crate::{layout::TaffyLayout, layout_to_screen_space}; /// ``` #[derive(Clone)] pub struct Query { - pub(crate) rdom: Rc>, + pub(crate) rdom: Arc>, pub(crate) stretch: Arc>, } impl Query { pub fn get(&self, id: NodeId) -> ElementRef { ElementRef::new( - self.rdom.borrow(), + self.rdom.read().expect("rdom lock poisoned"), self.stretch.lock().expect("taffy lock poisoned"), id, ) @@ -61,13 +57,17 @@ impl Query { } pub struct ElementRef<'a> { - inner: Ref<'a, RealDom>, + inner: RwLockReadGuard<'a, RealDom>, stretch: MutexGuard<'a, Taffy>, id: NodeId, } impl<'a> ElementRef<'a> { - fn new(inner: Ref<'a, RealDom>, stretch: MutexGuard<'a, Taffy>, id: NodeId) -> Self { + fn new( + inner: RwLockReadGuard<'a, RealDom>, + stretch: MutexGuard<'a, Taffy>, + id: NodeId, + ) -> Self { Self { inner, stretch, id } }