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 = ()> {
|
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
@@ -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();
|
|
||||||
}
|
}
|
||||||
@@ -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 {}
|
||||||
|
|||||||
@@ -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
@@ -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();
|
||||||
|
|||||||
@@ -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 }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in new issue
Block a user