finishing touches and benchmarks

This commit is contained in:
Evan Almloff 2022-03-30 20:45:41 -05:00
parent 05d04165ba
commit 5b25500c0b
15 changed files with 305 additions and 206 deletions

View file

@ -28,6 +28,8 @@ dioxus-tui = { path = "./packages/tui", version = "^0.2.0", optional = true }
dioxus-liveview = { path = "./packages/liveview", optional = true }
dioxus-native-core = { path = "./packages/native-core", optional = true }
# dioxus-mobile = { path = "./packages/mobile", version = "^0.2.0", optional = true }
# dioxus-rsx = { path = "./packages/rsx", optional = true }
# macro = ["dioxus-core-macro", "dioxus-rsx"]
@ -45,6 +47,7 @@ ayatana = ["dioxus-desktop/ayatana"]
router = ["dioxus-router"]
tui = ["dioxus-tui"]
liveview = ["dioxus-liveview"]
native-core = ["dioxus-native-core"]
[workspace]
@ -90,5 +93,6 @@ harness = false
name = "jsframework"
harness = false
[profile.release]
debug = true
[[bench]]
name = "tui_update"
harness = false

196
benches/tui_update.rs Normal file
View file

@ -0,0 +1,196 @@
use criterion::{criterion_group, criterion_main, BenchmarkId, Criterion};
use dioxus::prelude::*;
use dioxus_tui::TuiContext;
criterion_group!(mbenches, update_boxes);
criterion_main!(mbenches);
/// This benchmarks the cache performance of the TUI for small edits by changing one box at a time.
fn update_boxes(c: &mut Criterion) {
let mut group = c.benchmark_group("Update boxes");
// We can override the configuration on a per-group level
group.sample_size(10);
// We can also use loops to define multiple benchmarks, even over multiple dimensions.
for size in 1..=6 {
let parameter_string = format!("{}", 5 * size);
group.bench_with_input(
BenchmarkId::new("size", parameter_string),
&size,
|b, size| {
b.iter(|| match size {
1 => dioxus::tui::launch(app5),
2 => dioxus::tui::launch(app10),
3 => dioxus::tui::launch(app15),
4 => dioxus::tui::launch(app20),
5 => dioxus::tui::launch(app25),
6 => dioxus::tui::launch(app30),
_ => (),
})
},
);
}
}
#[derive(Props, PartialEq)]
struct BoxProps {
x: usize,
y: usize,
hue: f32,
alpha: f32,
}
#[allow(non_snake_case)]
fn Box(cx: Scope<BoxProps>) -> Element {
let count = use_state(&cx, || 0);
let x = cx.props.x * 2;
let y = cx.props.y * 2;
let hue = cx.props.hue;
let display_hue = cx.props.hue as u32 / 10;
let count = count.get();
let alpha = cx.props.alpha + (count % 100) as f32;
cx.render(rsx! {
div {
left: "{x}%",
top: "{y}%",
width: "100%",
height: "100%",
background_color: "hsl({hue}, 100%, 50%, {alpha}%)",
align_items: "center",
p{"{display_hue:03}"}
}
})
}
#[derive(Props, PartialEq)]
struct GridProps {
size: usize,
}
#[allow(non_snake_case)]
fn Grid(cx: Scope<GridProps>) -> Element {
let size = cx.props.size;
let count = use_state(&cx, || 0);
let counts = use_ref(&cx, || vec![0; size * size]);
let ctx: TuiContext = cx.consume_context().unwrap();
if *count.get() + 1 >= (size * size) {
ctx.quit();
} else {
counts.with_mut(|c| {
let i = *count.current();
c[i] += 1;
c[i] = c[i] % 360;
});
count.with_mut(|i| {
*i += 1;
*i = *i % (size * size);
});
}
cx.render(rsx! {
div{
width: "100%",
height: "100%",
flex_direction: "column",
(0..size).map(|x|
{
cx.render(rsx! {
div{
width: "100%",
height: "100%",
flex_direction: "row",
(0..size).map(|y|
{
let alpha = y as f32*100.0/size as f32 + counts.read()[x*size + y] as f32;
let key = format!("{}-{}", x, y);
cx.render(rsx! {
Box{
x: x,
y: y,
alpha: 100.0,
hue: alpha,
key: "{key}",
}
})
}
)
}
})
}
)
}
})
}
fn app5(cx: Scope) -> Element {
cx.render(rsx! {
div{
width: "100%",
height: "100%",
Grid{
size: 5,
}
}
})
}
fn app10(cx: Scope) -> Element {
cx.render(rsx! {
div{
width: "100%",
height: "100%",
Grid{
size: 10,
}
}
})
}
fn app15(cx: Scope) -> Element {
cx.render(rsx! {
div{
width: "100%",
height: "100%",
Grid{
size: 15,
}
}
})
}
fn app20(cx: Scope) -> Element {
cx.render(rsx! {
div{
width: "100%",
height: "100%",
Grid{
size: 20,
}
}
})
}
fn app25(cx: Scope) -> Element {
cx.render(rsx! {
div{
width: "100%",
height: "100%",
Grid{
size: 25,
}
}
})
}
fn app30(cx: Scope) -> Element {
cx.render(rsx! {
div{
width: "100%",
height: "100%",
Grid{
size: 30,
}
}
})
}

