dioxus/packages/tui/src/lib.rs

227 lines
8 KiB
Rust
Raw Normal View History

2022-03-23 19:18:17 +00:00
// notes:
// mouse events binary search was broken for absolutely positioned elements
2021-07-28 14:52:38 +00:00
use anyhow::Result;
2022-01-01 04:53:37 +00:00
use crossterm::{
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::exports::futures_channel::mpsc::unbounded;
use dioxus_core::*;
2022-03-23 19:18:17 +00:00
use dioxus_html::on::{KeyboardData, MouseData, PointerData, TouchData, WheelData};
2022-03-23 19:33:06 +00:00
use dioxus_native_core::{layout::StretchLayout, Tree};
2022-02-04 22:57:00 +00:00
use futures::{channel::mpsc::UnboundedSender, pin_mut, StreamExt};
2022-03-23 19:18:17 +00:00
use std::collections::HashSet;
use std::{io, time::Duration};
use stretch2::{prelude::Size, Stretch};
2022-03-18 15:43:43 +00:00
use style_attributes::StyleModifier;
2022-03-23 19:18:17 +00:00
use tokio::time::Instant;
2022-02-17 22:06:28 +00:00
use tui::{backend::CrosstermBackend, Terminal};
2022-01-01 04:53:37 +00:00
2022-02-17 22:06:28 +00:00
mod config;
2022-01-12 14:40:36 +00:00
mod hooks;
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-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-01-01 04:53:37 +00:00
pub use render::*;
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) {
2022-01-12 14:40:36 +00:00
let mut dom = VirtualDom::new(app);
let (tx, rx) = unbounded();
2022-02-04 20:52:01 +00:00
let cx = dom.base_scope();
2022-02-05 22:28:19 +00:00
let (handler, state) = RinkInputHandler::new(rx, cx);
cx.provide_root_context(state);
2022-02-04 20:52:01 +00:00
2022-03-23 19:33:06 +00:00
let mut tree: Tree<StretchLayout, StyleModifier> = Tree::new();
2022-03-18 15:43:43 +00:00
let mutations = dom.rebuild();
let to_update = tree.apply_mutations(vec![mutations]);
let mut stretch = Stretch::new();
let _to_rerender = tree
.update_state(&dom, to_update, &mut stretch, &mut ())
.unwrap();
2022-01-12 14:40:36 +00:00
2022-03-18 15:43:43 +00:00
render_vdom(&mut dom, tx, handler, cfg, tree, stretch).unwrap();
2021-07-28 14:52:38 +00:00
}
2022-02-05 22:28:19 +00:00
pub fn render_vdom(
vdom: &mut VirtualDom,
ctx: UnboundedSender<TermEvent>,
handler: RinkInputHandler,
2022-02-17 22:06:28 +00:00
cfg: Config,
2022-03-23 19:33:06 +00:00
mut tree: Tree<StretchLayout, StyleModifier>,
2022-03-18 15:43:43 +00:00
mut stretch: Stretch,
2022-02-05 22:28:19 +00:00
) -> Result<()> {
2022-01-01 06:08:31 +00:00
// Setup input handling
2022-01-01 14:49:08 +00:00
let (tx, mut rx) = unbounded();
2022-01-01 06:08:31 +00:00
std::thread::spawn(move || {
let tick_rate = Duration::from_millis(100);
let mut last_tick = Instant::now();
loop {
// poll for tick rate duration, if no events, sent tick event.
let timeout = tick_rate
.checked_sub(last_tick.elapsed())
.unwrap_or_else(|| Duration::from_secs(0));
2022-01-01 14:49:08 +00:00
if crossterm::event::poll(timeout).unwrap() {
let evt = crossterm::event::read().unwrap();
tx.unbounded_send(InputEvent::UserInput(evt)).unwrap();
2022-01-01 06:08:31 +00:00
}
if last_tick.elapsed() >= tick_rate {
2022-01-01 14:49:08 +00:00
tx.unbounded_send(InputEvent::Tick).unwrap();
2022-01-01 06:08:31 +00:00
last_tick = Instant::now();
}
}
});
2022-01-01 14:49:08 +00:00
tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()?
.block_on(async {
/*
Get the terminal to calcualte the layout from
*/
enable_raw_mode().unwrap();
let mut stdout = std::io::stdout();
execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap();
let backend = CrosstermBackend::new(io::stdout());
let mut terminal = Terminal::new(backend).unwrap();
terminal.clear().unwrap();
2022-03-23 19:18:17 +00:00
let mut to_rerender: HashSet<usize> = tree
.nodes
.iter()
.filter_map(|n| n.as_ref())
.map(|n| n.id.0)
.collect();
2022-01-01 14:49:08 +00:00
loop {
/*
2022-03-18 15:43:43 +00:00
-> collect all the nodes
2022-02-07 11:57:57 +00:00
-> resolve events
2022-03-18 15:43:43 +00:00
-> render the nodes in the right place with tui/crossterm
-> rendering
-> 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-03-23 19:18:17 +00:00
if !to_rerender.is_empty() {
terminal.draw(|frame| {
// size is guaranteed to not change when rendering
let dims = frame.size();
// println!("{dims:?}");
let width = dims.width;
let height = dims.height;
let root_id = tree.root;
let root_node = tree.get(root_id).up_state.node.unwrap();
stretch
.compute_layout(
root_node,
Size {
width: stretch2::prelude::Number::Defined((width - 1) as f32),
height: stretch2::prelude::Number::Defined((height - 1) as f32),
},
)
.unwrap();
let root = tree.get(tree.root);
render::render_vnode(frame, &stretch, &tree, &root, cfg);
})?;
}
2022-01-01 14:49:08 +00:00
2022-03-23 19:18:17 +00:00
// resolve events before rendering
// todo: events do not trigger update?
for e in handler.get_events(&stretch, &mut tree) {
2022-03-23 19:33:06 +00:00
// let tname = if e.data.is::<PointerData>() {
// "PointerData"
// } else if e.data.is::<WheelData>() {
// "WheelData"
// } else if e.data.is::<MouseData>() {
// "MouseData"
// } else if e.data.is::<KeyboardData>() {
// "KeyboardData"
// } else if e.data.is::<TouchData>() {
// "TouchData"
// } else if e.data.is::<(u16, u16)>() {
// "(u16, u16)"
// } else {
// panic!()
// };
2022-03-23 19:18:17 +00:00
// println!("{tname}: {e:?}");
2022-02-07 11:57:57 +00:00
vdom.handle_message(SchedulerMsg::Event(e));
}
2022-01-01 14:49:08 +00:00
use futures::future::{select, Either};
{
let wait = vdom.wait_for_work();
pin_mut!(wait);
match select(wait, rx.next()).await {
2022-02-04 22:57:00 +00:00
Either::Left((_a, _b)) => {
2022-01-12 14:40:36 +00:00
//
2022-01-01 14:49:08 +00:00
}
2022-02-04 22:57:00 +00:00
Either::Right((evt, _o)) => {
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)
{
break;
2022-01-01 14:49:08 +00:00
}
}
2022-01-12 14:40:36 +00:00
TermEvent::Resize(_, _) | TermEvent::Mouse(_) => {}
2022-01-01 14:49:08 +00:00
},
InputEvent::Tick => {} // tick
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
if let InputEvent::UserInput(evt) = evt.unwrap() {
ctx.unbounded_send(evt).unwrap();
}
2022-01-01 14:49:08 +00:00
}
}
2022-01-01 06:08:31 +00:00
}
2022-01-01 14:49:08 +00:00
2022-03-18 15:43:43 +00:00
let mutations = vdom.work_with_deadline(|| false);
2022-03-23 19:18:17 +00:00
// updates the tree's nodes
2022-03-18 15:43:43 +00:00
let to_update = tree.apply_mutations(mutations);
2022-03-23 19:18:17 +00:00
// update the style and layout
to_rerender = tree
2022-03-18 15:43:43 +00:00
.update_state(&vdom, to_update, &mut stretch, &mut ())
.unwrap();
2022-01-01 06:08:31 +00:00
}
2022-01-01 14:49:08 +00:00
disable_raw_mode()?;
execute!(
terminal.backend_mut(),
LeaveAlternateScreen,
DisableMouseCapture
)?;
terminal.show_cursor()?;
Ok(())
})
2021-07-28 14:52:38 +00:00
}
2022-01-01 06:08:31 +00:00
enum InputEvent {
2022-01-01 14:49:08 +00:00
UserInput(TermEvent),
2022-01-01 06:08:31 +00:00
Tick,
2022-03-09 18:30:44 +00:00
#[allow(dead_code)]
Close,
2022-01-01 06:08:31 +00:00
}