2021-07-28 14:52:38 +00:00
|
|
|
use anyhow::Result;
|
2022-04-12 23:46:16 +00:00
|
|
|
use anymap::AnyMap;
|
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-05-03 20:06:07 +00:00
|
|
|
use dioxus_native_core::real_dom::RealDom;
|
2022-05-03 16:02:35 +00:00
|
|
|
use dioxus_native_core::state::*;
|
2022-04-16 17:23:31 +00:00
|
|
|
use dioxus_native_core_macro::State;
|
2022-05-03 20:06:07 +00:00
|
|
|
use focus::Focus;
|
|
|
|
use focus::FocusState;
|
2022-03-31 01:45:41 +00:00
|
|
|
use futures::{
|
|
|
|
channel::mpsc::{UnboundedReceiver, UnboundedSender},
|
|
|
|
pin_mut, StreamExt,
|
|
|
|
};
|
|
|
|
use layout::StretchLayout;
|
2022-04-12 23:46:16 +00:00
|
|
|
use std::cell::RefCell;
|
|
|
|
use std::rc::Rc;
|
2022-03-23 19:18:17 +00:00
|
|
|
use std::{io, time::Duration};
|
|
|
|
use stretch2::{prelude::Size, Stretch};
|
2022-03-18 15:43:43 +00:00
|
|
|
use style_attributes::StyleModifier;
|
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-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
|
|
|
|
2022-04-12 23:46:16 +00:00
|
|
|
type Dom = RealDom<NodeState>;
|
|
|
|
type Node = dioxus_native_core::real_dom::Node<NodeState>;
|
|
|
|
|
2022-04-12 12:51:57 +00:00
|
|
|
#[derive(Debug, Clone, State, Default)]
|
2022-04-12 23:46:16 +00:00
|
|
|
struct NodeState {
|
2022-04-16 17:23:31 +00:00
|
|
|
#[child_dep_state(layout, RefCell<Stretch>)]
|
2022-04-12 23:46:16 +00:00
|
|
|
layout: StretchLayout,
|
|
|
|
// depends on attributes, the C component of it's parent and a u8 context
|
2022-04-16 17:23:31 +00:00
|
|
|
#[parent_dep_state(style)]
|
2022-04-12 23:46:16 +00:00
|
|
|
style: StyleModifier,
|
2022-05-03 20:06:07 +00:00
|
|
|
#[node_dep_state()]
|
|
|
|
focus: Focus,
|
2022-05-03 16:02:35 +00:00
|
|
|
focused: bool,
|
2022-04-12 23:46:16 +00:00
|
|
|
}
|
2022-04-12 12:51:57 +00:00
|
|
|
|
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-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);
|
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() {
|
|
|
|
// if crossterm::event::poll(timeout).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-02-05 22:28:19 +00:00
|
|
|
cx.provide_root_context(state);
|
2022-03-31 01:45:41 +00:00
|
|
|
cx.provide_root_context(TuiContext { tx: event_tx_clone });
|
2022-02-04 20:52:01 +00:00
|
|
|
|
2022-04-12 23:46:16 +00:00
|
|
|
let mut rdom: Dom = RealDom::new();
|
2022-03-18 15:43:43 +00:00
|
|
|
let mutations = dom.rebuild();
|
2022-04-04 18:37:04 +00:00
|
|
|
let to_update = rdom.apply_mutations(vec![mutations]);
|
2022-04-12 23:46:16 +00:00
|
|
|
let stretch = Rc::new(RefCell::new(Stretch::new()));
|
|
|
|
let mut any_map = AnyMap::new();
|
|
|
|
any_map.insert(stretch.clone());
|
|
|
|
let _to_rerender = rdom.update_state(&dom, to_update, any_map).unwrap();
|
2022-01-12 14:40:36 +00:00
|
|
|
|
2022-04-02 21:46:46 +00:00
|
|
|
render_vdom(
|
|
|
|
&mut dom,
|
|
|
|
event_rx,
|
|
|
|
handler,
|
|
|
|
cfg,
|
2022-04-04 18:37:04 +00:00
|
|
|
rdom,
|
2022-04-02 21:46:46 +00:00
|
|
|
stretch,
|
|
|
|
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,
|
2022-04-12 23:46:16 +00:00
|
|
|
mut rdom: Dom,
|
2022-04-12 23:58:25 +00:00
|
|
|
stretch: Rc<RefCell<Stretch>>,
|
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 {
|
2022-04-05 17:08:01 +00:00
|
|
|
let mut terminal = (!cfg.headless).then(|| {
|
|
|
|
enable_raw_mode().unwrap();
|
|
|
|
let mut stdout = std::io::stdout();
|
|
|
|
execute!(stdout, EnterAlternateScreen, EnableMouseCapture).unwrap();
|
|
|
|
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
|
|
|
|
2022-04-12 23:46:16 +00:00
|
|
|
let to_rerender: fxhash::FxHashSet<usize> = vec![0].into_iter().collect();
|
2022-05-03 16:02:35 +00:00
|
|
|
let mut updated = true;
|
|
|
|
|
2022-05-03 20:06:07 +00:00
|
|
|
let mut focus_state = FocusState::default();
|
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;
|
2022-04-12 23:46:16 +00:00
|
|
|
fn resize(dims: Rect, stretch: &mut Stretch, rdom: &Dom) {
|
2022-03-23 19:18:17 +00:00
|
|
|
let width = dims.width;
|
|
|
|
let height = dims.height;
|
2022-04-13 16:39:38 +00:00
|
|
|
let root_node = rdom[0].state.layout.node.unwrap();
|
2022-03-31 01:45:41 +00:00
|
|
|
|
2022-03-23 19:18:17 +00:00
|
|
|
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();
|
2022-04-05 17:08:01 +00:00
|
|
|
}
|
|
|
|
if let Some(terminal) = &mut terminal {
|
|
|
|
terminal.draw(|frame| {
|
|
|
|
// size is guaranteed to not change when rendering
|
2022-04-12 23:46:16 +00:00
|
|
|
resize(frame.size(), &mut stretch.borrow_mut(), &rdom);
|
2022-04-13 16:39:38 +00:00
|
|
|
let root = &rdom[0];
|
2022-04-17 13:43:15 +00:00
|
|
|
render::render_vnode(frame, &stretch.borrow(), &rdom, root, cfg);
|
2022-04-05 17:08:01 +00:00
|
|
|
})?;
|
|
|
|
} else {
|
|
|
|
resize(
|
|
|
|
Rect {
|
|
|
|
x: 0,
|
|
|
|
y: 0,
|
|
|
|
width: 300,
|
|
|
|
height: 300,
|
|
|
|
},
|
2022-04-12 23:46:16 +00:00
|
|
|
&mut stretch.borrow_mut(),
|
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
|
|
|
|
|
|
|
use futures::future::{select, Either};
|
|
|
|
{
|
|
|
|
let wait = vdom.wait_for_work();
|
|
|
|
pin_mut!(wait);
|
|
|
|
|
2022-03-31 01:45:41 +00:00
|
|
|
match select(wait, event_reciever.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-05-03 16:02:35 +00:00
|
|
|
let mut evt_intersepted = false;
|
|
|
|
|
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
|
|
|
if let KeyCode::BackTab = key.code {
|
2022-05-03 20:06:07 +00:00
|
|
|
evt_intersepted =
|
|
|
|
focus_state.progress(&mut rdom, false);
|
2022-05-03 16:02:35 +00:00
|
|
|
}
|
|
|
|
if let KeyCode::Tab = key.code {
|
2022-05-03 20:06:07 +00:00
|
|
|
evt_intersepted = focus_state.progress(&mut rdom, true);
|
2022-05-03 16:02:35 +00:00
|
|
|
}
|
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 16:02:35 +00:00
|
|
|
if !evt_intersepted {
|
|
|
|
if let InputEvent::UserInput(evt) = evt.unwrap() {
|
|
|
|
register_event(evt);
|
|
|
|
}
|
2022-01-12 14:40:36 +00:00
|
|
|
}
|
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-04-02 21:46:46 +00:00
|
|
|
{
|
2022-04-12 23:46:16 +00:00
|
|
|
let evts = handler.get_events(&stretch.borrow(), &mut rdom);
|
2022-04-02 21:46:46 +00:00
|
|
|
for e in evts {
|
|
|
|
vdom.handle_message(SchedulerMsg::Event(e));
|
|
|
|
}
|
|
|
|
let mutations = vdom.work_with_deadline(|| false);
|
2022-05-03 16:02:35 +00:00
|
|
|
for m in &mutations {
|
2022-05-03 20:06:07 +00:00
|
|
|
focus_state.prune(m, &rdom);
|
2022-05-03 16:02:35 +00:00
|
|
|
}
|
2022-04-04 18:37:04 +00:00
|
|
|
// updates the dom's nodes
|
|
|
|
let to_update = rdom.apply_mutations(mutations);
|
2022-04-02 21:46:46 +00:00
|
|
|
// update the style and layout
|
2022-04-12 23:46:16 +00:00
|
|
|
let mut any_map = AnyMap::new();
|
|
|
|
any_map.insert(stretch.clone());
|
2022-04-17 13:43:15 +00:00
|
|
|
let _to_rerender = rdom.update_state(vdom, to_update, any_map).unwrap();
|
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-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
|
|
|
}
|