dioxus/packages/tui/src/lib.rs

343 lines
12 KiB
Rust
Raw Normal View History

2021-07-28 14:52:38 +00:00
use anyhow::Result;
2022-01-01 04:53:37 +00:00
use crossterm::{
2022-12-08 05:01:13 +00:00
cursor::{MoveTo, RestorePosition, SavePosition, Show},
2022-02-04 22:57:00 +00:00
event::{DisableMouseCapture, EnableMouseCapture, Event as TermEvent, KeyCode, KeyModifiers},
2022-01-01 04:53:37 +00:00
execute,
terminal::{disable_raw_mode, enable_raw_mode, EnterAlternateScreen, LeaveAlternateScreen},
};
2022-03-09 18:30:44 +00:00
use dioxus_core::*;
use dioxus_native_core::{real_dom::RealDom, FxDashSet, NodeId, NodeMask, SendAnyMap};
2022-05-03 20:06:07 +00:00
use focus::FocusState;
2022-03-31 01:45:41 +00:00
use futures::{
channel::mpsc::{UnboundedReceiver, UnboundedSender},
pin_mut, StreamExt,
};
use futures_channel::mpsc::unbounded;
2022-07-06 00:44:54 +00:00
use query::Query;
2022-04-12 23:46:16 +00:00
use std::rc::Rc;
use std::{
cell::RefCell,
sync::{Arc, Mutex},
};
2022-03-23 19:18:17 +00:00
use std::{io, time::Duration};
2022-07-06 00:44:54 +00:00
use taffy::Taffy;
pub use taffy::{geometry::Point, prelude::*};
use tokio::select;
2022-04-05 17:08:01 +00:00
use tui::{backend::CrosstermBackend, layout::Rect, Terminal};
2022-01-01 04:53:37 +00:00
2022-02-17 22:06:28 +00:00
mod config;
2022-05-03 20:06:07 +00:00
mod focus;
2022-01-12 14:40:36 +00:00
mod hooks;
2022-03-31 01:45:41 +00:00
mod layout;
2022-05-05 12:09:18 +00:00
mod node;
2022-12-08 02:28:01 +00:00
pub mod prelude;
2022-07-06 00:44:54 +00:00
pub mod query;
2022-01-01 04:53:37 +00:00
mod render;
2022-02-17 22:06:28 +00:00
mod style;
2022-03-18 15:43:43 +00:00
mod style_attributes;
2022-02-17 22:06:28 +00:00
mod widget;
2022-12-08 02:28:01 +00:00
mod widgets;
2022-01-01 04:53:37 +00:00
2022-02-17 22:06:28 +00:00
pub use config::*;
2022-01-12 14:40:36 +00:00
pub use hooks::*;
2022-05-05 12:09:18 +00:00
pub(crate) use node::*;
2022-04-12 12:51:57 +00:00
// the layout space has a multiplier of 10 to minimize rounding errors
pub(crate) fn screen_to_layout_space(screen: u16) -> f32 {
screen as f32 * 10.0
}
2022-12-08 05:01:13 +00:00
pub(crate) fn unit_to_layout_space(screen: f32) -> f32 {
screen * 10.0
}
pub(crate) fn layout_to_screen_space(layout: f32) -> f32 {
layout / 10.0
}
2022-03-31 01:45:41 +00:00
#[derive(Clone)]
pub struct TuiContext {
tx: UnboundedSender<InputEvent>,
}
impl TuiContext {
pub fn quit(&self) {
self.tx.unbounded_send(InputEvent::Close).unwrap();
}
2022-06-14 00:55:57 +00:00
pub fn inject_event(&self, event: crossterm::event::Event) {
self.tx
.unbounded_send(InputEvent::UserInput(event))
.unwrap();
}
2022-03-31 01:45:41 +00:00
}
2022-01-12 14:40:36 +00:00
pub fn launch(app: Component<()>) {
2022-02-17 22:06:28 +00:00
launch_cfg(app, Config::default())
}
pub fn launch_cfg(app: Component<()>, cfg: Config) {
launch_cfg_with_props(app, (), cfg);
}
pub fn launch_cfg_with_props<Props: 'static>(app: Component<Props>, props: Props, cfg: Config) {
let mut dom = VirtualDom::new_with_props(app, props);
2022-02-04 20:52:01 +00:00
2022-04-02 21:46:46 +00:00
let (handler, state, register_event) = RinkInputHandler::new();
2022-02-05 22:28:19 +00:00
2022-03-31 01:45:41 +00:00
// Setup input handling
let (event_tx, event_rx) = unbounded();
let event_tx_clone = event_tx.clone();
2022-04-05 17:08:01 +00:00
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;
}
2022-03-31 01:45:41 +00:00
}
}
2022-04-05 17:08:01 +00:00
});
}
2022-03-31 01:45:41 +00:00
2022-04-02 21:46:46 +00:00
let cx = dom.base_scope();
2022-07-06 00:44:54 +00:00
let rdom = Rc::new(RefCell::new(RealDom::new()));
let taffy = Arc::new(Mutex::new(Taffy::new()));
cx.provide_context(state);
cx.provide_context(TuiContext { tx: event_tx_clone });
cx.provide_context(Query {
2022-07-06 00:44:54 +00:00
rdom: rdom.clone(),
stretch: taffy.clone(),
});
2022-02-04 20:52:01 +00:00
2022-07-06 00:44:54 +00:00
{
let mut rdom = rdom.borrow_mut();
let mutations = dom.rebuild();
let (to_update, _) = rdom.apply_mutations(mutations);
let mut any_map = SendAnyMap::new();
2022-07-06 00:44:54 +00:00
any_map.insert(taffy.clone());
let _to_rerender = rdom.update_state(to_update, any_map);
2022-07-06 00:44:54 +00:00
}
2022-01-12 14:40:36 +00:00
2022-04-02 21:46:46 +00:00
render_vdom(
&mut dom,
event_rx,
handler,
cfg,
rdom,
2022-06-10 22:23:30 +00:00
taffy,
2022-04-02 21:46:46 +00:00
register_event,
)
.unwrap();
2021-07-28 14:52:38 +00:00
}
2022-03-31 01:45:41 +00:00
fn render_vdom(
2022-02-05 22:28:19 +00:00
vdom: &mut VirtualDom,
2022-03-31 01:45:41 +00:00
mut event_reciever: UnboundedReceiver<InputEvent>,
2022-02-05 22:28:19 +00:00
handler: RinkInputHandler,
2022-02-17 22:06:28 +00:00
cfg: Config,
rdom: Rc<RefCell<TuiDom>>,
taffy: Arc<Mutex<Taffy>>,
2022-04-02 21:46:46 +00:00
mut register_event: impl FnMut(crossterm::event::Event),
2022-02-05 22:28:19 +00:00
) -> Result<()> {
2022-01-01 14:49:08 +00:00
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?
.block_on(async {
2023-01-11 20:20:38 +00:00
#[cfg(all(feature = "hot-reload", debug_assertions))]
let mut hot_reload_rx = {
2023-01-13 23:50:32 +00:00
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);
});
2023-01-11 20:20:38 +00:00
hot_reload_rx
};
2022-04-05 17:08:01 +00:00
let mut terminal = (!cfg.headless).then(|| {
enable_raw_mode().unwrap();
let mut stdout = std::io::stdout();
2022-12-08 05:01:13 +00:00
execute!(
stdout,
EnterAlternateScreen,
EnableMouseCapture,
MoveTo(0, 1000)
)
.unwrap();
2022-04-05 17:08:01 +00:00
let backend = CrosstermBackend::new(io::stdout());
Terminal::new(backend).unwrap()
});
if let Some(terminal) = &mut terminal {
terminal.clear().unwrap();
}
2022-01-01 14:49:08 +00:00
let mut to_rerender = FxDashSet::default();
to_rerender.insert(NodeId(0));
2022-05-03 16:02:35 +00:00
let mut updated = true;
2022-01-01 14:49:08 +00:00
loop {
/*
2022-03-18 15:43:43 +00:00
-> render the nodes in the right place with tui/crossterm
2022-03-31 01:45:41 +00:00
-> wait for changes
-> resolve events
2022-03-18 15:43:43 +00:00
-> lazily update the layout and style based on nodes changed
2022-01-01 14:49:08 +00:00
use simd to compare lines for diffing?
2022-03-23 19:18:17 +00:00
todo: lazy re-rendering
2022-01-01 14:49:08 +00:00
*/
2022-05-03 16:02:35 +00:00
if !to_rerender.is_empty() || updated {
updated = false;
fn resize(dims: Rect, taffy: &mut Taffy, rdom: &TuiDom) {
let width = screen_to_layout_space(dims.width);
let height = screen_to_layout_space(dims.height);
let root_node = rdom[NodeId(0)].state.layout.node.unwrap();
2022-03-31 01:45:41 +00:00
// the root node fills the entire area
let mut style = *taffy.style(root_node).unwrap();
style.size = Size {
width: Dimension::Points(width),
height: Dimension::Points(height),
};
taffy.set_style(root_node, style).unwrap();
let size = Size {
width: AvailableSpace::Definite(width),
height: AvailableSpace::Definite(height),
};
taffy.compute_layout(root_node, size).unwrap();
2022-04-05 17:08:01 +00:00
}
if let Some(terminal) = &mut terminal {
2022-12-08 05:01:13 +00:00
execute!(terminal.backend_mut(), SavePosition).unwrap();
2022-04-05 17:08:01 +00:00
terminal.draw(|frame| {
2022-07-06 00:44:54 +00:00
let rdom = rdom.borrow();
let mut taffy = taffy.lock().expect("taffy lock poisoned");
2022-04-05 17:08:01 +00:00
// size is guaranteed to not change when rendering
2022-12-08 05:01:13 +00:00
resize(frame.size(), &mut taffy, &rdom);
let root = &rdom[NodeId(0)];
2022-12-08 05:01:13 +00:00
render::render_vnode(frame, &taffy, &rdom, root, cfg, Point::ZERO);
2022-04-05 17:08:01 +00:00
})?;
2022-12-08 05:01:13 +00:00
execute!(terminal.backend_mut(), RestorePosition, Show).unwrap();
2022-04-05 17:08:01 +00:00
} else {
2022-07-06 00:44:54 +00:00
let rdom = rdom.borrow();
2022-04-05 17:08:01 +00:00
resize(
Rect {
x: 0,
y: 0,
width: 1000,
height: 1000,
2022-04-05 17:08:01 +00:00
},
&mut taffy.lock().expect("taffy lock poisoned"),
2022-04-05 17:08:01 +00:00
&rdom,
);
}
2022-03-23 19:18:17 +00:00
}
2022-01-01 14:49:08 +00:00
#[cfg(all(feature = "hot-reload", debug_assertions))]
let mut hot_reload_msg = None;
2022-01-01 14:49:08 +00:00
{
let wait = vdom.wait_for_work();
2023-01-11 20:20:38 +00:00
#[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();
2023-01-11 20:20:38 +00:00
2022-01-01 14:49:08 +00:00
pin_mut!(wait);
2023-01-11 20:20:38 +00:00
select! {
_ = wait => {
},
evt = event_reciever.next() => {
2022-01-12 14:40:36 +00:00
match evt.as_ref().unwrap() {
2022-01-01 14:49:08 +00:00
InputEvent::UserInput(event) => match event {
TermEvent::Key(key) => {
2022-03-10 03:06:45 +00:00
if matches!(key.code, KeyCode::Char('C' | 'c'))
2022-02-04 22:57:00 +00:00
&& key.modifiers.contains(KeyModifiers::CONTROL)
2022-04-02 21:46:46 +00:00
&& cfg.ctrl_c_quit
2022-02-04 22:57:00 +00:00
{
break;
2022-01-01 14:49:08 +00:00
}
}
2022-05-03 16:02:35 +00:00
TermEvent::Resize(_, _) => updated = true,
2022-03-27 01:10:15 +00:00
TermEvent::Mouse(_) => {}
2022-01-01 14:49:08 +00:00
},
2022-01-12 14:40:36 +00:00
InputEvent::Close => break,
2022-01-01 14:49:08 +00:00
};
2022-01-12 14:40:36 +00:00
2022-05-03 21:44:53 +00:00
if let InputEvent::UserInput(evt) = evt.unwrap() {
register_event(evt);
2022-01-12 14:40:36 +00:00
}
2023-01-11 20:20:38 +00:00
},
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;
2022-01-01 14:49:08 +00:00
}
}
2022-01-01 06:08:31 +00:00
}
2022-01-01 14:49:08 +00:00
2023-01-11 20:20:38 +00:00
// 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 {
2023-01-13 23:50:32 +00:00
dioxus_hot_reload::HotReloadMsg::UpdateTemplate(template) => {
vdom.replace_template(template);
}
2023-01-13 23:50:32 +00:00
dioxus_hot_reload::HotReloadMsg::Shutdown => {
break;
}
}
2023-01-11 20:20:38 +00:00
}
2022-04-02 21:46:46 +00:00
{
2022-07-06 00:44:54 +00:00
let evts = {
let mut rdom = rdom.borrow_mut();
handler.get_events(&taffy.lock().expect("taffy lock poisoned"), &mut rdom)
2022-07-06 00:44:54 +00:00
};
{
updated |= handler.state().focus_state.clean();
}
2022-04-02 21:46:46 +00:00
for e in evts {
vdom.handle_event(e.name, e.data, e.id, e.bubbles)
2022-04-02 21:46:46 +00:00
}
2022-07-06 00:44:54 +00:00
let mut rdom = rdom.borrow_mut();
let mutations = vdom.render_immediate();
handler.prune(&mutations, &rdom);
// updates the dom's nodes
let (to_update, dirty) = rdom.apply_mutations(mutations);
2022-04-02 21:46:46 +00:00
// update the style and layout
let mut any_map = SendAnyMap::new();
2022-06-10 22:23:30 +00:00
any_map.insert(taffy.clone());
to_rerender = rdom.update_state(to_update, any_map);
for (id, mask) in dirty {
if mask.overlaps(&NodeMask::new().with_text()) {
to_rerender.insert(id);
}
}
2022-03-31 01:45:41 +00:00
}
2022-01-01 06:08:31 +00:00
}
2022-01-01 14:49:08 +00:00
2022-04-05 17:08:01 +00:00
if let Some(terminal) = &mut terminal {
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
terminal.show_cursor()?;
}
2022-01-01 14:49:08 +00:00
Ok(())
})
2021-07-28 14:52:38 +00:00
}
2022-06-14 00:55:57 +00:00
#[derive(Debug)]
2022-01-01 06:08:31 +00:00
enum InputEvent {
2022-01-01 14:49:08 +00:00
UserInput(TermEvent),
2022-03-09 18:30:44 +00:00
Close,
2022-01-01 06:08:31 +00:00
}