View file

@ -1,88 +0,0 @@
use dioxus::prelude::*;
fn main() {
dioxus::tui::launch_cfg(
app,
dioxus::tui::Config {
rendering_mode: dioxus::tui::RenderingMode::Rgb,
},
);
}
#[derive(Props, PartialEq)]
struct BoxProps {
x: i32,
y: i32,
hue: f32,
alpha: f32,
}
#[allow(non_snake_case)]
fn Box(cx: Scope<BoxProps>) -> Element {
let count = use_state(&cx, || 0);
use_future(&cx, (), move |_| {
let count = count.to_owned();
let update = cx.schedule_update();
async move {
loop {
count.with_mut(|i| *i += 1);
tokio::time::sleep(std::time::Duration::from_millis(800)).await;
update();
}
}
});
let x = cx.props.x * 2;
let y = cx.props.y * 2;
let hue = cx.props.hue;
let count = count.get();
let alpha = cx.props.alpha + (count % 100) as f32;
cx.render(rsx! {
div {
left: "{x}%",
top: "{y}%",
width: "100%",
height: "100%",
background_color: "hsl({hue}, 100%, 50%, {alpha}%)",
align_items: "center",
p{"{count}"}
}
})
}
fn app(cx: Scope) -> Element {
let steps = 50;
cx.render(rsx! {
div{
width: "100%",
height: "100%",
flex_direction: "column",
(0..=steps).map(|x|
{
let hue = x as f32*360.0/steps as f32;
cx.render(rsx! {
div{
width: "100%",
height: "100%",
flex_direction: "row",
(0..=steps).map(|y|
{
let alpha = y as f32*100.0/steps as f32;
cx.render(rsx! {
Box{
x: x,
y: y,
alpha: alpha,
hue: hue,
}
})
}
)
}
})
}
)
}
})
}

View file

