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-02-04 22:57:00 +00:00
|
|
|
use futures::{channel::mpsc::UnboundedSender, pin_mut, StreamExt};
|
2022-01-01 06:08:31 +00:00
|
|
|
use std::{
|
|
|
|
collections::HashMap,
|
|
|
|
io,
|
|
|
|
time::{Duration, Instant},
|
|
|
|
};
|
2022-01-01 04:53:37 +00:00
|
|
|
use stretch2::{prelude::Size, Stretch};
|
2022-02-17 22:06:28 +00:00
|
|
|
use style::RinkStyle;
|
|
|
|
use tui::{backend::CrosstermBackend, Terminal};
|
2022-01-01 04:53:37 +00:00
|
|
|
|
|
|
|
mod attributes;
|
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 layout;
|
|
|
|
mod render;
|
2022-02-17 22:06:28 +00:00
|
|
|
mod style;
|
|
|
|
mod widget;
|
2022-01-01 04:53:37 +00:00
|
|
|
|
|
|
|
pub use attributes::*;
|
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 layout::*;
|
|
|
|
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-01-12 14:40:36 +00:00
|
|
|
dom.rebuild();
|
|
|
|
|
2022-02-17 22:06:28 +00:00
|
|
|
render_vdom(&mut dom, tx, handler, cfg).unwrap();
|
2022-01-12 14:40:36 +00:00
|
|
|
}
|
|
|
|
|
2022-01-01 04:53:37 +00:00
|
|
|
pub struct TuiNode<'a> {
|
|
|
|
pub layout: stretch2::node::Node,
|
2022-02-17 22:06:28 +00:00
|
|
|
pub block_style: RinkStyle,
|
2022-02-12 20:03:08 +00:00
|
|
|
pub tui_modifier: TuiModifier,
|
2022-01-01 04:53:37 +00:00
|
|
|
pub node: &'a VNode<'a>,
|
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-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();
|
|
|
|
|
|
|
|
loop {
|
|
|
|
/*
|
|
|
|
-> collect all the nodes with their layout
|
|
|
|
-> solve their layout
|
2022-02-07 11:57:57 +00:00
|
|
|
-> resolve events
|
2022-01-01 14:49:08 +00:00
|
|
|
-> render the nodes in the right place with tui/crosstream
|
|
|
|
-> while rendering, apply styling
|
|
|
|
|
|
|
|
use simd to compare lines for diffing?
|
|
|
|
|
|
|
|
|
|
|
|
todo: reuse the layout and node objects.
|
|
|
|
our work_with_deadline method can tell us which nodes are dirty.
|
|
|
|
*/
|
|
|
|
let mut layout = Stretch::new();
|
|
|
|
let mut nodes = HashMap::new();
|
|
|
|
|
|
|
|
let root_node = vdom.base_scope().root_node();
|
|
|
|
layout::collect_layout(&mut layout, &mut nodes, vdom, root_node);
|
|
|
|
/*
|
2022-02-07 11:57:57 +00:00
|
|
|
Compute the layout given the terminal size
|
2022-01-01 14:49:08 +00:00
|
|
|
*/
|
|
|
|
let node_id = root_node.try_mounted_id().unwrap();
|
|
|
|
let root_layout = nodes[&node_id].layout;
|
2022-02-07 11:57:57 +00:00
|
|
|
let mut events = Vec::new();
|
2022-01-01 14:49:08 +00:00
|
|
|
|
|
|
|
terminal.draw(|frame| {
|
2022-02-05 02:19:17 +00:00
|
|
|
// size is guaranteed to not change when rendering
|
|
|
|
let dims = frame.size();
|
|
|
|
let width = dims.width;
|
|
|
|
let height = dims.height;
|
|
|
|
layout
|
|
|
|
.compute_layout(
|
|
|
|
root_layout,
|
|
|
|
Size {
|
|
|
|
width: stretch2::prelude::Number::Defined(width as f32),
|
|
|
|
height: stretch2::prelude::Number::Defined(height as f32),
|
|
|
|
},
|
|
|
|
)
|
|
|
|
.unwrap();
|
2022-02-07 11:57:57 +00:00
|
|
|
|
|
|
|
// resolve events before rendering
|
|
|
|
events = handler.get_events(vdom, &layout, &mut nodes, root_node);
|
2022-02-12 20:03:08 +00:00
|
|
|
render::render_vnode(
|
|
|
|
frame,
|
|
|
|
&layout,
|
|
|
|
&mut nodes,
|
|
|
|
vdom,
|
|
|
|
root_node,
|
2022-02-17 22:06:28 +00:00
|
|
|
&RinkStyle::default(),
|
|
|
|
cfg,
|
2022-02-12 20:03:08 +00:00
|
|
|
);
|
2022-01-01 14:49:08 +00:00
|
|
|
assert!(nodes.is_empty());
|
|
|
|
})?;
|
|
|
|
|
2022-02-07 11:57:57 +00:00
|
|
|
for e in events {
|
|
|
|
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
|
|
|
|
|
|
|
vdom.work_with_deadline(|| false);
|
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
|
|
|
}
|