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 = ()> {
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 = ()> + '_>>;
}
+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<()>) {
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();
}
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
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<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);
}
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);
}
}
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 {
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
View File
@@ -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
View File
@@ -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
View File
@@ -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 }
}