create dioxus handler for TUI
This commit is contained in:
1 parent
a2236df773
commit
4ee8b8b676
6 files changed
+117
-145
No files matched your search
@@ -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
@@ -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();
|
||||
}
|
||||
@@ -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 {}
|
||||
|
||||
@@ -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
@@ -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();
|
||||
|
||||
@@ -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 }
|
||||
}
|
||||
|
||||
|
||||
Reference in new issue
Block a user