create dioxus handler for TUI

This commit is contained in:
Evan Almloff committed 2023-02-07 14:51:30 -06:00
1 parent a2236df773
commit 4ee8b8b676
6 files changed
+117 -145

No files matched your search

+1 -1
View File
@@ -38,5 +38,5 @@ pub type SendAnyMap = anymap::Map<dyn Any + Send + Sync + 'static>;
pub trait Renderer<E, V: FromAnyValue + Send + Sync = ()> { pub trait Renderer<E, V: FromAnyValue + Send + Sync = ()> {
fn render(&mut self, root: NodeMut<V>); fn render(&mut self, root: NodeMut<V>);
fn handle_event(&mut self, node: NodeMut<V>, event: &str, value: E, bubbles: bool); 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 = ()> + '_>>;
} }
+93 -49
View File
@@ -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<()>) { pub fn launch(app: Component<()>) {
launch_cfg(app, Config::default()) 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) { pub fn launch_cfg_with_props<Props: 'static>(app: Component<Props>, props: Props, cfg: Config) {
let mut dom = VirtualDom::new_with_props(app, props); render(cfg, |rdom, taffy, event_tx| {
let mut rdom = RealDom::new(Box::new([ let mut vdom = VirtualDom::new_with_props(app, props)
TaffyLayout::to_type_erased(), .with_root_context(TuiContext { tx: event_tx })
Focus::to_type_erased(), .with_root_context(Query {
StyleModifier::to_type_erased(), rdom: rdom.clone(),
PreventDefault::to_type_erased(), 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();
}
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<dioxus_hot_reload::HotReloadMsg>,
}
// Setup input handling impl Renderer<Rc<EventData>> for DioxusRenderer {
let (event_tx, event_rx) = unbounded(); fn render(&mut self, mut root: dioxus_native_core::NodeMut<()>) {
let event_tx_clone = event_tx.clone(); let rdom = root.real_dom_mut();
if !cfg.headless { let muts = self.vdom.render_immediate();
std::thread::spawn(move || { self.dioxus_state.apply_mutations(rdom, muts);
let tick_rate = Duration::from_millis(1000); }
loop {
if crossterm::event::poll(tick_rate).unwrap() { fn handle_event(
let evt = crossterm::event::read().unwrap(); &mut self,
if event_tx.unbounded_send(InputEvent::UserInput(evt)).is_err() { node: dioxus_native_core::NodeMut<()>,
break; 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);
}
}
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);
} }
} }
} }
}); });
#[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();
} }
+4 -4
View File
@@ -147,7 +147,7 @@ pub(crate) struct FocusState {
impl FocusState { impl FocusState {
pub fn create(rdom: &mut RealDom) -> Self { pub fn create(rdom: &mut RealDom) -> Self {
let mut focus_iter = PersistantElementIter::create(rdom); let focus_iter = PersistantElementIter::create(rdom);
Self { Self {
focus_iter, focus_iter,
last_focused_id: Default::default(), last_focused_id: Default::default(),
@@ -271,12 +271,12 @@ impl FocusState {
pub(crate) fn set_focus(&mut self, rdom: &mut RealDom, id: NodeId) { pub(crate) fn set_focus(&mut self, rdom: &mut RealDom, id: NodeId) {
if let Some(old) = self.last_focused_id.replace(id) { 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); *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); *focused = Focused(true);
let mut node = rdom.get(id).unwrap(); let node = rdom.get(id).unwrap();
self.focus_level = node.get::<Focus>().unwrap().level; self.focus_level = node.get::<Focus>().unwrap().level;
// reset the position to the currently focused element // reset the position to the currently focused element
while self.focus_iter.next(rdom).id() != id {} while self.focus_iter.next(rdom).id() != id {}
+2 -44
View File
@@ -32,41 +32,6 @@ pub(crate) struct Event {
pub bubbles: bool, 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); type EventCore = (&'static str, EventData);
const MAX_REPEAT_TIME: Duration = Duration::from_millis(100); const MAX_REPEAT_TIME: Duration = Duration::from_millis(100);
@@ -606,13 +571,7 @@ pub struct RinkInputHandler {
impl RinkInputHandler { impl RinkInputHandler {
/// global context that handles events /// 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 /// 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( pub fn create(rdom: &mut RealDom) -> (Self, impl FnMut(crossterm::event::Event)) {
rdom: &mut RealDom,
) -> (
Self,
Rc<RefCell<InnerInputState>>,
impl FnMut(crossterm::event::Event),
) {
let queued_events = Rc::new(RefCell::new(Vec::new())); let queued_events = Rc::new(RefCell::new(Vec::new()));
let queued_events2 = Rc::downgrade(&queued_events); let queued_events2 = Rc::downgrade(&queued_events);
@@ -628,10 +587,9 @@ impl RinkInputHandler {
( (
Self { Self {
state: state.clone(), state,
queued_events, queued_events,
}, },
state,
regester_event, regester_event,
) )
} }
+8 -38
View File
@@ -24,6 +24,8 @@ use tokio::select;
use tui::{backend::CrosstermBackend, layout::Rect, Terminal}; use tui::{backend::CrosstermBackend, layout::Rect, Terminal};
mod config; mod config;
#[cfg(feature = "dioxus")]
pub mod dioxus;
mod focus; mod focus;
mod hooks; mod hooks;
mod layout; mod layout;
@@ -36,6 +38,8 @@ mod style_attributes;
mod widget; mod widget;
// mod widgets; // mod widgets;
#[cfg(feature = "dioxus")]
pub use crate::dioxus::*;
pub use config::*; pub use config::*;
pub use hooks::*; pub use hooks::*;
@@ -56,6 +60,7 @@ pub(crate) fn layout_to_screen_space(layout: f32) -> f32 {
pub struct TuiContext { pub struct TuiContext {
tx: UnboundedSender<InputEvent>, tx: UnboundedSender<InputEvent>,
} }
impl TuiContext { impl TuiContext {
pub fn quit(&self) { pub fn quit(&self) {
self.tx.unbounded_send(InputEvent::Close).unwrap(); self.tx.unbounded_send(InputEvent::Close).unwrap();
@@ -79,7 +84,7 @@ pub fn render<R: Renderer<Rc<EventData>>>(
PreventDefault::to_type_erased(), 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 // Setup input handling
let (event_tx, mut event_reciever) = unbounded(); let (event_tx, mut event_reciever) = unbounded();
@@ -115,15 +120,6 @@ pub fn render<R: Renderer<Rc<EventData>>>(
.enable_all() .enable_all()
.build()? .build()?
.block_on(async { .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(|| { let mut terminal = (!cfg.headless).then(|| {
enable_raw_mode().unwrap(); enable_raw_mode().unwrap();
let mut stdout = std::io::stdout(); 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(); 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); pin_mut!(wait);
@@ -228,8 +219,8 @@ pub fn render<R: Renderer<Rc<EventData>>>(
InputEvent::UserInput(event) => match event { InputEvent::UserInput(event) => match event {
TermEvent::Key(key) => { TermEvent::Key(key) => {
if matches!(key.code, KeyCode::Char('C' | 'c')) if matches!(key.code, KeyCode::Char('C' | 'c'))
&& key.modifiers.contains(KeyModifiers::CONTROL) && key.modifiers.contains(KeyModifiers::CONTROL)
&& cfg.ctrl_c_quit && cfg.ctrl_c_quit
{ {
break; break;
} }
@@ -244,30 +235,9 @@ pub fn render<R: Renderer<Rc<EventData>>>(
register_event(evt); 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(); let mut rdom = rdom.write().unwrap();
+9 -9
View File
@@ -1,8 +1,4 @@
use std::{ use std::sync::{Arc, Mutex, MutexGuard, RwLock, RwLockReadGuard};
cell::{Ref, RefCell},
rc::Rc,
sync::{Arc, Mutex, MutexGuard},
};
use dioxus_native_core::{real_dom::NodeImmutable, NodeId, RealDom}; use dioxus_native_core::{real_dom::NodeImmutable, NodeId, RealDom};
use taffy::{ use taffy::{
@@ -46,14 +42,14 @@ use crate::{layout::TaffyLayout, layout_to_screen_space};
/// ``` /// ```
#[derive(Clone)] #[derive(Clone)]
pub struct Query { pub struct Query {
pub(crate) rdom: Rc<RefCell<RealDom>>, pub(crate) rdom: Arc<RwLock<RealDom>>,
pub(crate) stretch: Arc<Mutex<Taffy>>, pub(crate) stretch: Arc<Mutex<Taffy>>,
} }
impl Query { impl Query {
pub fn get(&self, id: NodeId) -> ElementRef { pub fn get(&self, id: NodeId) -> ElementRef {
ElementRef::new( ElementRef::new(
self.rdom.borrow(), self.rdom.read().expect("rdom lock poisoned"),
self.stretch.lock().expect("taffy lock poisoned"), self.stretch.lock().expect("taffy lock poisoned"),
id, id,
) )
@@ -61,13 +57,17 @@ impl Query {
} }
pub struct ElementRef<'a> { pub struct ElementRef<'a> {
inner: Ref<'a, RealDom>, inner: RwLockReadGuard<'a, RealDom>,
stretch: MutexGuard<'a, Taffy>, stretch: MutexGuard<'a, Taffy>,
id: NodeId, id: NodeId,
} }
impl<'a> ElementRef<'a> { 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 } Self { inner, stretch, id }
} }