@ -32,12 +32,7 @@
use stretch2::{prelude::*, style::PositionType};
/// applies the entire html namespace defined in dioxus-html
pub fn apply_layout_attributes(
//
name: &str,
value: &str,
style: &mut Style,
) {
pub fn apply_layout_attributes(name: &str, value: &str, style: &mut Style) {
match name {
"align-content"
| "align-items"
@ -253,6 +248,7 @@ pub fn apply_layout_attributes(
}
}
/// a relative or absolute size
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum UnitSystem {
Percent(f32),
@ -268,6 +264,7 @@ impl Into<Dimension> for UnitSystem {
}
}
/// parse relative or absolute value
pub fn parse_value(value: &str) -> Option<UnitSystem> {
if value.ends_with("px") {
if let Ok(px) = value.trim_end_matches("px").parse::<f32>() {
@ -605,11 +602,7 @@ fn apply_align(name: &str, value: &str, style: &mut Style) {
}
}
pub fn apply_size(_name: &str, _value: &str, _style: &mut Style) {
//
}
pub fn apply_margin(name: &str, value: &str, style: &mut Style) {
fn apply_margin(name: &str, value: &str, style: &mut Style) {
match parse_value(value) {
Some(UnitSystem::Percent(v)) => match name {
"margin" => {

View file

@ -1,3 +1,2 @@
pub mod client_tree;
pub mod layout;
pub mod layout_attributes;
pub mod real_dom;

View file

@ -6,20 +6,20 @@ use std::{
use dioxus_core::{ElementId, Mutations, VNode, VirtualDom};
/// A tree that can sync with dioxus mutations backed by a hashmap.
/// Intended for use in lazy native renderers with a state that passes from parrent to children and or accumulates state from children to parrents.
/// To get started implement [PushedDownState] and or [BubbledUpState] and call [Tree::apply_mutations] and [Tree::update_state].
/// A tree that can sync with the VirtualDom mutations intended for use in lazy renderers.
/// The render state passes from parent to children and or accumulates state from children to parents.
/// To get started implement [PushedDownState] and or [BubbledUpState] and call [Tree::apply_mutations] to update the tree and [Tree::update_state] to update the state of the nodes.
#[derive(Debug)]
pub struct ClientTree<US: BubbledUpState = (), DS: PushedDownState = ()> {
pub struct RealDom<US: BubbledUpState = (), DS: PushedDownState = ()> {
root: usize,
nodes: Vec<Option<TreeNode<US, DS>>>,
nodes_listening: FxHashMap<&'static str, FxHashSet<usize>>,
node_stack: smallvec::SmallVec<[usize; 10]>,
}
impl<US: BubbledUpState, DS: PushedDownState> ClientTree<US, DS> {
pub fn new() -> ClientTree<US, DS> {
ClientTree {
impl<US: BubbledUpState, DS: PushedDownState> RealDom<US, DS> {
pub fn new() -> RealDom<US, DS> {
RealDom {
root: 0,
nodes: {
let mut v = Vec::new();
@ -187,6 +187,7 @@ impl<US: BubbledUpState, DS: PushedDownState> ClientTree<US, DS> {
ds_ctx: &mut DS::Ctx,
) -> Option<FxHashSet<usize>> {
let mut to_rerender = FxHashSet::default();
to_rerender.extend(nodes_updated.iter());
let mut nodes_updated: Vec<_> = nodes_updated
.into_iter()
.map(|id| (id, self[id].height))
@ -296,10 +297,11 @@ impl<US: BubbledUpState, DS: PushedDownState> ClientTree<US, DS> {
}
}
// remove a node and it's children from the tree.
fn remove(&mut self, id: usize) -> Option<TreeNode<US, DS>> {
// We do not need to remove the node from the parent's children list for children.
fn inner<US: BubbledUpState, DS: PushedDownState>(
tree: &mut ClientTree<US, DS>,
tree: &mut RealDom<US, DS>,
id: usize,
) -> Option<TreeNode<US, DS>> {
let mut node = tree.nodes[id as usize].take()?;
@ -418,9 +420,9 @@ impl<US: BubbledUpState, DS: PushedDownState> ClientTree<US, DS> {
}
/// Call a function for each node in the tree, depth first.
pub fn traverse(&self, mut f: impl FnMut(&TreeNode<US, DS>)) {
pub fn traverse_depth_first(&self, mut f: impl FnMut(&TreeNode<US, DS>)) {
fn inner<US: BubbledUpState, DS: PushedDownState>(
tree: &ClientTree<US, DS>,
tree: &RealDom<US, DS>,
id: ElementId,
f: &mut impl FnMut(&TreeNode<US, DS>),
) {
@ -446,7 +448,7 @@ impl<US: BubbledUpState, DS: PushedDownState> ClientTree<US, DS> {
}
}
impl<US: BubbledUpState, DS: PushedDownState> Index<usize> for ClientTree<US, DS> {
impl<US: BubbledUpState, DS: PushedDownState> Index<usize> for RealDom<US, DS> {
type Output = TreeNode<US, DS>;
fn index(&self, idx: usize) -> &Self::Output {
@ -454,7 +456,7 @@ impl<US: BubbledUpState, DS: PushedDownState> Index<usize> for ClientTree<US, DS
}
}
impl<US: BubbledUpState, DS: PushedDownState> Index<ElementId> for ClientTree<US, DS> {
impl<US: BubbledUpState, DS: PushedDownState> Index<ElementId> for RealDom<US, DS> {
type Output = TreeNode<US, DS>;
fn index(&self, idx: ElementId) -> &Self::Output {
@ -462,12 +464,12 @@ impl<US: BubbledUpState, DS: PushedDownState> Index<ElementId> for ClientTree<US
}
}
impl<US: BubbledUpState, DS: PushedDownState> IndexMut<usize> for ClientTree<US, DS> {
impl<US: BubbledUpState, DS: PushedDownState> IndexMut<usize> for RealDom<US, DS> {
fn index_mut(&mut self, idx: usize) -> &mut Self::Output {
self.get_mut(idx).expect("Node does not exist")
}
}
impl<US: BubbledUpState, DS: PushedDownState> IndexMut<ElementId> for ClientTree<US, DS> {
impl<US: BubbledUpState, DS: PushedDownState> IndexMut<ElementId> for RealDom<US, DS> {
fn index_mut(&mut self, idx: ElementId) -> &mut Self::Output {
&mut self[idx.0]
}
@ -557,7 +559,7 @@ impl PushedDownState for () {
fn reduce(&mut self, _parent: Option<&Self>, _vnode: &VNode, _ctx: &mut Self::Ctx) {}
}
/// This state is derived from children. For example a non-flexbox div's size could be derived from the size of children.
/// This state is derived from children. For example a node's size could be derived from the size of children.
/// Called when the current node's node properties are modified, a child's [BubbledUpState] is modified or a child is removed.
/// Called at most once per update.
pub trait BubbledUpState: Default + PartialEq + Clone {

View file

@ -2,7 +2,7 @@ use dioxus_core::VNode;
use dioxus_core::*;
use dioxus_core_macro::*;
use dioxus_html as dioxus_elements;
use dioxus_native_core::client_tree::ClientTree;
use dioxus_native_core::real_dom::RealDom;
use std::cell::Cell;
#[test]
@ -20,7 +20,7 @@ fn tree_remove_node() {
}
});
let mut tree: ClientTree<(), ()> = ClientTree::new();
let mut tree: RealDom<(), ()> = RealDom::new();
let _to_update = tree.apply_mutations(vec![mutations]);
let child_div = VElement {
@ -92,7 +92,7 @@ fn tree_add_node() {
div{}
});
let mut tree: ClientTree<(), ()> = ClientTree::new();
let mut tree: RealDom<(), ()> = RealDom::new();
let _to_update = tree.apply_mutations(vec![mutations]);

View file

@ -4,7 +4,7 @@ use dioxus_core::VNode;
use dioxus_core::*;
use dioxus_core_macro::*;
use dioxus_html as dioxus_elements;
use dioxus_native_core::client_tree::ClientTree;
use dioxus_native_core::real_dom::RealDom;
#[test]
fn tree_initial_build_simple() {
@ -21,7 +21,7 @@ fn tree_initial_build_simple() {
div{}
});
let mut tree: ClientTree<(), ()> = ClientTree::new();
let mut tree: RealDom<(), ()> = RealDom::new();
let _to_update = tree.apply_mutations(vec![mutations]);
let root_div = VElement {
@ -60,7 +60,7 @@ fn tree_initial_build_with_children() {
}
});
let mut tree: ClientTree<(), ()> = ClientTree::new();
let mut tree: RealDom<(), ()> = RealDom::new();
let _to_update = tree.apply_mutations(vec![mutations]);
let first_text = VText {

View file

@ -2,7 +2,7 @@ use dioxus_core::VNode;
use dioxus_core::*;
use dioxus_core_macro::*;
use dioxus_html as dioxus_elements;
use dioxus_native_core::client_tree::*;
use dioxus_native_core::real_dom::*;
#[derive(Debug, Clone, PartialEq, Default)]
struct CallCounter(u32);
@ -77,7 +77,7 @@ fn tree_state_initial() {
}
});
let mut tree: ClientTree<BubbledUpStateTester, PushedDownStateTester> = ClientTree::new();
let mut tree: RealDom<BubbledUpStateTester, PushedDownStateTester> = RealDom::new();
let nodes_updated = tree.apply_mutations(vec![mutations]);
let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut 42, &mut 42);
@ -178,12 +178,12 @@ fn tree_state_reduce_initally_called_minimally() {
}
});
let mut tree: ClientTree<CallCounter, CallCounter> = ClientTree::new();
let mut tree: RealDom<CallCounter, CallCounter> = RealDom::new();
let nodes_updated = tree.apply_mutations(vec![mutations]);
let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ());
tree.traverse(|n| {
tree.traverse_depth_first(|n| {
assert_eq!(n.up_state.0, 1);
assert_eq!(n.down_state.0, 1);
});
@ -233,7 +233,7 @@ fn tree_state_reduce_down_called_minimally_on_update() {
}
});
let mut tree: ClientTree<CallCounter, CallCounter> = ClientTree::new();
let mut tree: RealDom<CallCounter, CallCounter> = RealDom::new();
let nodes_updated = tree.apply_mutations(vec![mutations]);
let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ());
@ -249,7 +249,7 @@ fn tree_state_reduce_down_called_minimally_on_update() {
}]);
let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ());
tree.traverse(|n| {
tree.traverse_depth_first(|n| {
assert_eq!(n.down_state.0, 2);
});
}
@ -298,7 +298,7 @@ fn tree_state_reduce_up_called_minimally_on_update() {
}
});
let mut tree: ClientTree<CallCounter, CallCounter> = ClientTree::new();
let mut tree: RealDom<CallCounter, CallCounter> = RealDom::new();
let nodes_updated = tree.apply_mutations(vec![mutations]);
let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ());
@ -314,7 +314,7 @@ fn tree_state_reduce_up_called_minimally_on_update() {
}]);
let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ());
tree.traverse(|n| {
tree.traverse_depth_first(|n| {
assert_eq!(n.up_state.0, if n.id.0 > 4 { 1 } else { 2 });
});
}

View file

@ -5,10 +5,7 @@ use dioxus_core::*;
use fxhash::{FxHashMap, FxHashSet};
use dioxus_html::{on::*, KeyCode};
use dioxus_native_core::{
client_tree::{ClientTree, TreeNode},
layout::StretchLayout,
};
use dioxus_native_core::real_dom::{RealDom, TreeNode};
use futures::{channel::mpsc::UnboundedReceiver, StreamExt};
use std::{
any::Any,
@ -19,6 +16,7 @@ use std::{
};
use stretch2::{prelude::Layout, Stretch};
use crate::layout::StretchLayout;
use crate::style_attributes::StyleModifier;
// a wrapper around the input state for easier access
@ -169,7 +167,7 @@ impl InnerInputState {
evts: &mut Vec<EventCore>,
resolved_events: &mut Vec<UserEvent>,
layout: &Stretch,
tree: &mut ClientTree<StretchLayout, StyleModifier>,
tree: &mut RealDom<StretchLayout, StyleModifier>,
) {
let previous_mouse = self
.mouse
@ -194,7 +192,7 @@ impl InnerInputState {
previous_mouse: Option<(MouseData, Vec<u16>)>,
resolved_events: &mut Vec<UserEvent>,
layout: &Stretch,
tree: &mut ClientTree<StretchLayout, StyleModifier>,
tree: &mut RealDom<StretchLayout, StyleModifier>,
) {
struct Data<'b> {
new_pos: (i32, i32),
@ -219,7 +217,7 @@ impl InnerInputState {
will_bubble: &mut FxHashSet<ElementId>,
resolved_events: &mut Vec<UserEvent>,
node: &TreeNode<StretchLayout, StyleModifier>,
tree: &ClientTree<StretchLayout, StyleModifier>,
tree: &RealDom<StretchLayout, StyleModifier>,
) {
// only trigger event if the event was not triggered already by a child
if will_bubble.insert(node.id) {
@ -546,7 +544,7 @@ impl RinkInputHandler {
pub fn get_events<'a>(
&self,
layout: &Stretch,
tree: &mut ClientTree<StretchLayout, StyleModifier>,
tree: &mut RealDom<StretchLayout, StyleModifier>,
) -> Vec<UserEvent> {
let mut resolved_events = Vec::new();

View file

@ -1,15 +1,9 @@
use crate::client_tree::BubbledUpState;
use dioxus_core::*;
use dioxus_native_core::layout_attributes::apply_layout_attributes;
use dioxus_native_core::real_dom::BubbledUpState;
use stretch2::prelude::*;
use crate::layout_attributes::apply_layout_attributes;
/*
The layout system uses the lineheight as one point.
stretch uses fractional points, so we can rasterize if we need too, but not with characters
this means anything thats "1px" is 1 lineheight. Unfortunately, text cannot be smaller or bigger
*/
/// the size
#[derive(Clone, PartialEq, Default, Debug)]
pub struct StretchLayout {
pub style: Style,
@ -19,7 +13,7 @@ pub struct StretchLayout {
impl BubbledUpState for StretchLayout {
type Ctx = Stretch;
// Although we pass in the parent, the state of RinkLayout must only depend on the parent for optimiztion purposes
/// Setup the layout
fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, stretch: &mut Self::Ctx)
where
I: Iterator<Item = &'a Self>,

View file

@ -9,16 +9,20 @@ use crossterm::{
};
use dioxus_core::exports::futures_channel::mpsc::unbounded;
use dioxus_core::*;
use dioxus_native_core::{client_tree::ClientTree, layout::StretchLayout};
use futures::{channel::mpsc::UnboundedSender, pin_mut, StreamExt};
use dioxus_native_core::real_dom::RealDom;
use futures::{
channel::mpsc::{UnboundedReceiver, UnboundedSender},
pin_mut, StreamExt,
};
use layout::StretchLayout;
use std::{io, time::Duration};
use stretch2::{prelude::Size, Stretch};
use style_attributes::StyleModifier;
use tokio::time::Instant;
use tui::{backend::CrosstermBackend, Terminal};
mod config;
mod hooks;
mod layout;
mod render;
mod style;
mod style_attributes;
@ -28,6 +32,16 @@ pub use config::*;
pub use hooks::*;
pub use render::*;
#[derive(Clone)]
pub struct TuiContext {
tx: UnboundedSender<InputEvent>,
}
impl TuiContext {
pub fn quit(&self) {
self.tx.unbounded_send(InputEvent::Close).unwrap();
}
}
pub fn launch(app: Component<()>) {
launch_cfg(app, Config::default())
}
@ -40,9 +54,26 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) {
let (handler, state) = RinkInputHandler::new(rx, cx);
cx.provide_root_context(state);
// Setup input handling
let (event_tx, event_rx) = unbounded();
let event_tx_clone = event_tx.clone();
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;
}
}
}
});
let mut tree: ClientTree<StretchLayout, StyleModifier> = ClientTree::new();
cx.provide_root_context(state);
cx.provide_root_context(TuiContext { tx: event_tx_clone });
let mut tree: RealDom<StretchLayout, StyleModifier> = RealDom::new();
let mutations = dom.rebuild();
let to_update = tree.apply_mutations(vec![mutations]);
let mut stretch = Stretch::new();
@ -50,47 +81,22 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) {
.update_state(&dom, to_update, &mut stretch, &mut ())
.unwrap();
render_vdom(&mut dom, tx, handler, cfg, tree, stretch).unwrap();
render_vdom(&mut dom, tx, event_rx, handler, cfg, tree, stretch).unwrap();
}
pub fn render_vdom(
fn render_vdom(
vdom: &mut VirtualDom,
ctx: UnboundedSender<TermEvent>,
crossterm_event_sender: UnboundedSender<TermEvent>,
mut event_reciever: UnboundedReceiver<InputEvent>,
handler: RinkInputHandler,
cfg: Config,
mut tree: ClientTree<StretchLayout, StyleModifier>,
mut tree: RealDom<StretchLayout, StyleModifier>,
mut stretch: Stretch,
) -> Result<()> {
// Setup input handling
let (tx, mut rx) = unbounded();
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));
if crossterm::event::poll(timeout).unwrap() {
let evt = crossterm::event::read().unwrap();
tx.unbounded_send(InputEvent::UserInput(evt)).unwrap();
}
if last_tick.elapsed() >= tick_rate {
tx.unbounded_send(InputEvent::Tick).unwrap();
last_tick = Instant::now();
}
}
});
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();
@ -103,10 +109,9 @@ pub fn render_vdom(
loop {
/*
-> collect all the nodes
-> resolve events
-> render the nodes in the right place with tui/crossterm
-> rendering
-> wait for changes
-> resolve events
-> lazily update the layout and style based on nodes changed
use simd to compare lines for diffing?
@ -119,11 +124,11 @@ pub fn render_vdom(
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_id();
let root_node = tree[root_id].up_state.node.unwrap();
stretch
.compute_layout(
root_node,
@ -138,18 +143,12 @@ pub fn render_vdom(
})?;
}
// resolve events before rendering
// todo: events do not trigger update?
for e in handler.get_events(&stretch, &mut tree) {
vdom.handle_message(SchedulerMsg::Event(e));
}
use futures::future::{select, Either};
{
let wait = vdom.wait_for_work();
pin_mut!(wait);
match select(wait, rx.next()).await {
match select(wait, event_reciever.next()).await {
Either::Left((_a, _b)) => {
//
}
@ -166,17 +165,21 @@ pub fn render_vdom(
TermEvent::Resize(_, _) => redraw = true,
TermEvent::Mouse(_) => {}
},
InputEvent::Tick => {} // tick
InputEvent::Close => break,
};
if let InputEvent::UserInput(evt) = evt.unwrap() {
ctx.unbounded_send(evt).unwrap();
crossterm_event_sender.unbounded_send(evt).unwrap();
}
}
}
}
// resolve events before rendering
for e in handler.get_events(&stretch, &mut tree) {
vdom.handle_message(SchedulerMsg::Event(e));
}
vdom.process_all_messages();
let mutations = vdom.work_with_deadline(|| false);
// updates the tree's nodes
let to_update = tree.apply_mutations(mutations);
@ -200,7 +203,6 @@ pub fn render_vdom(
enum InputEvent {
UserInput(TermEvent),
Tick,
#[allow(dead_code)]
Close,
}

View file

@ -1,7 +1,7 @@
use crate::layout::StretchLayout;
use dioxus_native_core::{
client_tree::{ClientTree, TreeNode},
layout::StretchLayout,
layout_attributes::UnitSystem,
real_dom::{RealDom, TreeNode},
};
use std::io::Stdout;
use stretch2::{
@ -23,11 +23,11 @@ const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5];
pub fn render_vnode<'a>(
frame: &mut tui::Frame<CrosstermBackend<Stdout>>,
layout: &Stretch,
tree: &ClientTree<StretchLayout, StyleModifier>,
tree: &RealDom<StretchLayout, StyleModifier>,
node: &TreeNode<StretchLayout, StyleModifier>,
cfg: Config,
) {
use dioxus_native_core::client_tree::TreeNodeType;
use dioxus_native_core::real_dom::TreeNodeType;
match &node.node_type {
TreeNodeType::Placeholder => return,
@ -51,7 +51,6 @@ pub fn render_vnode<'a>(
fn render(self, area: Rect, mut buf: RinkBuffer) {
for (i, c) in self.text.char_indices() {
let mut new_cell = RinkCell::default();
// println!("{:?}", self.style);
new_cell.set_style(self.style);
new_cell.symbol = c.to_string();
buf.set(area.left() + i as u16, area.top(), new_cell);

View file

@ -28,7 +28,7 @@ impl RinkColor {
} else {
let [sr, sg, sb] = to_rgb(self.color).map(|e| e as u16);
let [or, og, ob] = to_rgb(other).map(|e| e as u16);
let sa: u16 = self.alpha as u16;
let sa = self.alpha as u16;
let rsa = 255 - sa;
Color::Rgb(
((sr * sa + or * rsa) / 255) as u8,

View file

@ -31,8 +31,8 @@
use dioxus_core::{Attribute, VNode};
use dioxus_native_core::{
client_tree::PushedDownState,
layout_attributes::{parse_value, UnitSystem},
real_dom::PushedDownState,
};
use crate::style::{RinkColor, RinkStyle};