소스 검색

create dioxus handler for TUI

Evan Almloff 2 년 전
부모
커밋
4ee8b8b676
6개의 변경된 파일116개의 추가작업 그리고 144개의 파일을 삭제
  1. 1 1
      packages/native-core/src/lib.rs
  2. 92 48
      packages/tui/src/dioxus.rs
  3. 4 4
      packages/tui/src/focus.rs
  4. 2 44
      packages/tui/src/hooks.rs
  5. 8 38
      packages/tui/src/lib.rs
  6. 9 9
      packages/tui/src/query.rs

+ 1 - 1
packages/native-core/src/lib.rs

@@ -38,5 +38,5 @@ pub type SendAnyMap = anymap::Map<dyn Any + Send + Sync + 'static>;
 pub trait Renderer<E, V: FromAnyValue + Send + Sync = ()> {
     fn render(&mut self, root: NodeMut<V>);
     fn handle_event(&mut self, node: NodeMut<V>, event: &str, value: E, bubbles: bool);
-    fn poll_async(&mut self) -> Pin<Box<dyn Future<Output = ()> + Send>>;
+    fn poll_async(&mut self) -> Pin<Box<dyn Future<Output = ()> + '_>>;
 }

+ 92 - 48
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<Props: 'static>(app: Component<Props>, 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::HotReloadMsg>();
+                dioxus_hot_reload::connect(move |msg| {
+                    let _ = hot_reload_tx.send(msg);
+                });
+                hot_reload_rx
+            },
+        }
+    })
+    .unwrap();
+}
+
+struct DioxusRenderer {
+    vdom: VirtualDom,
+    dioxus_state: DioxusState,
+    #[cfg(all(feature = "hot-reload", debug_assertions))]
+    hot_reload_rx: tokio::sync::mpsc::UnboundedReceiver<dioxus_hot_reload::HotReloadMsg>,
+}
+
+impl Renderer<Rc<EventData>> 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);
+    }
 
-    let (handler, state, register_event) = RinkInputHandler::craete(&mut rdom);
+    fn handle_event(
+        &mut self,
+        node: dioxus_native_core::NodeMut<()>,
+        event: &str,
+        value: Rc<EventData>,
+        bubbles: bool,
+    ) {
+        if let Some(id) = node.mounted_id() {
+            self.vdom
+                .handle_event(event, value.deref().clone().into_any(), id, bubbles);
+        }
+    }
 
-    // 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;
+    fn poll_async(&mut self) -> std::pin::Pin<Box<dyn futures::Future<Output = ()> + '_>> {
+        #[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);
                     }
                 }
             }
         });
-    }
-
-    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);
+        #[cfg(not(all(feature = "hot-reload", debug_assertions)))]
+        Box::pin(self.vdom.wait_for_work())
     }
-
-    render_vdom(
-        &mut dom,
-        event_rx,
-        handler,
-        cfg,
-        rdom,
-        taffy,
-        register_event,
-    )
-    .unwrap();
 }

+ 4 - 4
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::<Focused>(old).unwrap();
+            let focused = rdom.get_state_mut_raw::<Focused>(old).unwrap();
             *focused = Focused(false);
         }
-        let mut focused = rdom.get_state_mut_raw::<Focused>(id).unwrap();
+        let focused = rdom.get_state_mut_raw::<Focused>(id).unwrap();
         *focused = Focused(true);
-        let mut node = rdom.get(id).unwrap();
+        let node = rdom.get(id).unwrap();
         self.focus_level = node.get::<Focus>().unwrap().level;
         // reset the position to the currently focused element
         while self.focus_iter.next(rdom).id() != id {}

+ 2 - 44
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<Rc<RefCell<InnerInputState>>>);
-// impl InputState {
-//     pub fn get(cx: &ScopeState) -> InputState {
-//         let inner = cx
-//             .consume_context::<Rc<RefCell<InnerInputState>>>()
-//             .expect("Rink InputState can only be used in Rink apps!");
-//         (**inner).borrow_mut().subscribe(cx.schedule_update());
-//         InputState(inner)
-//     }
-
-//     pub fn mouse(&self) -> Option<MouseData> {
-//         let data = (**self.0).borrow();
-//         mouse.as_ref().map(|m| m.clone())
-//     }
-
-//     pub fn wheel(&self) -> Option<WheelData> {
-//         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<KeyboardData> {
-//         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<RefCell<InnerInputState>>,
-        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,
         )
     }

+ 8 - 38
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<InputEvent>,
 }
+
 impl TuiContext {
     pub fn quit(&self) {
         self.tx.unbounded_send(InputEvent::Close).unwrap();
@@ -79,7 +84,7 @@ pub fn render<R: Renderer<Rc<EventData>>>(
         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<R: Renderer<Rc<EventData>>>(
         .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::HotReloadMsg>();
-                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<R: Renderer<Rc<EventData>>>(
                     }
                 }
 
-                // 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<Option<()>> = std::future::pending();
 
                     pin_mut!(wait);
 
@@ -228,8 +219,8 @@ pub fn render<R: Renderer<Rc<EventData>>>(
                                 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<R: Renderer<Rc<EventData>>>(
                                 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();

+ 9 - 9
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<RefCell<RealDom>>,
+    pub(crate) rdom: Arc<RwLock<RealDom>>,
     pub(crate) stretch: Arc<Mutex<Taffy>>,
 }
 
 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 }
     }