mirror of
https://github.com/DioxusLabs/dioxus
synced 2024-11-27 06:30:20 +00:00
bugfixes, testing and refactoring
This commit is contained in:
parent
6adfa8805c
commit
7f4e257757
20 changed files with 3888 additions and 739 deletions
2541
.vscode/launch.json
vendored
Normal file
2541
.vscode/launch.json
vendored
Normal file
File diff suppressed because it is too large
Load diff
|
@ -4,54 +4,11 @@ fn main() {
|
||||||
dioxus::tui::launch_cfg(
|
dioxus::tui::launch_cfg(
|
||||||
app,
|
app,
|
||||||
dioxus::tui::Config {
|
dioxus::tui::Config {
|
||||||
rendering_mode: dioxus::tui::RenderingMode::Rgb,
|
rendering_mode: dioxus::tui::RenderingMode::Ansi,
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Props, PartialEq)]
|
|
||||||
struct BoxProps {
|
|
||||||
x: i32,
|
|
||||||
y: i32,
|
|
||||||
hue: f32,
|
|
||||||
alpha: f32,
|
|
||||||
}
|
|
||||||
fn Box(cx: Scope<BoxProps>) -> Element {
|
|
||||||
let painted = use_state(&cx, || true);
|
|
||||||
|
|
||||||
// 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;
|
|
||||||
let y = cx.props.y;
|
|
||||||
let hue = cx.props.hue;
|
|
||||||
let current_painted = painted.get();
|
|
||||||
let alpha = cx.props.alpha + if *current_painted { 100.0 } else { 0.0 };
|
|
||||||
|
|
||||||
cx.render(rsx! {
|
|
||||||
div {
|
|
||||||
left: "{x}px",
|
|
||||||
top: "{y}px",
|
|
||||||
width: "100%",
|
|
||||||
height: "100%",
|
|
||||||
background_color: "hsl({hue}, 100%, 50%, {alpha}%)",
|
|
||||||
align_items: "center",
|
|
||||||
onkeydown: |_| painted.with_mut(|i| *i = !*i),
|
|
||||||
onmouseenter: |_| painted.with_mut(|i| *i = !*i),
|
|
||||||
p{" "}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
fn app(cx: Scope) -> Element {
|
||||||
let steps = 50;
|
let steps = 50;
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
|
@ -71,11 +28,12 @@ fn app(cx: Scope) -> Element {
|
||||||
{
|
{
|
||||||
let alpha = y as f32*100.0/steps as f32;
|
let alpha = y as f32*100.0/steps as f32;
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
Box{
|
div {
|
||||||
x: x,
|
left: "{x}px",
|
||||||
y: y,
|
top: "{y}px",
|
||||||
alpha: alpha,
|
width: "10%",
|
||||||
hue: hue,
|
height: "100%",
|
||||||
|
background_color: "hsl({hue}, 100%, 50%, {alpha}%)",
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,26 +5,15 @@ fn main() {
|
||||||
}
|
}
|
||||||
|
|
||||||
fn app(cx: Scope) -> Element {
|
fn app(cx: Scope) -> Element {
|
||||||
let alpha = use_state(&cx, || 100);
|
|
||||||
|
|
||||||
cx.render(rsx! {
|
cx.render(rsx! {
|
||||||
div {
|
div {
|
||||||
onwheel: move |evt| alpha.set((**alpha + evt.data.delta_y as i64).min(100).max(0)),
|
|
||||||
|
|
||||||
width: "100%",
|
width: "100%",
|
||||||
height: "10px",
|
height: "10px",
|
||||||
background_color: "red",
|
background_color: "red",
|
||||||
// justify_content: "center",
|
justify_content: "center",
|
||||||
// align_items: "center",
|
align_items: "center",
|
||||||
|
|
||||||
p{
|
"Hello world!"
|
||||||
color: "rgba(0, 255, 0, {alpha}%)",
|
|
||||||
"Hello world!"
|
|
||||||
}
|
|
||||||
p{
|
|
||||||
"{alpha}"
|
|
||||||
}
|
|
||||||
// p{"Hi"}
|
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
88
examples/tui_stress_test.rs
Normal file
88
examples/tui_stress_test.rs
Normal file
|
@ -0,0 +1,88 @@
|
||||||
|
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,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
|
@ -15,7 +15,7 @@ fn app(cx: Scope) -> Element {
|
||||||
onwheel: move |evt| alpha.set((**alpha + evt.data.delta_y as i64).min(100).max(0)),
|
onwheel: move |evt| alpha.set((**alpha + evt.data.delta_y as i64).min(100).max(0)),
|
||||||
|
|
||||||
p {
|
p {
|
||||||
// background_color: "black",
|
background_color: "black",
|
||||||
flex_direction: "column",
|
flex_direction: "column",
|
||||||
justify_content: "center",
|
justify_content: "center",
|
||||||
align_items: "center",
|
align_items: "center",
|
||||||
|
|
|
@ -423,6 +423,7 @@ impl<'b> DiffState<'b> {
|
||||||
match (old.children.len(), new.children.len()) {
|
match (old.children.len(), new.children.len()) {
|
||||||
(0, 0) => {}
|
(0, 0) => {}
|
||||||
(0, _) => {
|
(0, _) => {
|
||||||
|
self.mutations.push_root(root);
|
||||||
let created = self.create_children(new.children);
|
let created = self.create_children(new.children);
|
||||||
self.mutations.append_children(created as u32);
|
self.mutations.append_children(created as u32);
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,8 +3,6 @@ name = "dioxus-native-core"
|
||||||
version = "0.2.0"
|
version = "0.2.0"
|
||||||
edition = "2021"
|
edition = "2021"
|
||||||
license = "MIT/Apache-2.0"
|
license = "MIT/Apache-2.0"
|
||||||
authors = ["@dementhos"]
|
|
||||||
description = "TUI-based renderer for Dioxus"
|
|
||||||
repository = "https://github.com/DioxusLabs/dioxus/"
|
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||||
homepage = "https://dioxuslabs.com"
|
homepage = "https://dioxuslabs.com"
|
||||||
|
|
||||||
|
@ -13,5 +11,10 @@ dioxus-core = { path = "../core", version = "^0.2.0" }
|
||||||
dioxus-html = { path = "../html", version = "^0.2.0" }
|
dioxus-html = { path = "../html", version = "^0.2.0" }
|
||||||
dioxus-core-macro = { path = "../core-macro", version = "^0.2.0" }
|
dioxus-core-macro = { path = "../core-macro", version = "^0.2.0" }
|
||||||
|
|
||||||
stretch2 = "0.4.1"
|
# stretch2 = "0.4.1"
|
||||||
|
stretch2 = { git = "https://github.com/Demonthos/stretch.git" }
|
||||||
smallvec = "1.6"
|
smallvec = "1.6"
|
||||||
|
fxhash = "0.2"
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
rand = "0.8.5"
|
580
packages/native-core/src/client_tree.rs
Normal file
580
packages/native-core/src/client_tree.rs
Normal file
|
@ -0,0 +1,580 @@
|
||||||
|
use fxhash::{FxHashMap, FxHashSet};
|
||||||
|
use std::{
|
||||||
|
collections::VecDeque,
|
||||||
|
ops::{Index, IndexMut},
|
||||||
|
};
|
||||||
|
|
||||||
|
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].
|
||||||
|
#[derive(Debug)]
|
||||||
|
pub struct ClientTree<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 {
|
||||||
|
root: 0,
|
||||||
|
nodes: {
|
||||||
|
let mut v = Vec::new();
|
||||||
|
v.push(Some(TreeNode::new(
|
||||||
|
0,
|
||||||
|
TreeNodeType::Element {
|
||||||
|
tag: "Root".to_string(),
|
||||||
|
namespace: Some("Root"),
|
||||||
|
children: Vec::new(),
|
||||||
|
},
|
||||||
|
)));
|
||||||
|
v
|
||||||
|
},
|
||||||
|
nodes_listening: FxHashMap::default(),
|
||||||
|
node_stack: smallvec::SmallVec::new(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Updates the tree, up and down state and return a set of nodes that were updated pass this to update_state.
|
||||||
|
pub fn apply_mutations(&mut self, mutations_vec: Vec<Mutations>) -> Vec<usize> {
|
||||||
|
let mut nodes_updated = Vec::new();
|
||||||
|
for mutations in mutations_vec {
|
||||||
|
for e in mutations.edits {
|
||||||
|
use dioxus_core::DomEdit::*;
|
||||||
|
match e {
|
||||||
|
PushRoot { root } => self.node_stack.push(root as usize),
|
||||||
|
AppendChildren { many } => {
|
||||||
|
let target = if self.node_stack.len() >= many as usize + 1 {
|
||||||
|
*self
|
||||||
|
.node_stack
|
||||||
|
.get(self.node_stack.len() - (many as usize + 1))
|
||||||
|
.unwrap()
|
||||||
|
} else {
|
||||||
|
0
|
||||||
|
};
|
||||||
|
let drained: Vec<_> = self
|
||||||
|
.node_stack
|
||||||
|
.drain(self.node_stack.len() - many as usize..)
|
||||||
|
.collect();
|
||||||
|
for ns in drained {
|
||||||
|
self.link_child(ns, target).unwrap();
|
||||||
|
nodes_updated.push(ns);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ReplaceWith { root, m } => {
|
||||||
|
let root = self.remove(root as usize).unwrap();
|
||||||
|
let target = root.parent.unwrap().0;
|
||||||
|
let drained: Vec<_> = self.node_stack.drain(0..m as usize).collect();
|
||||||
|
for ns in drained {
|
||||||
|
nodes_updated.push(ns);
|
||||||
|
self.link_child(ns, target).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InsertAfter { root, n } => {
|
||||||
|
let target = self[root as usize].parent.unwrap().0;
|
||||||
|
let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect();
|
||||||
|
for ns in drained {
|
||||||
|
nodes_updated.push(ns);
|
||||||
|
self.link_child(ns, target).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
InsertBefore { root, n } => {
|
||||||
|
let target = self[root as usize].parent.unwrap().0;
|
||||||
|
let drained: Vec<_> = self.node_stack.drain(0..n as usize).collect();
|
||||||
|
for ns in drained {
|
||||||
|
nodes_updated.push(ns);
|
||||||
|
self.link_child(ns, target).unwrap();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Remove { root } => {
|
||||||
|
if let Some(parent) = self[root as usize].parent {
|
||||||
|
nodes_updated.push(parent.0);
|
||||||
|
}
|
||||||
|
self.remove(root as usize).unwrap();
|
||||||
|
}
|
||||||
|
CreateTextNode { root, text } => {
|
||||||
|
let n = TreeNode::new(
|
||||||
|
root,
|
||||||
|
TreeNodeType::Text {
|
||||||
|
text: text.to_string(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self.insert(n);
|
||||||
|
self.node_stack.push(root as usize)
|
||||||
|
}
|
||||||
|
CreateElement { root, tag } => {
|
||||||
|
let n = TreeNode::new(
|
||||||
|
root,
|
||||||
|
TreeNodeType::Element {
|
||||||
|
tag: tag.to_string(),
|
||||||
|
namespace: None,
|
||||||
|
children: Vec::new(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self.insert(n);
|
||||||
|
self.node_stack.push(root as usize)
|
||||||
|
}
|
||||||
|
CreateElementNs { root, tag, ns } => {
|
||||||
|
let n = TreeNode::new(
|
||||||
|
root,
|
||||||
|
TreeNodeType::Element {
|
||||||
|
tag: tag.to_string(),
|
||||||
|
namespace: Some(ns),
|
||||||
|
children: Vec::new(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
self.insert(n);
|
||||||
|
self.node_stack.push(root as usize)
|
||||||
|
}
|
||||||
|
CreatePlaceholder { root } => {
|
||||||
|
let n = TreeNode::new(root, TreeNodeType::Placeholder);
|
||||||
|
self.insert(n);
|
||||||
|
self.node_stack.push(root as usize)
|
||||||
|
}
|
||||||
|
|
||||||
|
NewEventListener {
|
||||||
|
event_name,
|
||||||
|
scope: _,
|
||||||
|
root,
|
||||||
|
} => {
|
||||||
|
if let Some(v) = self.nodes_listening.get_mut(event_name) {
|
||||||
|
v.insert(root as usize);
|
||||||
|
} else {
|
||||||
|
let mut hs = FxHashSet::default();
|
||||||
|
hs.insert(root as usize);
|
||||||
|
self.nodes_listening.insert(event_name, hs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
RemoveEventListener { root, event } => {
|
||||||
|
let v = self.nodes_listening.get_mut(event).unwrap();
|
||||||
|
v.remove(&(root as usize));
|
||||||
|
}
|
||||||
|
SetText {
|
||||||
|
root,
|
||||||
|
text: new_text,
|
||||||
|
} => {
|
||||||
|
let target = &mut self[root as usize];
|
||||||
|
nodes_updated.push(root as usize);
|
||||||
|
match &mut target.node_type {
|
||||||
|
TreeNodeType::Text { text } => {
|
||||||
|
*text = new_text.to_string();
|
||||||
|
}
|
||||||
|
_ => unreachable!(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
SetAttribute { root, .. } => {
|
||||||
|
nodes_updated.push(root as usize);
|
||||||
|
}
|
||||||
|
RemoveAttribute { root, .. } => {
|
||||||
|
nodes_updated.push(root as usize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
nodes_updated
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Seperated from apply_mutations because Mutations require a mutable reference to the VirtualDom.
|
||||||
|
pub fn update_state(
|
||||||
|
&mut self,
|
||||||
|
vdom: &VirtualDom,
|
||||||
|
nodes_updated: Vec<usize>,
|
||||||
|
us_ctx: &mut US::Ctx,
|
||||||
|
ds_ctx: &mut DS::Ctx,
|
||||||
|
) -> Option<FxHashSet<usize>> {
|
||||||
|
let mut to_rerender = FxHashSet::default();
|
||||||
|
let mut nodes_updated: Vec<_> = nodes_updated
|
||||||
|
.into_iter()
|
||||||
|
.map(|id| (id, self[id].height))
|
||||||
|
.collect();
|
||||||
|
// Sort nodes first by height, then if the height is the same id.
|
||||||
|
nodes_updated.sort_by(|fst, snd| fst.1.cmp(&snd.1).then(fst.0.cmp(&snd.0)));
|
||||||
|
nodes_updated.dedup();
|
||||||
|
|
||||||
|
// bubble up state. To avoid calling reduce more times than nessisary start from the bottom and go up.
|
||||||
|
let mut to_bubble: VecDeque<_> = nodes_updated.clone().into();
|
||||||
|
while let Some((id, height)) = to_bubble.pop_back() {
|
||||||
|
let node = &mut self[id as usize];
|
||||||
|
let vnode = node.element(vdom);
|
||||||
|
let node_type = &node.node_type;
|
||||||
|
let up_state = &mut node.up_state;
|
||||||
|
let children = match node_type {
|
||||||
|
TreeNodeType::Element { children, .. } => Some(children),
|
||||||
|
_ => None,
|
||||||
|
};
|
||||||
|
// todo: reduce cloning state
|
||||||
|
let old = up_state.clone();
|
||||||
|
let mut new = up_state.clone();
|
||||||
|
let parent = node.parent.clone();
|
||||||
|
new.reduce(
|
||||||
|
children
|
||||||
|
.unwrap_or(&Vec::new())
|
||||||
|
.clone()
|
||||||
|
.iter()
|
||||||
|
.map(|c| &self[*c].up_state),
|
||||||
|
vnode,
|
||||||
|
us_ctx,
|
||||||
|
);
|
||||||
|
if new != old {
|
||||||
|
to_rerender.insert(id);
|
||||||
|
if let Some(p) = parent {
|
||||||
|
let i = to_bubble.partition_point(|(other_id, h)| {
|
||||||
|
*h < height - 1 || (*h == height - 1 && *other_id < p.0)
|
||||||
|
});
|
||||||
|
// make sure the parent is not already queued
|
||||||
|
if i >= to_bubble.len() || to_bubble[i].0 != p.0 {
|
||||||
|
to_bubble.insert(i, (p.0, height - 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
let node = &mut self[id as usize];
|
||||||
|
node.up_state = new;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// push down state. To avoid calling reduce more times than nessisary start from the top and go down.
|
||||||
|
let mut to_push: VecDeque<_> = nodes_updated.clone().into();
|
||||||
|
while let Some((id, height)) = to_push.pop_front() {
|
||||||
|
let node = &self[id as usize];
|
||||||
|
// todo: reduce cloning state
|
||||||
|
let old = node.down_state.clone();
|
||||||
|
let mut new = node.down_state.clone();
|
||||||
|
let vnode = node.element(vdom);
|
||||||
|
new.reduce(
|
||||||
|
node.parent
|
||||||
|
.filter(|e| e.0 != 0)
|
||||||
|
.map(|e| &self[e].down_state),
|
||||||
|
vnode,
|
||||||
|
ds_ctx,
|
||||||
|
);
|
||||||
|
if new != old {
|
||||||
|
to_rerender.insert(id);
|
||||||
|
let node = &mut self[id as usize];
|
||||||
|
match &node.node_type {
|
||||||
|
TreeNodeType::Element { children, .. } => {
|
||||||
|
for c in children {
|
||||||
|
let i = to_push.partition_point(|(other_id, h)| {
|
||||||
|
*h < height + 1 || (*h == height + 1 && *other_id < c.0)
|
||||||
|
});
|
||||||
|
if i >= to_push.len() || to_push[i].0 != c.0 {
|
||||||
|
to_push.insert(i, (c.0, height + 1));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
};
|
||||||
|
node.down_state = new;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Some(to_rerender)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn link_child(&mut self, child_id: usize, parent_id: usize) -> Option<()> {
|
||||||
|
debug_assert_ne!(child_id, parent_id);
|
||||||
|
let parent = &mut self[parent_id];
|
||||||
|
parent.add_child(ElementId(child_id));
|
||||||
|
let parent_height = parent.height + 1;
|
||||||
|
self[child_id].set_parent(ElementId(parent_id));
|
||||||
|
self.increase_height(child_id, parent_height);
|
||||||
|
Some(())
|
||||||
|
}
|
||||||
|
|
||||||
|
fn increase_height(&mut self, id: usize, amount: u16) {
|
||||||
|
let n = &mut self[id];
|
||||||
|
n.height += amount;
|
||||||
|
match &n.node_type {
|
||||||
|
TreeNodeType::Element { children, .. } => {
|
||||||
|
for c in children.clone() {
|
||||||
|
self.increase_height(c.0, amount);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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>,
|
||||||
|
id: usize,
|
||||||
|
) -> Option<TreeNode<US, DS>> {
|
||||||
|
let mut node = tree.nodes[id as usize].take()?;
|
||||||
|
match &mut node.node_type {
|
||||||
|
TreeNodeType::Element { children, .. } => {
|
||||||
|
for c in children {
|
||||||
|
inner(tree, c.0)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
Some(node)
|
||||||
|
}
|
||||||
|
let mut node = self.nodes[id as usize].take()?;
|
||||||
|
if let Some(parent) = node.parent {
|
||||||
|
let parent = &mut self[parent];
|
||||||
|
parent.remove_child(ElementId(id));
|
||||||
|
}
|
||||||
|
match &mut node.node_type {
|
||||||
|
TreeNodeType::Element { children, .. } => {
|
||||||
|
for c in children {
|
||||||
|
inner(self, c.0)?;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
Some(node)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn insert(&mut self, node: TreeNode<US, DS>) {
|
||||||
|
let current_len = self.nodes.len();
|
||||||
|
let id = node.id.0;
|
||||||
|
if current_len - 1 < node.id.0 {
|
||||||
|
// self.nodes.reserve(1 + id - current_len);
|
||||||
|
self.nodes.extend((0..1 + id - current_len).map(|_| None));
|
||||||
|
}
|
||||||
|
self.nodes[id] = Some(node);
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get(&self, id: usize) -> Option<&TreeNode<US, DS>> {
|
||||||
|
self.nodes.get(id)?.as_ref()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_mut(&mut self, id: usize) -> Option<&mut TreeNode<US, DS>> {
|
||||||
|
self.nodes.get_mut(id)?.as_mut()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&TreeNode<US, DS>> {
|
||||||
|
if let Some(nodes) = self.nodes_listening.get(event) {
|
||||||
|
let mut listening: Vec<_> = nodes.iter().map(|id| &self[*id]).collect();
|
||||||
|
listening.sort_by(|n1, n2| (n1.height).cmp(&n2.height).reverse());
|
||||||
|
listening
|
||||||
|
} else {
|
||||||
|
Vec::new()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Check if the tree contains a node and its children.
|
||||||
|
pub fn contains_node(&self, node: &VNode) -> bool {
|
||||||
|
match node {
|
||||||
|
VNode::Component(_) => {
|
||||||
|
todo!()
|
||||||
|
}
|
||||||
|
VNode::Element(e) => {
|
||||||
|
if let Some(id) = e.id.get() {
|
||||||
|
let tree_node = &self[id];
|
||||||
|
match &tree_node.node_type {
|
||||||
|
TreeNodeType::Element {
|
||||||
|
tag,
|
||||||
|
namespace,
|
||||||
|
children,
|
||||||
|
} => {
|
||||||
|
tag == e.tag
|
||||||
|
&& namespace == &e.namespace
|
||||||
|
&& children.iter().copied().collect::<FxHashSet<_>>()
|
||||||
|
== e.children
|
||||||
|
.iter()
|
||||||
|
.map(|c| c.mounted_id())
|
||||||
|
.collect::<FxHashSet<_>>()
|
||||||
|
&& e.children.iter().all(|c| {
|
||||||
|
self.contains_node(c)
|
||||||
|
&& self[c.mounted_id()].parent == e.id.get()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
VNode::Fragment(f) => f.children.iter().all(|c| self.contains_node(c)),
|
||||||
|
VNode::Placeholder(_) => true,
|
||||||
|
VNode::Text(t) => {
|
||||||
|
if let Some(id) = t.id.get() {
|
||||||
|
let tree_node = &self[id];
|
||||||
|
match &tree_node.node_type {
|
||||||
|
TreeNodeType::Text { text } => t.text == text,
|
||||||
|
_ => false,
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Return the number of nodes in the tree.
|
||||||
|
pub fn size(&self) -> usize {
|
||||||
|
// The tree has a root node, ignore it.
|
||||||
|
self.nodes.iter().filter(|n| n.is_some()).count() - 1
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns the id of the root node.
|
||||||
|
pub fn root_id(&self) -> usize {
|
||||||
|
self.root
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Call a function for each node in the tree, depth first.
|
||||||
|
pub fn traverse(&self, mut f: impl FnMut(&TreeNode<US, DS>)) {
|
||||||
|
fn inner<US: BubbledUpState, DS: PushedDownState>(
|
||||||
|
tree: &ClientTree<US, DS>,
|
||||||
|
id: ElementId,
|
||||||
|
f: &mut impl FnMut(&TreeNode<US, DS>),
|
||||||
|
) {
|
||||||
|
let node = &tree[id];
|
||||||
|
f(node);
|
||||||
|
match &node.node_type {
|
||||||
|
TreeNodeType::Element { children, .. } => {
|
||||||
|
for c in children {
|
||||||
|
inner(tree, *c, f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
match &self[self.root].node_type {
|
||||||
|
TreeNodeType::Element { children, .. } => {
|
||||||
|
for c in children {
|
||||||
|
inner(self, *c, &mut f);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<US: BubbledUpState, DS: PushedDownState> Index<usize> for ClientTree<US, DS> {
|
||||||
|
type Output = TreeNode<US, DS>;
|
||||||
|
|
||||||
|
fn index(&self, idx: usize) -> &Self::Output {
|
||||||
|
self.get(idx).expect("Node does not exist")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<US: BubbledUpState, DS: PushedDownState> Index<ElementId> for ClientTree<US, DS> {
|
||||||
|
type Output = TreeNode<US, DS>;
|
||||||
|
|
||||||
|
fn index(&self, idx: ElementId) -> &Self::Output {
|
||||||
|
&self[idx.0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<US: BubbledUpState, DS: PushedDownState> IndexMut<usize> for ClientTree<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> {
|
||||||
|
fn index_mut(&mut self, idx: ElementId) -> &mut Self::Output {
|
||||||
|
&mut self[idx.0]
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// The node is stored client side and stores only basic data about the node. For more complete information about the node see [`TreeNode::element`].
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub struct TreeNode<US: BubbledUpState, DS: PushedDownState> {
|
||||||
|
/// The id of the node this node was created from.
|
||||||
|
pub id: ElementId,
|
||||||
|
/// The parent id of the node.
|
||||||
|
pub parent: Option<ElementId>,
|
||||||
|
/// State of the node that is bubbled up to its parents. The state must depend only on the node and its children.
|
||||||
|
pub up_state: US,
|
||||||
|
/// State of the node that is pushed down to the children. The state must depend only on the node itself and its parent.
|
||||||
|
pub down_state: DS,
|
||||||
|
/// Additional inforation specific to the node type
|
||||||
|
pub node_type: TreeNodeType,
|
||||||
|
/// The number of parents before the root node. The root node has height 1.
|
||||||
|
pub height: u16,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone)]
|
||||||
|
pub enum TreeNodeType {
|
||||||
|
Text {
|
||||||
|
text: String,
|
||||||
|
},
|
||||||
|
Element {
|
||||||
|
tag: String,
|
||||||
|
namespace: Option<&'static str>,
|
||||||
|
children: Vec<ElementId>,
|
||||||
|
},
|
||||||
|
Placeholder,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl<US: BubbledUpState, DS: PushedDownState> TreeNode<US, DS> {
|
||||||
|
fn new(id: u64, node_type: TreeNodeType) -> Self {
|
||||||
|
TreeNode {
|
||||||
|
id: ElementId(id as usize),
|
||||||
|
parent: None,
|
||||||
|
node_type,
|
||||||
|
down_state: DS::default(),
|
||||||
|
up_state: US::default(),
|
||||||
|
height: 0,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Returns a reference to the element that this node refrences.
|
||||||
|
pub fn element<'b>(&self, vdom: &'b VirtualDom) -> &'b VNode<'b> {
|
||||||
|
vdom.get_element(self.id).unwrap()
|
||||||
|
}
|
||||||
|
|
||||||
|
fn add_child(&mut self, child: ElementId) {
|
||||||
|
match &mut self.node_type {
|
||||||
|
TreeNodeType::Element { children, .. } => {
|
||||||
|
children.push(child);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn remove_child(&mut self, child: ElementId) {
|
||||||
|
match &mut self.node_type {
|
||||||
|
TreeNodeType::Element { children, .. } => {
|
||||||
|
children.retain(|c| c != &child);
|
||||||
|
}
|
||||||
|
_ => (),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn set_parent(&mut self, parent: ElementId) {
|
||||||
|
self.parent = Some(parent);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/// This state that is passed down to children. For example text properties (`<b>` `<i>` `<u>`) would be passed to children.
|
||||||
|
/// Called when the current node's node properties are modified or a parrent's [PushedDownState] is modified.
|
||||||
|
/// Called at most once per update.
|
||||||
|
pub trait PushedDownState: Default + PartialEq + Clone {
|
||||||
|
/// The context is passed to the [PushedDownState::reduce] when it is pushed down.
|
||||||
|
/// This is sometimes nessisary for lifetime purposes.
|
||||||
|
type Ctx;
|
||||||
|
fn reduce(&mut self, parent: Option<&Self>, vnode: &VNode, ctx: &mut Self::Ctx);
|
||||||
|
}
|
||||||
|
impl PushedDownState for () {
|
||||||
|
type Ctx = ();
|
||||||
|
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.
|
||||||
|
/// 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 {
|
||||||
|
/// The context is passed to the [BubbledUpState::reduce] when it is bubbled up.
|
||||||
|
/// This is sometimes nessisary for lifetime purposes.
|
||||||
|
type Ctx;
|
||||||
|
fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, ctx: &mut Self::Ctx)
|
||||||
|
where
|
||||||
|
I: Iterator<Item = &'a Self>,
|
||||||
|
Self: 'a;
|
||||||
|
}
|
||||||
|
impl BubbledUpState for () {
|
||||||
|
type Ctx = ();
|
||||||
|
fn reduce<'a, I>(&mut self, _children: I, _vnode: &VNode, _ctx: &mut Self::Ctx)
|
||||||
|
where
|
||||||
|
I: Iterator<Item = &'a Self>,
|
||||||
|
Self: 'a,
|
||||||
|
{
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,4 +1,4 @@
|
||||||
use crate::BubbledUpState;
|
use crate::client_tree::BubbledUpState;
|
||||||
use dioxus_core::*;
|
use dioxus_core::*;
|
||||||
use stretch2::prelude::*;
|
use stretch2::prelude::*;
|
||||||
|
|
||||||
|
@ -58,7 +58,7 @@ impl BubbledUpState for StretchLayout {
|
||||||
apply_layout_attributes(name, value, &mut style);
|
apply_layout_attributes(name, value, &mut style);
|
||||||
}
|
}
|
||||||
|
|
||||||
// todo: move
|
// the root node fills the entire area
|
||||||
if el.id.get() == Some(ElementId(0)) {
|
if el.id.get() == Some(ElementId(0)) {
|
||||||
apply_layout_attributes("width", "100%", &mut style);
|
apply_layout_attributes("width", "100%", &mut style);
|
||||||
apply_layout_attributes("height", "100%", &mut style);
|
apply_layout_attributes("height", "100%", &mut style);
|
||||||
|
@ -69,7 +69,6 @@ impl BubbledUpState for StretchLayout {
|
||||||
for l in children {
|
for l in children {
|
||||||
child_layout.push(l.node.unwrap());
|
child_layout.push(l.node.unwrap());
|
||||||
}
|
}
|
||||||
child_layout.reverse();
|
|
||||||
|
|
||||||
if let Some(n) = self.node {
|
if let Some(n) = self.node {
|
||||||
if &stretch.children(n).unwrap() != &child_layout {
|
if &stretch.children(n).unwrap() != &child_layout {
|
||||||
|
|
|
@ -1,527 +1,3 @@
|
||||||
use std::collections::{HashMap, HashSet, VecDeque};
|
pub mod client_tree;
|
||||||
|
|
||||||
use dioxus_core::{ElementId, Mutations, VNode, VirtualDom};
|
|
||||||
pub mod layout;
|
pub mod layout;
|
||||||
pub mod layout_attributes;
|
pub mod layout_attributes;
|
||||||
|
|
||||||
/// 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].
|
|
||||||
#[derive(Debug)]
|
|
||||||
|
|
||||||
pub struct Tree<US: BubbledUpState = (), DS: PushedDownState = ()> {
|
|
||||||
pub root: usize,
|
|
||||||
pub nodes: Vec<Option<TreeNode<US, DS>>>,
|
|
||||||
pub nodes_listening: HashMap<&'static str, HashSet<usize>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<US: BubbledUpState, DS: PushedDownState> Tree<US, DS> {
|
|
||||||
pub fn new() -> Tree<US, DS> {
|
|
||||||
Tree {
|
|
||||||
root: 0,
|
|
||||||
nodes: {
|
|
||||||
let mut v = Vec::new();
|
|
||||||
v.push(Some(TreeNode::new(
|
|
||||||
0,
|
|
||||||
TreeNodeType::Element {
|
|
||||||
tag: "Root".to_string(),
|
|
||||||
namespace: Some("Root"),
|
|
||||||
children: Vec::new(),
|
|
||||||
},
|
|
||||||
)));
|
|
||||||
v
|
|
||||||
},
|
|
||||||
nodes_listening: HashMap::new(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Updates the tree, up and down state and return a set of nodes that were updated
|
|
||||||
pub fn apply_mutations(&mut self, mutations_vec: Vec<Mutations>) -> Vec<usize> {
|
|
||||||
let mut nodes_updated = Vec::new();
|
|
||||||
for mutations in mutations_vec {
|
|
||||||
let mut node_stack: smallvec::SmallVec<[usize; 5]> = smallvec::SmallVec::new();
|
|
||||||
for e in mutations.edits {
|
|
||||||
use dioxus_core::DomEdit::*;
|
|
||||||
match e {
|
|
||||||
PushRoot { root } => node_stack.push(root as usize),
|
|
||||||
AppendChildren { many } => {
|
|
||||||
let target = if node_stack.len() >= many as usize + 1 {
|
|
||||||
*node_stack
|
|
||||||
.get(node_stack.len() - (many as usize + 1))
|
|
||||||
.unwrap()
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
};
|
|
||||||
for ns in node_stack.drain(node_stack.len() - many as usize..).rev() {
|
|
||||||
self.link_child(ns, target).unwrap();
|
|
||||||
nodes_updated.push(ns);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
ReplaceWith { root, m } => {
|
|
||||||
let root = self.remove(root as usize).unwrap();
|
|
||||||
let target = root.parent.unwrap().0;
|
|
||||||
for ns in node_stack.drain(0..m as usize) {
|
|
||||||
nodes_updated.push(ns);
|
|
||||||
self.link_child(ns, target).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
InsertAfter { root, n } => {
|
|
||||||
let target = self.get(root as usize).parent.unwrap().0;
|
|
||||||
for ns in node_stack.drain(0..n as usize) {
|
|
||||||
nodes_updated.push(ns);
|
|
||||||
self.link_child(ns, target).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
InsertBefore { root, n } => {
|
|
||||||
let target = self.get(root as usize).parent.unwrap().0;
|
|
||||||
for ns in node_stack.drain(0..n as usize) {
|
|
||||||
nodes_updated.push(ns);
|
|
||||||
self.link_child(ns, target).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Remove { root } => {
|
|
||||||
if let Some(parent) = self.get(root as usize).parent {
|
|
||||||
nodes_updated.push(parent.0);
|
|
||||||
}
|
|
||||||
self.remove(root as usize).unwrap();
|
|
||||||
}
|
|
||||||
CreateTextNode { root, text } => {
|
|
||||||
let n = TreeNode::new(
|
|
||||||
root,
|
|
||||||
TreeNodeType::Text {
|
|
||||||
text: text.to_string(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
self.insert(n);
|
|
||||||
node_stack.push(root as usize)
|
|
||||||
}
|
|
||||||
CreateElement { root, tag } => {
|
|
||||||
let n = TreeNode::new(
|
|
||||||
root,
|
|
||||||
TreeNodeType::Element {
|
|
||||||
tag: tag.to_string(),
|
|
||||||
namespace: None,
|
|
||||||
children: Vec::new(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
self.insert(n);
|
|
||||||
node_stack.push(root as usize)
|
|
||||||
}
|
|
||||||
CreateElementNs { root, tag, ns } => {
|
|
||||||
let n = TreeNode::new(
|
|
||||||
root,
|
|
||||||
TreeNodeType::Element {
|
|
||||||
tag: tag.to_string(),
|
|
||||||
namespace: Some(ns),
|
|
||||||
children: Vec::new(),
|
|
||||||
},
|
|
||||||
);
|
|
||||||
self.insert(n);
|
|
||||||
node_stack.push(root as usize)
|
|
||||||
}
|
|
||||||
CreatePlaceholder { root } => {
|
|
||||||
let n = TreeNode::new(root, TreeNodeType::Placeholder);
|
|
||||||
self.insert(n);
|
|
||||||
node_stack.push(root as usize)
|
|
||||||
}
|
|
||||||
|
|
||||||
NewEventListener {
|
|
||||||
event_name,
|
|
||||||
scope: _,
|
|
||||||
root,
|
|
||||||
} => {
|
|
||||||
if let Some(v) = self.nodes_listening.get_mut(event_name) {
|
|
||||||
v.insert(root as usize);
|
|
||||||
} else {
|
|
||||||
let mut hs = HashSet::new();
|
|
||||||
hs.insert(root as usize);
|
|
||||||
self.nodes_listening.insert(event_name, hs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
RemoveEventListener { root, event } => {
|
|
||||||
let v = self.nodes_listening.get_mut(event).unwrap();
|
|
||||||
v.remove(&(root as usize));
|
|
||||||
}
|
|
||||||
SetText {
|
|
||||||
root,
|
|
||||||
text: new_text,
|
|
||||||
} => {
|
|
||||||
let target = self.get_mut(root as usize);
|
|
||||||
nodes_updated.push(root as usize);
|
|
||||||
match &mut target.node_type {
|
|
||||||
TreeNodeType::Text { text } => {
|
|
||||||
*text = new_text.to_string();
|
|
||||||
}
|
|
||||||
_ => unreachable!(),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
SetAttribute { root, .. } => {
|
|
||||||
nodes_updated.push(root as usize);
|
|
||||||
}
|
|
||||||
RemoveAttribute { root, .. } => {
|
|
||||||
nodes_updated.push(root as usize);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
nodes_updated
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn update_state(
|
|
||||||
&mut self,
|
|
||||||
vdom: &VirtualDom,
|
|
||||||
nodes_updated: Vec<usize>,
|
|
||||||
us_ctx: &mut US::Ctx,
|
|
||||||
ds_ctx: &mut DS::Ctx,
|
|
||||||
) -> Option<HashSet<usize>> {
|
|
||||||
let mut to_rerender = HashSet::new();
|
|
||||||
let mut nodes_updated: Vec<_> = nodes_updated
|
|
||||||
.into_iter()
|
|
||||||
.map(|id| (id, self.get(id).height))
|
|
||||||
.collect();
|
|
||||||
nodes_updated.dedup();
|
|
||||||
nodes_updated.sort_by_key(|(_, h)| *h);
|
|
||||||
|
|
||||||
// bubble up state. To avoid calling reduce more times than nessisary start from the bottom and go up.
|
|
||||||
// todo: this is called multable times per element?
|
|
||||||
let mut to_bubble: VecDeque<_> = nodes_updated.clone().into();
|
|
||||||
while let Some((id, height)) = to_bubble.pop_back() {
|
|
||||||
let node = self.get_mut(id as usize);
|
|
||||||
let vnode = node.element(vdom);
|
|
||||||
let node_type = &node.node_type;
|
|
||||||
let up_state = &mut node.up_state;
|
|
||||||
let children = match node_type {
|
|
||||||
TreeNodeType::Element { children, .. } => Some(children),
|
|
||||||
_ => None,
|
|
||||||
};
|
|
||||||
// todo: reduce cloning state
|
|
||||||
let old = up_state.clone();
|
|
||||||
let mut new = up_state.clone();
|
|
||||||
let parent = node.parent.clone();
|
|
||||||
new.reduce(
|
|
||||||
children
|
|
||||||
.unwrap_or(&Vec::new())
|
|
||||||
.clone()
|
|
||||||
.iter()
|
|
||||||
.map(|c| &self.get(c.0).up_state),
|
|
||||||
vnode,
|
|
||||||
us_ctx,
|
|
||||||
);
|
|
||||||
if new != old {
|
|
||||||
to_rerender.insert(id);
|
|
||||||
if let Some(p) = parent {
|
|
||||||
let i = to_bubble.partition_point(|(_, h)| *h < height - 1);
|
|
||||||
// make sure the parent is not already queued
|
|
||||||
if i >= to_bubble.len() || to_bubble.get(i).unwrap().0 != p.0 {
|
|
||||||
to_bubble.insert(i, (p.0, height - 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
let node = self.get_mut(id as usize);
|
|
||||||
node.up_state = new;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// push down state. To avoid calling reduce more times than nessisary start from the top and go down.
|
|
||||||
let mut to_push: VecDeque<_> = nodes_updated.clone().into();
|
|
||||||
while let Some((id, height)) = to_push.pop_front() {
|
|
||||||
let node = self.get_mut(id as usize);
|
|
||||||
// todo: reduce cloning state
|
|
||||||
let old = node.down_state.clone();
|
|
||||||
let mut new = node.down_state.clone();
|
|
||||||
let vnode = node.element(vdom);
|
|
||||||
new.reduce(
|
|
||||||
node.parent.map(|e| &self.get(e.0).down_state),
|
|
||||||
vnode,
|
|
||||||
ds_ctx,
|
|
||||||
);
|
|
||||||
if new != old {
|
|
||||||
to_rerender.insert(id);
|
|
||||||
let node = self.get_mut(id as usize);
|
|
||||||
match &node.node_type {
|
|
||||||
TreeNodeType::Element { children, .. } => {
|
|
||||||
for c in children {
|
|
||||||
let i = to_bubble.partition_point(|(_, h)| *h < height + 1);
|
|
||||||
to_bubble.insert(i, (c.0, height + 1));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
};
|
|
||||||
node.down_state = new;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Some(to_rerender)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn link_child(&mut self, child_id: usize, parent_id: usize) -> Option<()> {
|
|
||||||
debug_assert_ne!(child_id, parent_id);
|
|
||||||
let parent = self.get_mut(parent_id);
|
|
||||||
parent.add_child(ElementId(child_id));
|
|
||||||
let parent_height = parent.height + 1;
|
|
||||||
self.get_mut(child_id).set_parent(ElementId(parent_id));
|
|
||||||
self.increase_height(child_id, parent_height);
|
|
||||||
Some(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn increase_height(&mut self, id: usize, amount: u16) {
|
|
||||||
let n = self.get_mut(id);
|
|
||||||
n.height += amount;
|
|
||||||
match &n.node_type {
|
|
||||||
TreeNodeType::Element { children, .. } => {
|
|
||||||
for c in children.clone() {
|
|
||||||
self.increase_height(c.0, amount);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn remove(&mut self, id: usize) -> Option<TreeNode<US, DS>> {
|
|
||||||
let mut node = self.nodes.get_mut(id as usize).unwrap().take().unwrap();
|
|
||||||
match &mut node.node_type {
|
|
||||||
TreeNodeType::Element { children, .. } => {
|
|
||||||
for c in children {
|
|
||||||
self.remove(c.0).unwrap();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
Some(node)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn insert(&mut self, node: TreeNode<US, DS>) {
|
|
||||||
let current_len = self.nodes.len();
|
|
||||||
let id = node.id.0;
|
|
||||||
if current_len - 1 < node.id.0 {
|
|
||||||
// self.nodes.reserve(1 + id - current_len);
|
|
||||||
self.nodes.extend((0..1 + id - current_len).map(|_| None));
|
|
||||||
}
|
|
||||||
self.nodes[id] = Some(node);
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get(&self, id: usize) -> &TreeNode<US, DS> {
|
|
||||||
self.nodes.get(id).unwrap().as_ref().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn get_mut(&mut self, id: usize) -> &mut TreeNode<US, DS> {
|
|
||||||
self.nodes.get_mut(id).unwrap().as_mut().unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&TreeNode<US, DS>> {
|
|
||||||
if let Some(nodes) = self.nodes_listening.get(event) {
|
|
||||||
let mut listening: Vec<_> = nodes.iter().map(|id| self.get(*id)).collect();
|
|
||||||
listening.sort_by(|n1, n2| (n1.height).cmp(&n2.height).reverse());
|
|
||||||
listening
|
|
||||||
} else {
|
|
||||||
Vec::new()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// The node is stored client side and stores render data
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub struct TreeNode<US: BubbledUpState, DS: PushedDownState> {
|
|
||||||
pub id: ElementId,
|
|
||||||
pub parent: Option<ElementId>,
|
|
||||||
pub up_state: US,
|
|
||||||
pub down_state: DS,
|
|
||||||
pub node_type: TreeNodeType,
|
|
||||||
pub height: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
|
||||||
pub enum TreeNodeType {
|
|
||||||
Text {
|
|
||||||
text: String,
|
|
||||||
},
|
|
||||||
Element {
|
|
||||||
tag: String,
|
|
||||||
namespace: Option<&'static str>,
|
|
||||||
children: Vec<ElementId>,
|
|
||||||
},
|
|
||||||
Placeholder,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl<US: BubbledUpState, DS: PushedDownState> TreeNode<US, DS> {
|
|
||||||
fn new(id: u64, node_type: TreeNodeType) -> Self {
|
|
||||||
TreeNode {
|
|
||||||
id: ElementId(id as usize),
|
|
||||||
parent: None,
|
|
||||||
node_type,
|
|
||||||
down_state: DS::default(),
|
|
||||||
up_state: US::default(),
|
|
||||||
height: 0,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn element<'b>(&self, vdom: &'b VirtualDom) -> &'b VNode<'b> {
|
|
||||||
vdom.get_element(self.id).unwrap()
|
|
||||||
}
|
|
||||||
|
|
||||||
fn add_child(&mut self, child: ElementId) {
|
|
||||||
match &mut self.node_type {
|
|
||||||
TreeNodeType::Element { children, .. } => {
|
|
||||||
children.push(child);
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn set_parent(&mut self, parent: ElementId) {
|
|
||||||
self.parent = Some(parent);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// This state that is passed down to children. For example text properties (`<b>` `<i>` `<u>`) would be passed to children.
|
|
||||||
/// Called when the current node's node properties are modified or a parrent's [PushedDownState] is modified.
|
|
||||||
/// Called at most once per update.
|
|
||||||
pub trait PushedDownState: Default + PartialEq + Clone {
|
|
||||||
type Ctx;
|
|
||||||
fn reduce(&mut self, parent: Option<&Self>, vnode: &VNode, ctx: &mut Self::Ctx);
|
|
||||||
}
|
|
||||||
impl PushedDownState for () {
|
|
||||||
type Ctx = ();
|
|
||||||
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.
|
|
||||||
/// 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 {
|
|
||||||
type Ctx;
|
|
||||||
fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, ctx: &mut Self::Ctx)
|
|
||||||
where
|
|
||||||
I: Iterator<Item = &'a Self>,
|
|
||||||
Self: 'a;
|
|
||||||
}
|
|
||||||
impl BubbledUpState for () {
|
|
||||||
type Ctx = ();
|
|
||||||
fn reduce<'a, I>(&mut self, _children: I, _vnode: &VNode, _ctx: &mut Self::Ctx)
|
|
||||||
where
|
|
||||||
I: Iterator<Item = &'a Self>,
|
|
||||||
Self: 'a,
|
|
||||||
{
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// /// The nodes that need to be updated after updating a state.
|
|
||||||
// pub struct Update {
|
|
||||||
// children: bool,
|
|
||||||
// parent: bool,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// /// This state is derived from children and parents.
|
|
||||||
// /// Called when the current node's node properties are modified or a parent or child's [State] is modified.
|
|
||||||
// /// Unlike [BubbledUpState] and [PushedDownState] this may be called mulable times per update. Prefer those over this.
|
|
||||||
// pub trait State: Default + PartialEq + Clone {
|
|
||||||
// fn reduce<'a, I>(&mut self, parent: Option<&Self>, children: I, vnode: &VNode) -> Update
|
|
||||||
// where
|
|
||||||
// I: Iterator<Item = &'a Self>,
|
|
||||||
// Self: 'a;
|
|
||||||
// }
|
|
||||||
// impl State for () {
|
|
||||||
// fn reduce<'a, I>(&mut self, _parent: Option<&Self>, _children: I, _vnode: &VNode) -> Update
|
|
||||||
// where
|
|
||||||
// I: Iterator<Item = &'a Self>,
|
|
||||||
// Self: 'a,
|
|
||||||
// {
|
|
||||||
// Update {
|
|
||||||
// children: false,
|
|
||||||
// parent: false,
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn test_insert() {
|
|
||||||
use dioxus_core::*;
|
|
||||||
use dioxus_core_macro::*;
|
|
||||||
use dioxus_html as dioxus_elements;
|
|
||||||
|
|
||||||
#[derive(Debug, Default, PartialEq, Clone)]
|
|
||||||
struct Rect {
|
|
||||||
x: u16,
|
|
||||||
y: u16,
|
|
||||||
width: u16,
|
|
||||||
height: u16,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl BubbledUpState for Rect {
|
|
||||||
type Ctx = ();
|
|
||||||
|
|
||||||
fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, _ctx: &mut Self::Ctx)
|
|
||||||
where
|
|
||||||
I: Iterator<Item = &'a Self>,
|
|
||||||
Self: 'a,
|
|
||||||
{
|
|
||||||
match vnode {
|
|
||||||
VNode::Text(t) => {
|
|
||||||
*self = Rect {
|
|
||||||
x: 0,
|
|
||||||
y: 0,
|
|
||||||
width: t.text.len().try_into().unwrap(),
|
|
||||||
height: 1,
|
|
||||||
};
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
_ => (),
|
|
||||||
}
|
|
||||||
self.width = 2;
|
|
||||||
self.height = 2;
|
|
||||||
for c in children {
|
|
||||||
println!("\t{c:?}");
|
|
||||||
self.width = self.width.max(c.width);
|
|
||||||
self.height += c.height;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[allow(non_snake_case)]
|
|
||||||
fn Base(cx: Scope) -> Element {
|
|
||||||
rsx!(cx, div {})
|
|
||||||
}
|
|
||||||
|
|
||||||
let vdom = VirtualDom::new(Base);
|
|
||||||
let node_1 = rsx! {
|
|
||||||
div{
|
|
||||||
div{
|
|
||||||
"hello"
|
|
||||||
"hello world"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let node_2 = rsx! {
|
|
||||||
div{
|
|
||||||
div{
|
|
||||||
"hello"
|
|
||||||
"hello world"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
let mutations = vdom.diff_lazynodes(node_1, node_2);
|
|
||||||
|
|
||||||
let mut tree: Tree<Rect, ()> = Tree {
|
|
||||||
root: 0,
|
|
||||||
nodes: {
|
|
||||||
let mut v = Vec::new();
|
|
||||||
v.push(Some(TreeNode::new(
|
|
||||||
0,
|
|
||||||
TreeNodeType::Element {
|
|
||||||
tag: "Root".to_string(),
|
|
||||||
namespace: Some("Root"),
|
|
||||||
children: Vec::new(),
|
|
||||||
},
|
|
||||||
)));
|
|
||||||
v
|
|
||||||
},
|
|
||||||
nodes_listening: HashMap::new(),
|
|
||||||
};
|
|
||||||
println!("{:?}", mutations);
|
|
||||||
let to_update = tree.apply_mutations(vec![mutations.0]);
|
|
||||||
let to_rerender = tree
|
|
||||||
.update_state(&vdom, to_update, &mut (), &mut ())
|
|
||||||
.unwrap();
|
|
||||||
println!("{to_rerender:?}");
|
|
||||||
panic!("{}", format!("{:?}", &tree.nodes[1..]).replace("\\", ""));
|
|
||||||
}
|
|
||||||
|
|
153
packages/native-core/tests/change_nodes.rs
Normal file
153
packages/native-core/tests/change_nodes.rs
Normal file
|
@ -0,0 +1,153 @@
|
||||||
|
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 std::cell::Cell;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tree_remove_node() {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn Base(cx: Scope) -> Element {
|
||||||
|
rsx!(cx, div {})
|
||||||
|
}
|
||||||
|
|
||||||
|
let vdom = VirtualDom::new(Base);
|
||||||
|
|
||||||
|
let mutations = vdom.create_vnodes(rsx! {
|
||||||
|
div{
|
||||||
|
div{}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut tree: ClientTree<(), ()> = ClientTree::new();
|
||||||
|
|
||||||
|
let _to_update = tree.apply_mutations(vec![mutations]);
|
||||||
|
let child_div = VElement {
|
||||||
|
id: Cell::new(Some(ElementId(2))),
|
||||||
|
key: None,
|
||||||
|
tag: "div",
|
||||||
|
namespace: None,
|
||||||
|
parent: Cell::new(Some(ElementId(1))),
|
||||||
|
listeners: &[],
|
||||||
|
attributes: &[],
|
||||||
|
children: &[],
|
||||||
|
};
|
||||||
|
let child_div_el = VNode::Element(&child_div);
|
||||||
|
let root_div = VElement {
|
||||||
|
id: Cell::new(Some(ElementId(1))),
|
||||||
|
key: None,
|
||||||
|
tag: "div",
|
||||||
|
namespace: None,
|
||||||
|
parent: Cell::new(Some(ElementId(0))),
|
||||||
|
listeners: &[],
|
||||||
|
attributes: &[],
|
||||||
|
children: &[child_div_el],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(tree.size(), 2);
|
||||||
|
assert!(&tree.contains_node(&VNode::Element(&root_div)));
|
||||||
|
assert_eq!(tree[1].height, 1);
|
||||||
|
assert_eq!(tree[2].height, 2);
|
||||||
|
|
||||||
|
let vdom = VirtualDom::new(Base);
|
||||||
|
let mutations = vdom.diff_lazynodes(
|
||||||
|
rsx! {
|
||||||
|
div{
|
||||||
|
div{}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
rsx! {
|
||||||
|
div{}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
tree.apply_mutations(vec![mutations.1]);
|
||||||
|
|
||||||
|
let new_root_div = VElement {
|
||||||
|
id: Cell::new(Some(ElementId(1))),
|
||||||
|
key: None,
|
||||||
|
tag: "div",
|
||||||
|
namespace: None,
|
||||||
|
parent: Cell::new(Some(ElementId(0))),
|
||||||
|
listeners: &[],
|
||||||
|
attributes: &[],
|
||||||
|
children: &[],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(tree.size(), 1);
|
||||||
|
assert!(&tree.contains_node(&VNode::Element(&new_root_div)));
|
||||||
|
assert_eq!(tree[1].height, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tree_add_node() {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn Base(cx: Scope) -> Element {
|
||||||
|
rsx!(cx, div {})
|
||||||
|
}
|
||||||
|
|
||||||
|
let vdom = VirtualDom::new(Base);
|
||||||
|
|
||||||
|
let mutations = vdom.create_vnodes(rsx! {
|
||||||
|
div{}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut tree: ClientTree<(), ()> = ClientTree::new();
|
||||||
|
|
||||||
|
let _to_update = tree.apply_mutations(vec![mutations]);
|
||||||
|
|
||||||
|
let root_div = VElement {
|
||||||
|
id: Cell::new(Some(ElementId(1))),
|
||||||
|
key: None,
|
||||||
|
tag: "div",
|
||||||
|
namespace: None,
|
||||||
|
parent: Cell::new(Some(ElementId(0))),
|
||||||
|
listeners: &[],
|
||||||
|
attributes: &[],
|
||||||
|
children: &[],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(tree.size(), 1);
|
||||||
|
assert!(&tree.contains_node(&VNode::Element(&root_div)));
|
||||||
|
assert_eq!(tree[1].height, 1);
|
||||||
|
|
||||||
|
let vdom = VirtualDom::new(Base);
|
||||||
|
let mutations = vdom.diff_lazynodes(
|
||||||
|
rsx! {
|
||||||
|
div{}
|
||||||
|
},
|
||||||
|
rsx! {
|
||||||
|
div{
|
||||||
|
p{}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
);
|
||||||
|
tree.apply_mutations(vec![mutations.1]);
|
||||||
|
|
||||||
|
let child_div = VElement {
|
||||||
|
id: Cell::new(Some(ElementId(2))),
|
||||||
|
key: None,
|
||||||
|
tag: "p",
|
||||||
|
namespace: None,
|
||||||
|
parent: Cell::new(Some(ElementId(1))),
|
||||||
|
listeners: &[],
|
||||||
|
attributes: &[],
|
||||||
|
children: &[],
|
||||||
|
};
|
||||||
|
let child_div_el = VNode::Element(&child_div);
|
||||||
|
let new_root_div = VElement {
|
||||||
|
id: Cell::new(Some(ElementId(1))),
|
||||||
|
key: None,
|
||||||
|
tag: "div",
|
||||||
|
namespace: None,
|
||||||
|
parent: Cell::new(Some(ElementId(0))),
|
||||||
|
listeners: &[],
|
||||||
|
attributes: &[],
|
||||||
|
children: &[child_div_el],
|
||||||
|
};
|
||||||
|
|
||||||
|
assert_eq!(tree.size(), 2);
|
||||||
|
assert!(&tree.contains_node(&VNode::Element(&new_root_div)));
|
||||||
|
assert_eq!(tree[1].height, 1);
|
||||||
|
assert_eq!(tree[2].height, 2);
|
||||||
|
}
|
124
packages/native-core/tests/initial_build.rs
Normal file
124
packages/native-core/tests/initial_build.rs
Normal file
|
@ -0,0 +1,124 @@
|
||||||
|
use std::cell::Cell;
|
||||||
|
|
||||||
|
use dioxus_core::VNode;
|
||||||
|
use dioxus_core::*;
|
||||||
|
use dioxus_core_macro::*;
|
||||||
|
use dioxus_html as dioxus_elements;
|
||||||
|
use dioxus_native_core::client_tree::ClientTree;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tree_initial_build_simple() {
|
||||||
|
use std::cell::Cell;
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn Base(cx: Scope) -> Element {
|
||||||
|
rsx!(cx, div {})
|
||||||
|
}
|
||||||
|
|
||||||
|
let vdom = VirtualDom::new(Base);
|
||||||
|
|
||||||
|
let mutations = vdom.create_vnodes(rsx! {
|
||||||
|
div{}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut tree: ClientTree<(), ()> = ClientTree::new();
|
||||||
|
|
||||||
|
let _to_update = tree.apply_mutations(vec![mutations]);
|
||||||
|
let root_div = VElement {
|
||||||
|
id: Cell::new(Some(ElementId(1))),
|
||||||
|
key: None,
|
||||||
|
tag: "div",
|
||||||
|
namespace: None,
|
||||||
|
parent: Cell::new(Some(ElementId(0))),
|
||||||
|
listeners: &[],
|
||||||
|
attributes: &[],
|
||||||
|
children: &[],
|
||||||
|
};
|
||||||
|
assert_eq!(tree.size(), 1);
|
||||||
|
assert!(&tree.contains_node(&VNode::Element(&root_div)));
|
||||||
|
assert_eq!(tree[1].height, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tree_initial_build_with_children() {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn Base(cx: Scope) -> Element {
|
||||||
|
rsx!(cx, div {})
|
||||||
|
}
|
||||||
|
|
||||||
|
let vdom = VirtualDom::new(Base);
|
||||||
|
|
||||||
|
let mutations = vdom.create_vnodes(rsx! {
|
||||||
|
div{
|
||||||
|
div{
|
||||||
|
"hello"
|
||||||
|
p{
|
||||||
|
"world"
|
||||||
|
}
|
||||||
|
"hello world"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut tree: ClientTree<(), ()> = ClientTree::new();
|
||||||
|
|
||||||
|
let _to_update = tree.apply_mutations(vec![mutations]);
|
||||||
|
let first_text = VText {
|
||||||
|
id: Cell::new(Some(ElementId(3))),
|
||||||
|
text: "hello",
|
||||||
|
is_static: true,
|
||||||
|
};
|
||||||
|
let first_text_node = VNode::Text(&first_text);
|
||||||
|
let child_text = VText {
|
||||||
|
id: Cell::new(Some(ElementId(5))),
|
||||||
|
text: "world",
|
||||||
|
is_static: true,
|
||||||
|
};
|
||||||
|
let child_text_node = VNode::Text(&child_text);
|
||||||
|
let child_p_el = VElement {
|
||||||
|
id: Cell::new(Some(ElementId(4))),
|
||||||
|
key: None,
|
||||||
|
tag: "p",
|
||||||
|
namespace: None,
|
||||||
|
parent: Cell::new(Some(ElementId(2))),
|
||||||
|
listeners: &[],
|
||||||
|
attributes: &[],
|
||||||
|
children: &[child_text_node],
|
||||||
|
};
|
||||||
|
let child_p_node = VNode::Element(&child_p_el);
|
||||||
|
let second_text = VText {
|
||||||
|
id: Cell::new(Some(ElementId(6))),
|
||||||
|
text: "hello world",
|
||||||
|
is_static: true,
|
||||||
|
};
|
||||||
|
let second_text_node = VNode::Text(&second_text);
|
||||||
|
let child_div_el = VElement {
|
||||||
|
id: Cell::new(Some(ElementId(2))),
|
||||||
|
key: None,
|
||||||
|
tag: "div",
|
||||||
|
namespace: None,
|
||||||
|
parent: Cell::new(Some(ElementId(1))),
|
||||||
|
listeners: &[],
|
||||||
|
attributes: &[],
|
||||||
|
children: &[first_text_node, child_p_node, second_text_node],
|
||||||
|
};
|
||||||
|
let child_div_node = VNode::Element(&child_div_el);
|
||||||
|
let root_div = VElement {
|
||||||
|
id: Cell::new(Some(ElementId(1))),
|
||||||
|
key: None,
|
||||||
|
tag: "div",
|
||||||
|
namespace: None,
|
||||||
|
parent: Cell::new(Some(ElementId(0))),
|
||||||
|
listeners: &[],
|
||||||
|
attributes: &[],
|
||||||
|
children: &[child_div_node],
|
||||||
|
};
|
||||||
|
assert_eq!(tree.size(), 6);
|
||||||
|
assert!(&tree.contains_node(&VNode::Element(&root_div)));
|
||||||
|
assert_eq!(tree[1].height, 1);
|
||||||
|
assert_eq!(tree[2].height, 2);
|
||||||
|
assert_eq!(tree[3].height, 3);
|
||||||
|
assert_eq!(tree[4].height, 3);
|
||||||
|
assert_eq!(tree[5].height, 4);
|
||||||
|
assert_eq!(tree[6].height, 3);
|
||||||
|
}
|
320
packages/native-core/tests/state.rs
Normal file
320
packages/native-core/tests/state.rs
Normal file
|
@ -0,0 +1,320 @@
|
||||||
|
use dioxus_core::VNode;
|
||||||
|
use dioxus_core::*;
|
||||||
|
use dioxus_core_macro::*;
|
||||||
|
use dioxus_html as dioxus_elements;
|
||||||
|
use dioxus_native_core::client_tree::*;
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Default)]
|
||||||
|
struct CallCounter(u32);
|
||||||
|
impl BubbledUpState for CallCounter {
|
||||||
|
type Ctx = ();
|
||||||
|
|
||||||
|
fn reduce<'a, I>(&mut self, _children: I, _vnode: &VNode, _ctx: &mut Self::Ctx)
|
||||||
|
where
|
||||||
|
I: Iterator<Item = &'a Self>,
|
||||||
|
Self: 'a,
|
||||||
|
{
|
||||||
|
self.0 += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PushedDownState for CallCounter {
|
||||||
|
type Ctx = ();
|
||||||
|
|
||||||
|
fn reduce(&mut self, _parent: Option<&Self>, _vnode: &VNode, _ctx: &mut Self::Ctx) {
|
||||||
|
self.0 += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Default)]
|
||||||
|
struct BubbledUpStateTester(String, Vec<Box<BubbledUpStateTester>>);
|
||||||
|
impl BubbledUpState for BubbledUpStateTester {
|
||||||
|
type Ctx = u32;
|
||||||
|
|
||||||
|
fn reduce<'a, I>(&mut self, children: I, vnode: &VNode, ctx: &mut Self::Ctx)
|
||||||
|
where
|
||||||
|
I: Iterator<Item = &'a Self>,
|
||||||
|
Self: 'a,
|
||||||
|
{
|
||||||
|
assert_eq!(*ctx, 42);
|
||||||
|
*self = BubbledUpStateTester(
|
||||||
|
vnode.mounted_id().to_string(),
|
||||||
|
children.map(|c| Box::new(c.clone())).collect(),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Debug, Clone, PartialEq, Default)]
|
||||||
|
struct PushedDownStateTester(String, Option<Box<PushedDownStateTester>>);
|
||||||
|
impl PushedDownState for PushedDownStateTester {
|
||||||
|
type Ctx = u32;
|
||||||
|
|
||||||
|
fn reduce(&mut self, parent: Option<&Self>, vnode: &VNode, ctx: &mut Self::Ctx) {
|
||||||
|
assert_eq!(*ctx, 42);
|
||||||
|
*self = PushedDownStateTester(
|
||||||
|
vnode.mounted_id().to_string(),
|
||||||
|
parent.map(|c| Box::new(c.clone())),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tree_state_initial() {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn Base(cx: Scope) -> Element {
|
||||||
|
rsx!(cx, div {
|
||||||
|
p{}
|
||||||
|
h1{}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let vdom = VirtualDom::new(Base);
|
||||||
|
|
||||||
|
let mutations = vdom.create_vnodes(rsx! {
|
||||||
|
div {
|
||||||
|
p{}
|
||||||
|
h1{}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut tree: ClientTree<BubbledUpStateTester, PushedDownStateTester> = ClientTree::new();
|
||||||
|
|
||||||
|
let nodes_updated = tree.apply_mutations(vec![mutations]);
|
||||||
|
let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut 42, &mut 42);
|
||||||
|
|
||||||
|
let root_div = &tree[1];
|
||||||
|
assert_eq!(root_div.up_state.0, "1");
|
||||||
|
assert_eq!(
|
||||||
|
root_div.up_state.1,
|
||||||
|
vec![
|
||||||
|
Box::new(BubbledUpStateTester("2".to_string(), Vec::new())),
|
||||||
|
Box::new(BubbledUpStateTester("3".to_string(), Vec::new()))
|
||||||
|
]
|
||||||
|
);
|
||||||
|
assert_eq!(root_div.down_state.0, "1");
|
||||||
|
assert_eq!(root_div.down_state.1, None);
|
||||||
|
|
||||||
|
let child_p = &tree[2];
|
||||||
|
assert_eq!(child_p.up_state.0, "2");
|
||||||
|
assert_eq!(child_p.up_state.1, Vec::new());
|
||||||
|
assert_eq!(child_p.down_state.0, "2");
|
||||||
|
assert_eq!(
|
||||||
|
child_p.down_state.1,
|
||||||
|
Some(Box::new(PushedDownStateTester("1".to_string(), None)))
|
||||||
|
);
|
||||||
|
|
||||||
|
let child_h1 = &tree[3];
|
||||||
|
assert_eq!(child_h1.up_state.0, "3");
|
||||||
|
assert_eq!(child_h1.up_state.1, Vec::new());
|
||||||
|
assert_eq!(child_h1.down_state.0, "3");
|
||||||
|
assert_eq!(
|
||||||
|
child_h1.down_state.1,
|
||||||
|
Some(Box::new(PushedDownStateTester("1".to_string(), None)))
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tree_state_reduce_initally_called_minimally() {
|
||||||
|
#[derive(Debug, Clone, PartialEq, Default)]
|
||||||
|
struct CallCounter(u32);
|
||||||
|
impl BubbledUpState for CallCounter {
|
||||||
|
type Ctx = ();
|
||||||
|
|
||||||
|
fn reduce<'a, I>(&mut self, _children: I, _vnode: &VNode, _ctx: &mut Self::Ctx)
|
||||||
|
where
|
||||||
|
I: Iterator<Item = &'a Self>,
|
||||||
|
Self: 'a,
|
||||||
|
{
|
||||||
|
self.0 += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PushedDownState for CallCounter {
|
||||||
|
type Ctx = ();
|
||||||
|
|
||||||
|
fn reduce(&mut self, _parent: Option<&Self>, _vnode: &VNode, _ctx: &mut Self::Ctx) {
|
||||||
|
self.0 += 1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn Base(cx: Scope) -> Element {
|
||||||
|
rsx!(cx, div {
|
||||||
|
div{
|
||||||
|
div{
|
||||||
|
p{}
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
"hello"
|
||||||
|
}
|
||||||
|
div{
|
||||||
|
h1{}
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
"world"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let vdom = VirtualDom::new(Base);
|
||||||
|
|
||||||
|
let mutations = vdom.create_vnodes(rsx! {
|
||||||
|
div {
|
||||||
|
div{
|
||||||
|
div{
|
||||||
|
p{}
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
"hello"
|
||||||
|
}
|
||||||
|
div{
|
||||||
|
h1{}
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
"world"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut tree: ClientTree<CallCounter, CallCounter> = ClientTree::new();
|
||||||
|
|
||||||
|
let nodes_updated = tree.apply_mutations(vec![mutations]);
|
||||||
|
let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ());
|
||||||
|
|
||||||
|
tree.traverse(|n| {
|
||||||
|
assert_eq!(n.up_state.0, 1);
|
||||||
|
assert_eq!(n.down_state.0, 1);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tree_state_reduce_down_called_minimally_on_update() {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn Base(cx: Scope) -> Element {
|
||||||
|
rsx!(cx, div {
|
||||||
|
div{
|
||||||
|
div{
|
||||||
|
p{}
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
"hello"
|
||||||
|
}
|
||||||
|
div{
|
||||||
|
h1{}
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
"world"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let vdom = VirtualDom::new(Base);
|
||||||
|
|
||||||
|
let mutations = vdom.create_vnodes(rsx! {
|
||||||
|
div {
|
||||||
|
width: "100%",
|
||||||
|
div{
|
||||||
|
div{
|
||||||
|
p{}
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
"hello"
|
||||||
|
}
|
||||||
|
div{
|
||||||
|
h1{}
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
"world"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut tree: ClientTree<CallCounter, CallCounter> = ClientTree::new();
|
||||||
|
|
||||||
|
let nodes_updated = tree.apply_mutations(vec![mutations]);
|
||||||
|
let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ());
|
||||||
|
let nodes_updated = tree.apply_mutations(vec![Mutations {
|
||||||
|
edits: vec![DomEdit::SetAttribute {
|
||||||
|
root: 1,
|
||||||
|
field: "width",
|
||||||
|
value: "99%",
|
||||||
|
ns: Some("style"),
|
||||||
|
}],
|
||||||
|
dirty_scopes: fxhash::FxHashSet::default(),
|
||||||
|
refs: Vec::new(),
|
||||||
|
}]);
|
||||||
|
let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ());
|
||||||
|
|
||||||
|
tree.traverse(|n| {
|
||||||
|
assert_eq!(n.down_state.0, 2);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tree_state_reduce_up_called_minimally_on_update() {
|
||||||
|
#[allow(non_snake_case)]
|
||||||
|
fn Base(cx: Scope) -> Element {
|
||||||
|
rsx!(cx, div {
|
||||||
|
div{
|
||||||
|
div{
|
||||||
|
p{}
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
"hello"
|
||||||
|
}
|
||||||
|
div{
|
||||||
|
h1{}
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
"world"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
let vdom = VirtualDom::new(Base);
|
||||||
|
|
||||||
|
let mutations = vdom.create_vnodes(rsx! {
|
||||||
|
div {
|
||||||
|
width: "100%",
|
||||||
|
div{
|
||||||
|
div{
|
||||||
|
p{}
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
"hello"
|
||||||
|
}
|
||||||
|
div{
|
||||||
|
h1{}
|
||||||
|
}
|
||||||
|
p{
|
||||||
|
"world"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
let mut tree: ClientTree<CallCounter, CallCounter> = ClientTree::new();
|
||||||
|
|
||||||
|
let nodes_updated = tree.apply_mutations(vec![mutations]);
|
||||||
|
let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ());
|
||||||
|
let nodes_updated = tree.apply_mutations(vec![Mutations {
|
||||||
|
edits: vec![DomEdit::SetAttribute {
|
||||||
|
root: 4,
|
||||||
|
field: "width",
|
||||||
|
value: "99%",
|
||||||
|
ns: Some("style"),
|
||||||
|
}],
|
||||||
|
dirty_scopes: fxhash::FxHashSet::default(),
|
||||||
|
refs: Vec::new(),
|
||||||
|
}]);
|
||||||
|
let _to_rerender = tree.update_state(&vdom, nodes_updated, &mut (), &mut ());
|
||||||
|
|
||||||
|
tree.traverse(|n| {
|
||||||
|
assert_eq!(n.up_state.0, if n.id.0 > 4 { 1 } else { 2 });
|
||||||
|
});
|
||||||
|
}
|
|
@ -22,5 +22,7 @@ crossterm = "0.23.0"
|
||||||
anyhow = "1.0.42"
|
anyhow = "1.0.42"
|
||||||
tokio = { version = "1.15.0", features = ["full"] }
|
tokio = { version = "1.15.0", features = ["full"] }
|
||||||
futures = "0.3.19"
|
futures = "0.3.19"
|
||||||
stretch2 = "0.4.1"
|
# stretch2 = "0.4.1"
|
||||||
|
stretch2 = { git = "https://github.com/Demonthos/stretch.git" }
|
||||||
smallvec = "1.6"
|
smallvec = "1.6"
|
||||||
|
fxhash = "0.2"
|
||||||
|
|
|
@ -2,14 +2,17 @@ use crossterm::event::{
|
||||||
Event as TermEvent, KeyCode as TermKeyCode, KeyModifiers, MouseButton, MouseEventKind,
|
Event as TermEvent, KeyCode as TermKeyCode, KeyModifiers, MouseButton, MouseEventKind,
|
||||||
};
|
};
|
||||||
use dioxus_core::*;
|
use dioxus_core::*;
|
||||||
|
use fxhash::{FxHashMap, FxHashSet};
|
||||||
|
|
||||||
use dioxus_html::{on::*, KeyCode};
|
use dioxus_html::{on::*, KeyCode};
|
||||||
use dioxus_native_core::{layout::StretchLayout, Tree, TreeNode};
|
use dioxus_native_core::{
|
||||||
|
client_tree::{ClientTree, TreeNode},
|
||||||
|
layout::StretchLayout,
|
||||||
|
};
|
||||||
use futures::{channel::mpsc::UnboundedReceiver, StreamExt};
|
use futures::{channel::mpsc::UnboundedReceiver, StreamExt};
|
||||||
use std::{
|
use std::{
|
||||||
any::Any,
|
any::Any,
|
||||||
cell::RefCell,
|
cell::RefCell,
|
||||||
collections::{HashMap, HashSet},
|
|
||||||
rc::Rc,
|
rc::Rc,
|
||||||
sync::Arc,
|
sync::Arc,
|
||||||
time::{Duration, Instant},
|
time::{Duration, Instant},
|
||||||
|
@ -166,7 +169,7 @@ impl InnerInputState {
|
||||||
evts: &mut Vec<EventCore>,
|
evts: &mut Vec<EventCore>,
|
||||||
resolved_events: &mut Vec<UserEvent>,
|
resolved_events: &mut Vec<UserEvent>,
|
||||||
layout: &Stretch,
|
layout: &Stretch,
|
||||||
tree: &mut Tree<StretchLayout, StyleModifier>,
|
tree: &mut ClientTree<StretchLayout, StyleModifier>,
|
||||||
) {
|
) {
|
||||||
let previous_mouse = self
|
let previous_mouse = self
|
||||||
.mouse
|
.mouse
|
||||||
|
@ -191,7 +194,7 @@ impl InnerInputState {
|
||||||
previous_mouse: Option<(MouseData, Vec<u16>)>,
|
previous_mouse: Option<(MouseData, Vec<u16>)>,
|
||||||
resolved_events: &mut Vec<UserEvent>,
|
resolved_events: &mut Vec<UserEvent>,
|
||||||
layout: &Stretch,
|
layout: &Stretch,
|
||||||
tree: &mut Tree<StretchLayout, StyleModifier>,
|
tree: &mut ClientTree<StretchLayout, StyleModifier>,
|
||||||
) {
|
) {
|
||||||
struct Data<'b> {
|
struct Data<'b> {
|
||||||
new_pos: (i32, i32),
|
new_pos: (i32, i32),
|
||||||
|
@ -213,17 +216,17 @@ impl InnerInputState {
|
||||||
fn try_create_event(
|
fn try_create_event(
|
||||||
name: &'static str,
|
name: &'static str,
|
||||||
data: Arc<dyn Any + Send + Sync>,
|
data: Arc<dyn Any + Send + Sync>,
|
||||||
will_bubble: &mut HashSet<ElementId>,
|
will_bubble: &mut FxHashSet<ElementId>,
|
||||||
resolved_events: &mut Vec<UserEvent>,
|
resolved_events: &mut Vec<UserEvent>,
|
||||||
node: &TreeNode<StretchLayout, StyleModifier>,
|
node: &TreeNode<StretchLayout, StyleModifier>,
|
||||||
tree: &Tree<StretchLayout, StyleModifier>,
|
tree: &ClientTree<StretchLayout, StyleModifier>,
|
||||||
) {
|
) {
|
||||||
// only trigger event if the event was not triggered already by a child
|
// only trigger event if the event was not triggered already by a child
|
||||||
if will_bubble.insert(node.id) {
|
if will_bubble.insert(node.id) {
|
||||||
let mut parent = node.parent;
|
let mut parent = node.parent;
|
||||||
while let Some(parent_id) = parent {
|
while let Some(parent_id) = parent {
|
||||||
will_bubble.insert(parent_id);
|
will_bubble.insert(parent_id);
|
||||||
parent = tree.get(parent_id.0).parent;
|
parent = tree[parent_id.0].parent;
|
||||||
}
|
}
|
||||||
resolved_events.push(UserEvent {
|
resolved_events.push(UserEvent {
|
||||||
scope_id: None,
|
scope_id: None,
|
||||||
|
@ -259,7 +262,7 @@ impl InnerInputState {
|
||||||
|
|
||||||
{
|
{
|
||||||
// mousemove
|
// mousemove
|
||||||
let mut will_bubble = HashSet::new();
|
let mut will_bubble = FxHashSet::default();
|
||||||
for node in tree.get_listening_sorted("mousemove") {
|
for node in tree.get_listening_sorted("mousemove") {
|
||||||
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
||||||
let previously_contained = data
|
let previously_contained = data
|
||||||
|
@ -285,7 +288,7 @@ impl InnerInputState {
|
||||||
|
|
||||||
{
|
{
|
||||||
// mouseenter
|
// mouseenter
|
||||||
let mut will_bubble = HashSet::new();
|
let mut will_bubble = FxHashSet::default();
|
||||||
for node in tree.get_listening_sorted("mouseenter") {
|
for node in tree.get_listening_sorted("mouseenter") {
|
||||||
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
||||||
let previously_contained = data
|
let previously_contained = data
|
||||||
|
@ -311,7 +314,7 @@ impl InnerInputState {
|
||||||
|
|
||||||
{
|
{
|
||||||
// mouseover
|
// mouseover
|
||||||
let mut will_bubble = HashSet::new();
|
let mut will_bubble = FxHashSet::default();
|
||||||
for node in tree.get_listening_sorted("mouseover") {
|
for node in tree.get_listening_sorted("mouseover") {
|
||||||
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
||||||
let previously_contained = data
|
let previously_contained = data
|
||||||
|
@ -337,7 +340,7 @@ impl InnerInputState {
|
||||||
|
|
||||||
{
|
{
|
||||||
// mousedown
|
// mousedown
|
||||||
let mut will_bubble = HashSet::new();
|
let mut will_bubble = FxHashSet::default();
|
||||||
for node in tree.get_listening_sorted("mousedown") {
|
for node in tree.get_listening_sorted("mousedown") {
|
||||||
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
||||||
let currently_contains = layout_contains_point(node_layout, data.new_pos);
|
let currently_contains = layout_contains_point(node_layout, data.new_pos);
|
||||||
|
@ -359,7 +362,7 @@ impl InnerInputState {
|
||||||
|
|
||||||
{
|
{
|
||||||
// mouseup
|
// mouseup
|
||||||
let mut will_bubble = HashSet::new();
|
let mut will_bubble = FxHashSet::default();
|
||||||
for node in tree.get_listening_sorted("mouseup") {
|
for node in tree.get_listening_sorted("mouseup") {
|
||||||
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
||||||
let currently_contains = layout_contains_point(node_layout, data.new_pos);
|
let currently_contains = layout_contains_point(node_layout, data.new_pos);
|
||||||
|
@ -381,7 +384,7 @@ impl InnerInputState {
|
||||||
|
|
||||||
{
|
{
|
||||||
// click
|
// click
|
||||||
let mut will_bubble = HashSet::new();
|
let mut will_bubble = FxHashSet::default();
|
||||||
for node in tree.get_listening_sorted("click") {
|
for node in tree.get_listening_sorted("click") {
|
||||||
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
||||||
let currently_contains = layout_contains_point(node_layout, data.new_pos);
|
let currently_contains = layout_contains_point(node_layout, data.new_pos);
|
||||||
|
@ -403,7 +406,7 @@ impl InnerInputState {
|
||||||
|
|
||||||
{
|
{
|
||||||
// contextmenu
|
// contextmenu
|
||||||
let mut will_bubble = HashSet::new();
|
let mut will_bubble = FxHashSet::default();
|
||||||
for node in tree.get_listening_sorted("contextmenu") {
|
for node in tree.get_listening_sorted("contextmenu") {
|
||||||
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
||||||
let currently_contains = layout_contains_point(node_layout, data.new_pos);
|
let currently_contains = layout_contains_point(node_layout, data.new_pos);
|
||||||
|
@ -425,7 +428,7 @@ impl InnerInputState {
|
||||||
|
|
||||||
{
|
{
|
||||||
// wheel
|
// wheel
|
||||||
let mut will_bubble = HashSet::new();
|
let mut will_bubble = FxHashSet::default();
|
||||||
for node in tree.get_listening_sorted("wheel") {
|
for node in tree.get_listening_sorted("wheel") {
|
||||||
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
||||||
let currently_contains = layout_contains_point(node_layout, data.new_pos);
|
let currently_contains = layout_contains_point(node_layout, data.new_pos);
|
||||||
|
@ -449,7 +452,7 @@ impl InnerInputState {
|
||||||
|
|
||||||
{
|
{
|
||||||
// mouseleave
|
// mouseleave
|
||||||
let mut will_bubble = HashSet::new();
|
let mut will_bubble = FxHashSet::default();
|
||||||
for node in tree.get_listening_sorted("mouseleave") {
|
for node in tree.get_listening_sorted("mouseleave") {
|
||||||
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
||||||
let previously_contained = data
|
let previously_contained = data
|
||||||
|
@ -473,7 +476,7 @@ impl InnerInputState {
|
||||||
|
|
||||||
{
|
{
|
||||||
// mouseout
|
// mouseout
|
||||||
let mut will_bubble = HashSet::new();
|
let mut will_bubble = FxHashSet::default();
|
||||||
for node in tree.get_listening_sorted("mouseout") {
|
for node in tree.get_listening_sorted("mouseout") {
|
||||||
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
let node_layout = layout.layout(node.up_state.node.unwrap()).unwrap();
|
||||||
let previously_contained = data
|
let previously_contained = data
|
||||||
|
@ -543,7 +546,7 @@ impl RinkInputHandler {
|
||||||
pub fn get_events<'a>(
|
pub fn get_events<'a>(
|
||||||
&self,
|
&self,
|
||||||
layout: &Stretch,
|
layout: &Stretch,
|
||||||
tree: &mut Tree<StretchLayout, StyleModifier>,
|
tree: &mut ClientTree<StretchLayout, StyleModifier>,
|
||||||
) -> Vec<UserEvent> {
|
) -> Vec<UserEvent> {
|
||||||
let mut resolved_events = Vec::new();
|
let mut resolved_events = Vec::new();
|
||||||
|
|
||||||
|
@ -578,7 +581,7 @@ impl RinkInputHandler {
|
||||||
.map(|evt| (evt.0, evt.1.into_any()));
|
.map(|evt| (evt.0, evt.1.into_any()));
|
||||||
|
|
||||||
// todo: currently resolves events in all nodes, but once the focus system is added it should filter by focus
|
// todo: currently resolves events in all nodes, but once the focus system is added it should filter by focus
|
||||||
let mut hm: HashMap<&'static str, Vec<Arc<dyn Any + Send + Sync>>> = HashMap::new();
|
let mut hm: FxHashMap<&'static str, Vec<Arc<dyn Any + Send + Sync>>> = FxHashMap::default();
|
||||||
for (event, data) in events {
|
for (event, data) in events {
|
||||||
if let Some(v) = hm.get_mut(event) {
|
if let Some(v) = hm.get_mut(event) {
|
||||||
v.push(data);
|
v.push(data);
|
||||||
|
|
|
@ -9,10 +9,8 @@ use crossterm::{
|
||||||
};
|
};
|
||||||
use dioxus_core::exports::futures_channel::mpsc::unbounded;
|
use dioxus_core::exports::futures_channel::mpsc::unbounded;
|
||||||
use dioxus_core::*;
|
use dioxus_core::*;
|
||||||
use dioxus_html::on::{KeyboardData, MouseData, PointerData, TouchData, WheelData};
|
use dioxus_native_core::{client_tree::ClientTree, layout::StretchLayout};
|
||||||
use dioxus_native_core::{layout::StretchLayout, Tree};
|
|
||||||
use futures::{channel::mpsc::UnboundedSender, pin_mut, StreamExt};
|
use futures::{channel::mpsc::UnboundedSender, pin_mut, StreamExt};
|
||||||
use std::collections::HashSet;
|
|
||||||
use std::{io, time::Duration};
|
use std::{io, time::Duration};
|
||||||
use stretch2::{prelude::Size, Stretch};
|
use stretch2::{prelude::Size, Stretch};
|
||||||
use style_attributes::StyleModifier;
|
use style_attributes::StyleModifier;
|
||||||
|
@ -44,7 +42,7 @@ pub fn launch_cfg(app: Component<()>, cfg: Config) {
|
||||||
|
|
||||||
cx.provide_root_context(state);
|
cx.provide_root_context(state);
|
||||||
|
|
||||||
let mut tree: Tree<StretchLayout, StyleModifier> = Tree::new();
|
let mut tree: ClientTree<StretchLayout, StyleModifier> = ClientTree::new();
|
||||||
let mutations = dom.rebuild();
|
let mutations = dom.rebuild();
|
||||||
let to_update = tree.apply_mutations(vec![mutations]);
|
let to_update = tree.apply_mutations(vec![mutations]);
|
||||||
let mut stretch = Stretch::new();
|
let mut stretch = Stretch::new();
|
||||||
|
@ -60,7 +58,7 @@ pub fn render_vdom(
|
||||||
ctx: UnboundedSender<TermEvent>,
|
ctx: UnboundedSender<TermEvent>,
|
||||||
handler: RinkInputHandler,
|
handler: RinkInputHandler,
|
||||||
cfg: Config,
|
cfg: Config,
|
||||||
mut tree: Tree<StretchLayout, StyleModifier>,
|
mut tree: ClientTree<StretchLayout, StyleModifier>,
|
||||||
mut stretch: Stretch,
|
mut stretch: Stretch,
|
||||||
) -> Result<()> {
|
) -> Result<()> {
|
||||||
// Setup input handling
|
// Setup input handling
|
||||||
|
@ -100,12 +98,8 @@ pub fn render_vdom(
|
||||||
let mut terminal = Terminal::new(backend).unwrap();
|
let mut terminal = Terminal::new(backend).unwrap();
|
||||||
|
|
||||||
terminal.clear().unwrap();
|
terminal.clear().unwrap();
|
||||||
let mut to_rerender: HashSet<usize> = tree
|
let mut to_rerender: fxhash::FxHashSet<usize> = vec![0].into_iter().collect();
|
||||||
.nodes
|
let mut redraw = true;
|
||||||
.iter()
|
|
||||||
.filter_map(|n| n.as_ref())
|
|
||||||
.map(|n| n.id.0)
|
|
||||||
.collect();
|
|
||||||
|
|
||||||
loop {
|
loop {
|
||||||
/*
|
/*
|
||||||
|
@ -120,15 +114,16 @@ pub fn render_vdom(
|
||||||
todo: lazy re-rendering
|
todo: lazy re-rendering
|
||||||
*/
|
*/
|
||||||
|
|
||||||
if !to_rerender.is_empty() {
|
if !to_rerender.is_empty() || redraw {
|
||||||
|
redraw = false;
|
||||||
terminal.draw(|frame| {
|
terminal.draw(|frame| {
|
||||||
// size is guaranteed to not change when rendering
|
// size is guaranteed to not change when rendering
|
||||||
let dims = frame.size();
|
let dims = frame.size();
|
||||||
// println!("{dims:?}");
|
// println!("{dims:?}");
|
||||||
let width = dims.width;
|
let width = dims.width;
|
||||||
let height = dims.height;
|
let height = dims.height;
|
||||||
let root_id = tree.root;
|
let root_id = tree.root_id();
|
||||||
let root_node = tree.get(root_id).up_state.node.unwrap();
|
let root_node = tree[root_id].up_state.node.unwrap();
|
||||||
stretch
|
stretch
|
||||||
.compute_layout(
|
.compute_layout(
|
||||||
root_node,
|
root_node,
|
||||||
|
@ -138,7 +133,7 @@ pub fn render_vdom(
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
let root = tree.get(tree.root);
|
let root = &tree[tree.root_id()];
|
||||||
render::render_vnode(frame, &stretch, &tree, &root, cfg);
|
render::render_vnode(frame, &stretch, &tree, &root, cfg);
|
||||||
})?;
|
})?;
|
||||||
}
|
}
|
||||||
|
@ -146,22 +141,6 @@ pub fn render_vdom(
|
||||||
// resolve events before rendering
|
// resolve events before rendering
|
||||||
// todo: events do not trigger update?
|
// todo: events do not trigger update?
|
||||||
for e in handler.get_events(&stretch, &mut tree) {
|
for e in handler.get_events(&stretch, &mut tree) {
|
||||||
// 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!()
|
|
||||||
// };
|
|
||||||
// println!("{tname}: {e:?}");
|
|
||||||
vdom.handle_message(SchedulerMsg::Event(e));
|
vdom.handle_message(SchedulerMsg::Event(e));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -184,7 +163,8 @@ pub fn render_vdom(
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
TermEvent::Resize(_, _) | TermEvent::Mouse(_) => {}
|
TermEvent::Resize(_, _) => redraw = true,
|
||||||
|
TermEvent::Mouse(_) => {}
|
||||||
},
|
},
|
||||||
InputEvent::Tick => {} // tick
|
InputEvent::Tick => {} // tick
|
||||||
InputEvent::Close => break,
|
InputEvent::Close => break,
|
||||||
|
|
|
@ -1,4 +1,8 @@
|
||||||
use dioxus_native_core::{layout::StretchLayout, layout_attributes::UnitSystem, Tree, TreeNode};
|
use dioxus_native_core::{
|
||||||
|
client_tree::{ClientTree, TreeNode},
|
||||||
|
layout::StretchLayout,
|
||||||
|
layout_attributes::UnitSystem,
|
||||||
|
};
|
||||||
use std::io::Stdout;
|
use std::io::Stdout;
|
||||||
use stretch2::{
|
use stretch2::{
|
||||||
geometry::Point,
|
geometry::Point,
|
||||||
|
@ -19,12 +23,14 @@ const RADIUS_MULTIPLIER: [f32; 2] = [1.0, 0.5];
|
||||||
pub fn render_vnode<'a>(
|
pub fn render_vnode<'a>(
|
||||||
frame: &mut tui::Frame<CrosstermBackend<Stdout>>,
|
frame: &mut tui::Frame<CrosstermBackend<Stdout>>,
|
||||||
layout: &Stretch,
|
layout: &Stretch,
|
||||||
tree: &Tree<StretchLayout, StyleModifier>,
|
tree: &ClientTree<StretchLayout, StyleModifier>,
|
||||||
node: &TreeNode<StretchLayout, StyleModifier>,
|
node: &TreeNode<StretchLayout, StyleModifier>,
|
||||||
cfg: Config,
|
cfg: Config,
|
||||||
) {
|
) {
|
||||||
|
use dioxus_native_core::client_tree::TreeNodeType;
|
||||||
|
|
||||||
match &node.node_type {
|
match &node.node_type {
|
||||||
dioxus_native_core::TreeNodeType::Placeholder => return,
|
TreeNodeType::Placeholder => return,
|
||||||
_ => (),
|
_ => (),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -34,7 +40,7 @@ pub fn render_vnode<'a>(
|
||||||
let Size { width, height } = size;
|
let Size { width, height } = size;
|
||||||
|
|
||||||
match &node.node_type {
|
match &node.node_type {
|
||||||
dioxus_native_core::TreeNodeType::Text { text } => {
|
TreeNodeType::Text { text } => {
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct Label<'a> {
|
struct Label<'a> {
|
||||||
text: &'a str,
|
text: &'a str,
|
||||||
|
@ -64,7 +70,7 @@ pub fn render_vnode<'a>(
|
||||||
frame.render_widget(WidgetWithContext::new(label, cfg), area);
|
frame.render_widget(WidgetWithContext::new(label, cfg), area);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dioxus_native_core::TreeNodeType::Element { children, .. } => {
|
TreeNodeType::Element { children, .. } => {
|
||||||
let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16);
|
let area = Rect::new(*x as u16, *y as u16, *width as u16, *height as u16);
|
||||||
|
|
||||||
// the renderer will panic if a node is rendered out of range even if the size is zero
|
// the renderer will panic if a node is rendered out of range even if the size is zero
|
||||||
|
@ -73,10 +79,10 @@ pub fn render_vnode<'a>(
|
||||||
}
|
}
|
||||||
|
|
||||||
for c in children {
|
for c in children {
|
||||||
render_vnode(frame, layout, tree, tree.get(c.0), cfg);
|
render_vnode(frame, layout, tree, &tree[c.0], cfg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
dioxus_native_core::TreeNodeType::Placeholder => unreachable!(),
|
TreeNodeType::Placeholder => unreachable!(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -31,8 +31,8 @@
|
||||||
|
|
||||||
use dioxus_core::{Attribute, VNode};
|
use dioxus_core::{Attribute, VNode};
|
||||||
use dioxus_native_core::{
|
use dioxus_native_core::{
|
||||||
|
client_tree::PushedDownState,
|
||||||
layout_attributes::{parse_value, UnitSystem},
|
layout_attributes::{parse_value, UnitSystem},
|
||||||
PushedDownState,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
use crate::style::{RinkColor, RinkStyle};
|
use crate::style::{RinkColor, RinkStyle};
|
||||||
|
@ -48,7 +48,9 @@ impl PushedDownState for StyleModifier {
|
||||||
|
|
||||||
fn reduce(&mut self, parent: Option<&Self>, vnode: &VNode, _ctx: &mut Self::Ctx) {
|
fn reduce(&mut self, parent: Option<&Self>, vnode: &VNode, _ctx: &mut Self::Ctx) {
|
||||||
*self = StyleModifier::default();
|
*self = StyleModifier::default();
|
||||||
self.style.fg = None;
|
if parent.is_some() {
|
||||||
|
self.style.fg = None;
|
||||||
|
}
|
||||||
match vnode {
|
match vnode {
|
||||||
VNode::Element(el) => {
|
VNode::Element(el) => {
|
||||||
// handle text modifier elements
|
// handle text modifier elements
|
||||||
|
|
|
@ -1,83 +0,0 @@
|
||||||
// use dioxus_core::{Element, ElementId, Mutations, VNode, VirtualDom, DomEdit};
|
|
||||||
|
|
||||||
// /// The focus system needs a iterator that can persist through changes in the [VirtualDom]. Iterate through it with [ElementIter::next], and update it with [ElementIter::update] (with data from [`VirtualDom::work_with_deadline`]).
|
|
||||||
// pub(crate) struct ElementIter {
|
|
||||||
// // stack of elements and fragments
|
|
||||||
// stack: smallvec::SmallVec<[(ElementId, usize); 5]>,
|
|
||||||
// }
|
|
||||||
|
|
||||||
// impl ElementIter {
|
|
||||||
// pub(crate) fn new(initial: ElementId) -> Self {
|
|
||||||
// ElementIter {
|
|
||||||
// stack: smallvec::smallvec![(initial, 0)],
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// /// remove stale element refreneces
|
|
||||||
// pub(crate) fn update(&mut self, mutations: &Mutations, vdom: &VirtualDom) {
|
|
||||||
// let ids_removed: Vec<_> = mutations.edits.iter().filter_map(|e| if let DomEdit::Remove{root: }).collect();
|
|
||||||
// for node in self.stack {
|
|
||||||
|
|
||||||
// match node.0 {
|
|
||||||
// VNode::Fragment(f) => {
|
|
||||||
|
|
||||||
// }
|
|
||||||
|
|
||||||
// VNode::Element(_) => {}
|
|
||||||
|
|
||||||
// _ => unreachable!(),
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// pub(crate) fn next<'a>(&mut self, vdom: &'a VirtualDom) -> Option<&'a VNode<'a>> {
|
|
||||||
// let last = self.stack.last()?.0;
|
|
||||||
// let node = vdom.get_element(last)?;
|
|
||||||
// match node {
|
|
||||||
// VNode::Fragment(f) => {
|
|
||||||
// let mut last_mut = self.stack.last_mut()?;
|
|
||||||
// if last_mut.1 + 1 >= f.children.len() {
|
|
||||||
// self.stack.pop();
|
|
||||||
// self.next(vdom)
|
|
||||||
// } else {
|
|
||||||
// last_mut.1 += 1;
|
|
||||||
// let new_node = &f.children[last_mut.1];
|
|
||||||
// if matches!(new_node, VNode::Fragment(_) | VNode::Element(_)) {
|
|
||||||
// self.stack.push((new_node.mounted_id(), 0));
|
|
||||||
// }
|
|
||||||
// self.next(vdom)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// VNode::Component(vcomp) => {
|
|
||||||
// let idx = vcomp.scope.get().unwrap();
|
|
||||||
// let new_node = vdom.get_scope(idx).unwrap().root_node();
|
|
||||||
// *self.stack.last_mut()? = (new_node.mounted_id(), 0);
|
|
||||||
// self.next(vdom)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// VNode::Placeholder(_) | VNode::Text(_) => {
|
|
||||||
// self.stack.pop();
|
|
||||||
// self.next(vdom)
|
|
||||||
// }
|
|
||||||
|
|
||||||
// VNode::Element(e) => {
|
|
||||||
// let mut last_mut = self.stack.last_mut()?;
|
|
||||||
// if last_mut.1 + 1 >= e.children.len() {
|
|
||||||
// self.stack.pop();
|
|
||||||
// self.next(vdom);
|
|
||||||
// } else {
|
|
||||||
// last_mut.1 += 1;
|
|
||||||
// let new_node = &e.children[last_mut.1];
|
|
||||||
// if matches!(new_node, VNode::Fragment(_) | VNode::Element(_)) {
|
|
||||||
// self.stack.push((new_node.mounted_id(), 0));
|
|
||||||
// }
|
|
||||||
// self.next(vdom);
|
|
||||||
// }
|
|
||||||
// Some(node)
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
// }
|
|
||||||
|
|
||||||
// pub(crate) fn peak(&self) -> Option<&ElementId> {
|
|
||||||
// self.stack.last().map(|(e, c)| e)
|
|
||||||
// }
|
|
||||||
// }
|
|
|
@ -90,4 +90,11 @@ fn margin_and_flex_row2() {
|
||||||
assert_eq!(stretch.layout(node).unwrap().size.height, 100f32);
|
assert_eq!(stretch.layout(node).unwrap().size.height, 100f32);
|
||||||
assert_eq!(stretch.layout(node).unwrap().location.x, 0f32);
|
assert_eq!(stretch.layout(node).unwrap().location.x, 0f32);
|
||||||
assert_eq!(stretch.layout(node).unwrap().location.y, 0f32);
|
assert_eq!(stretch.layout(node).unwrap().location.y, 0f32);
|
||||||
|
|
||||||
|
dbg!(stretch.layout(node0)).unwrap();
|
||||||
|
|
||||||
|
// assert_eq!(stretch.layout(node0).unwrap().size.width, 80f32);
|
||||||
|
// assert_eq!(stretch.layout(node0).unwrap().size.height, 100f32);
|
||||||
|
// assert_eq!(stretch.layout(node0).unwrap().location.x, 10f32);
|
||||||
|
// assert_eq!(stretch.layout(node0).unwrap().location.y, 0f32);
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue