mirror of
https://github.com/DioxusLabs/dioxus
synced 2025-02-17 06:08:26 +00:00
Merge branch 'master' into jk/suspense-rollover
This commit is contained in:
commit
8887d65e1b
35 changed files with 2513 additions and 2637 deletions
|
@ -126,11 +126,9 @@ impl VirtualDom {
|
|||
}
|
||||
});
|
||||
|
||||
for root in node.root_ids {
|
||||
if let Some(id) = root.get() {
|
||||
if id.0 != 0 {
|
||||
self.try_reclaim(id);
|
||||
}
|
||||
for id in &node.root_ids {
|
||||
if id.0 != 0 {
|
||||
self.try_reclaim(id);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -5,13 +5,50 @@ use crate::mutations::Mutation::*;
|
|||
use crate::nodes::VNode;
|
||||
use crate::nodes::{DynamicNode, TemplateNode};
|
||||
use crate::virtual_dom::VirtualDom;
|
||||
use crate::{AttributeValue, ElementId, RenderReturn, ScopeId, SuspenseContext};
|
||||
use crate::{AttributeValue, ElementId, RenderReturn, ScopeId, SuspenseContext, Template};
|
||||
use std::cell::Cell;
|
||||
use std::iter::{Enumerate, Peekable};
|
||||
use std::iter::Peekable;
|
||||
use std::rc::Rc;
|
||||
use std::slice;
|
||||
use TemplateNode::*;
|
||||
|
||||
fn sort_bfs(paths: &[&'static [u8]]) -> Vec<(usize, &'static [u8])> {
|
||||
let mut with_indecies = paths.iter().copied().enumerate().collect::<Vec<_>>();
|
||||
with_indecies.sort_unstable_by(|(_, a), (_, b)| {
|
||||
let mut a = a.iter();
|
||||
let mut b = b.iter();
|
||||
loop {
|
||||
match (a.next(), b.next()) {
|
||||
(Some(a), Some(b)) => {
|
||||
if a != b {
|
||||
return a.cmp(b);
|
||||
}
|
||||
}
|
||||
// The shorter path goes first
|
||||
(Some(_), None) => return std::cmp::Ordering::Less,
|
||||
(None, Some(_)) => return std::cmp::Ordering::Greater,
|
||||
(None, None) => return std::cmp::Ordering::Equal,
|
||||
}
|
||||
}
|
||||
});
|
||||
with_indecies
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn sorting() {
|
||||
let r: [(usize, &[u8]); 5] = [
|
||||
(0, &[0, 1]),
|
||||
(1, &[0, 2]),
|
||||
(2, &[1, 0]),
|
||||
(4, &[1, 1]),
|
||||
(3, &[1, 2]),
|
||||
];
|
||||
assert_eq!(
|
||||
sort_bfs(&[&[0, 1,], &[0, 2,], &[1, 0,], &[1, 2,], &[1, 1,],]),
|
||||
r
|
||||
);
|
||||
assert!(matches!(&[0], &[_, ..]))
|
||||
}
|
||||
|
||||
impl<'b> VirtualDom {
|
||||
/// Create a new template [`VNode`] and write it to the [`Mutations`] buffer.
|
||||
///
|
||||
|
@ -25,21 +62,79 @@ impl<'b> VirtualDom {
|
|||
|
||||
/// Create this template and write its mutations
|
||||
pub(crate) fn create(&mut self, node: &'b VNode<'b>) -> usize {
|
||||
// check for a overriden template
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
let (path, byte_index) = node.template.get().name.rsplit_once(':').unwrap();
|
||||
if let Some(template) = self
|
||||
.templates
|
||||
.get(path)
|
||||
.and_then(|map| map.get(&byte_index.parse().unwrap()))
|
||||
{
|
||||
node.template.set(*template);
|
||||
}
|
||||
}
|
||||
|
||||
// Intialize the root nodes slice
|
||||
node.root_ids
|
||||
.intialize(vec![ElementId(0); node.template.get().roots.len()].into_boxed_slice());
|
||||
|
||||
// The best renderers will have templates prehydrated and registered
|
||||
// Just in case, let's create the template using instructions anyways
|
||||
if !self.templates.contains_key(&node.template.name) {
|
||||
self.register_template(node);
|
||||
if !self.templates.contains_key(&node.template.get().name) {
|
||||
self.register_template(node.template.get());
|
||||
}
|
||||
|
||||
// we know that this will generate at least one mutation per node
|
||||
self.mutations.edits.reserve(node.template.roots.len());
|
||||
self.mutations
|
||||
.edits
|
||||
.reserve(node.template.get().roots.len());
|
||||
|
||||
// Walk the roots, creating nodes and assigning IDs
|
||||
// nodes in an iterator of ((dynamic_node_index, sorted_index), path)
|
||||
// todo: adjust dynamic nodes to be in the order of roots and then leaves (ie BFS)
|
||||
let mut attrs = node.template.attr_paths.iter().enumerate().peekable();
|
||||
let mut nodes = node.template.node_paths.iter().enumerate().peekable();
|
||||
#[cfg(not(debug_assertions))]
|
||||
let (mut attrs, mut nodes) = (
|
||||
node.template
|
||||
.get()
|
||||
.attr_paths
|
||||
.iter()
|
||||
.copied()
|
||||
.enumerate()
|
||||
.peekable(),
|
||||
node.template
|
||||
.get()
|
||||
.node_paths
|
||||
.iter()
|
||||
.copied()
|
||||
.enumerate()
|
||||
.map(|(i, path)| ((i, i), path))
|
||||
.peekable(),
|
||||
);
|
||||
// If this is a debug build, we need to check that the paths are in the correct order because hot reloading can cause scrambled states
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
let (attrs_sorted, nodes_sorted) = {
|
||||
(
|
||||
sort_bfs(node.template.get().attr_paths),
|
||||
sort_bfs(node.template.get().node_paths),
|
||||
)
|
||||
};
|
||||
#[cfg(debug_assertions)]
|
||||
let (mut attrs, mut nodes) = {
|
||||
(
|
||||
attrs_sorted.into_iter().peekable(),
|
||||
nodes_sorted
|
||||
.iter()
|
||||
.copied()
|
||||
.enumerate()
|
||||
.map(|(i, (id, path))| ((id, i), path))
|
||||
.peekable(),
|
||||
)
|
||||
};
|
||||
|
||||
node.template
|
||||
.get()
|
||||
.roots
|
||||
.iter()
|
||||
.enumerate()
|
||||
|
@ -48,7 +143,14 @@ impl<'b> VirtualDom {
|
|||
nodes.next().unwrap();
|
||||
self.write_dynamic_root(node, *id)
|
||||
}
|
||||
Element { .. } => self.write_element_root(node, idx, &mut attrs, &mut nodes),
|
||||
Element { .. } => {
|
||||
#[cfg(not(debug_assertions))]
|
||||
let id = self.write_element_root(node, idx, &mut attrs, &mut nodes, &[]);
|
||||
#[cfg(debug_assertions)]
|
||||
let id =
|
||||
self.write_element_root(node, idx, &mut attrs, &mut nodes, &nodes_sorted);
|
||||
id
|
||||
}
|
||||
Text { .. } => self.write_static_text_root(node, idx),
|
||||
})
|
||||
.sum()
|
||||
|
@ -65,8 +167,9 @@ impl<'b> VirtualDom {
|
|||
fn write_dynamic_root(&mut self, template: &'b VNode<'b>, idx: usize) -> usize {
|
||||
use DynamicNode::*;
|
||||
match &template.dynamic_nodes[idx] {
|
||||
node @ Fragment(_) => self.create_dynamic_node(template, node, idx),
|
||||
node @ Component { .. } => self.create_dynamic_node(template, node, idx),
|
||||
node @ Component { .. } | node @ Fragment(_) => {
|
||||
self.create_dynamic_node(template, node, idx)
|
||||
}
|
||||
Placeholder(VPlaceholder { id }) => {
|
||||
let id = self.set_slot(template, id, idx);
|
||||
self.mutations.push(CreatePlaceholder { id });
|
||||
|
@ -98,17 +201,18 @@ impl<'b> VirtualDom {
|
|||
&mut self,
|
||||
template: &'b VNode<'b>,
|
||||
root_idx: usize,
|
||||
dynamic_attrs: &mut Peekable<Enumerate<slice::Iter<&'static [u8]>>>,
|
||||
dynamic_nodes: &mut Peekable<Enumerate<slice::Iter<&'static [u8]>>>,
|
||||
dynamic_attrs: &mut Peekable<impl Iterator<Item = (usize, &'static [u8])>>,
|
||||
dynamic_nodes_iter: &mut Peekable<impl Iterator<Item = ((usize, usize), &'static [u8])>>,
|
||||
dynamic_nodes: &[(usize, &'static [u8])],
|
||||
) -> usize {
|
||||
// Load the template root and get the ID for the node on the stack
|
||||
let root_on_stack = self.load_template_root(template, root_idx);
|
||||
|
||||
// Write all the attributes below this root
|
||||
self.write_attrs_on_root(dynamic_attrs, root_idx, root_on_stack, template);
|
||||
self.write_attrs_on_root(dynamic_attrs, root_idx as u8, root_on_stack, template);
|
||||
|
||||
// Load in all of the placeholder or dynamic content under this root too
|
||||
self.load_placeholders(dynamic_nodes, root_idx, template);
|
||||
self.load_placeholders(dynamic_nodes_iter, dynamic_nodes, root_idx as u8, template);
|
||||
|
||||
1
|
||||
}
|
||||
|
@ -126,22 +230,32 @@ impl<'b> VirtualDom {
|
|||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[allow(unused)]
|
||||
fn load_placeholders(
|
||||
&mut self,
|
||||
dynamic_nodes: &mut Peekable<Enumerate<slice::Iter<&'static [u8]>>>,
|
||||
root_idx: usize,
|
||||
dynamic_nodes_iter: &mut Peekable<impl Iterator<Item = ((usize, usize), &'static [u8])>>,
|
||||
dynamic_nodes: &[(usize, &'static [u8])],
|
||||
root_idx: u8,
|
||||
template: &'b VNode<'b>,
|
||||
) {
|
||||
let (start, end) = match collect_dyn_node_range(dynamic_nodes, root_idx) {
|
||||
let (start, end) = match collect_dyn_node_range(dynamic_nodes_iter, root_idx) {
|
||||
Some((a, b)) => (a, b),
|
||||
None => return,
|
||||
};
|
||||
|
||||
for idx in (start..=end).rev() {
|
||||
// If hot reloading is enabled, we need to map the sorted index to the original index of the dynamic node. If it is disabled, we can just use the sorted index
|
||||
#[cfg(not(debug_assertions))]
|
||||
let reversed_iter = (start..=end).rev();
|
||||
#[cfg(debug_assertions)]
|
||||
let reversed_iter = (start..=end)
|
||||
.rev()
|
||||
.map(|sorted_index| dynamic_nodes[sorted_index].0);
|
||||
|
||||
for idx in reversed_iter {
|
||||
let m = self.create_dynamic_node(template, &template.dynamic_nodes[idx], idx);
|
||||
if m > 0 {
|
||||
// The path is one shorter because the top node is the root
|
||||
let path = &template.template.node_paths[idx][1..];
|
||||
let path = &template.template.get().node_paths[idx][1..];
|
||||
self.mutations.push(ReplacePlaceholder { m, path });
|
||||
}
|
||||
}
|
||||
|
@ -149,12 +263,14 @@ impl<'b> VirtualDom {
|
|||
|
||||
fn write_attrs_on_root(
|
||||
&mut self,
|
||||
attrs: &mut Peekable<Enumerate<slice::Iter<&'static [u8]>>>,
|
||||
root_idx: usize,
|
||||
attrs: &mut Peekable<impl Iterator<Item = (usize, &'static [u8])>>,
|
||||
root_idx: u8,
|
||||
root: ElementId,
|
||||
node: &VNode,
|
||||
) {
|
||||
while let Some((mut attr_id, path)) = attrs.next_if(|(_, p)| p[0] == root_idx as u8) {
|
||||
while let Some((mut attr_id, path)) =
|
||||
attrs.next_if(|(_, p)| p.first().copied() == Some(root_idx))
|
||||
{
|
||||
let id = self.assign_static_node_as_dynamic(path, root, node, attr_id);
|
||||
|
||||
loop {
|
||||
|
@ -210,10 +326,10 @@ impl<'b> VirtualDom {
|
|||
fn load_template_root(&mut self, template: &VNode, root_idx: usize) -> ElementId {
|
||||
// Get an ID for this root since it's a real root
|
||||
let this_id = self.next_root(template, root_idx);
|
||||
template.root_ids[root_idx].set(Some(this_id));
|
||||
template.root_ids.set(root_idx, this_id);
|
||||
|
||||
self.mutations.push(LoadTemplate {
|
||||
name: template.template.name,
|
||||
name: template.template.get().name,
|
||||
index: root_idx,
|
||||
id: this_id,
|
||||
});
|
||||
|
@ -241,7 +357,7 @@ impl<'b> VirtualDom {
|
|||
|
||||
// if attribute is on a root node, then we've already created the element
|
||||
// Else, it's deep in the template and we should create a new id for it
|
||||
let id = self.next_element(template, template.template.attr_paths[attr_id]);
|
||||
let id = self.next_element(template, template.template.get().attr_paths[attr_id]);
|
||||
|
||||
self.mutations.push(Mutation::AssignId {
|
||||
path: &path[1..],
|
||||
|
@ -252,14 +368,59 @@ impl<'b> VirtualDom {
|
|||
}
|
||||
|
||||
/// Insert a new template into the VirtualDom's template registry
|
||||
fn register_template(&mut self, template: &'b VNode<'b>) {
|
||||
pub(crate) fn register_template_first_byte_index(&mut self, mut template: Template<'static>) {
|
||||
// First, make sure we mark the template as seen, regardless if we process it
|
||||
self.templates
|
||||
.insert(template.template.name, template.template);
|
||||
let (path, _) = template.name.rsplit_once(':').unwrap();
|
||||
if let Some((_, old_template)) = self
|
||||
.templates
|
||||
.entry(path)
|
||||
.or_default()
|
||||
.iter_mut()
|
||||
.min_by_key(|(byte_index, _)| **byte_index)
|
||||
{
|
||||
// the byte index of the hot reloaded template could be different
|
||||
template.name = old_template.name;
|
||||
*old_template = template;
|
||||
} else {
|
||||
// This is a template without any current instances
|
||||
self.templates
|
||||
.entry(path)
|
||||
.or_default()
|
||||
.insert(usize::MAX, template);
|
||||
}
|
||||
|
||||
// If it's all dynamic nodes, then we don't need to register it
|
||||
if !template.template.is_completely_dynamic() {
|
||||
self.mutations.templates.push(template.template);
|
||||
if !template.is_completely_dynamic() {
|
||||
self.mutations.templates.push(template);
|
||||
}
|
||||
}
|
||||
|
||||
/// Insert a new template into the VirtualDom's template registry
|
||||
pub(crate) fn register_template(&mut self, mut template: Template<'static>) {
|
||||
// First, make sure we mark the template as seen, regardless if we process it
|
||||
let (path, byte_index) = template.name.rsplit_once(':').unwrap();
|
||||
let byte_index = byte_index.parse::<usize>().unwrap();
|
||||
|
||||
// if hot reloading is enabled, then we need to check for a template that has overriten this one
|
||||
#[cfg(debug_assertions)]
|
||||
if let Some(mut new_template) = self
|
||||
.templates
|
||||
.get_mut(path)
|
||||
.and_then(|map| map.remove(&usize::MAX))
|
||||
{
|
||||
// the byte index of the hot reloaded template could be different
|
||||
new_template.name = template.name;
|
||||
template = new_template;
|
||||
}
|
||||
|
||||
self.templates
|
||||
.entry(path)
|
||||
.or_default()
|
||||
.insert(byte_index, template);
|
||||
|
||||
// If it's all dynamic nodes, then we don't need to register it
|
||||
if !template.is_completely_dynamic() {
|
||||
self.mutations.templates.push(template);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -285,7 +446,7 @@ impl<'b> VirtualDom {
|
|||
idx: usize,
|
||||
) -> usize {
|
||||
// Allocate a dynamic element reference for this text node
|
||||
let new_id = self.next_element(template, template.template.node_paths[idx]);
|
||||
let new_id = self.next_element(template, template.template.get().node_paths[idx]);
|
||||
|
||||
// Make sure the text node is assigned to the correct element
|
||||
text.id.set(Some(new_id));
|
||||
|
@ -296,7 +457,7 @@ impl<'b> VirtualDom {
|
|||
// Add the mutation to the list
|
||||
self.mutations.push(HydrateText {
|
||||
id: new_id,
|
||||
path: &template.template.node_paths[idx][1..],
|
||||
path: &template.template.get().node_paths[idx][1..],
|
||||
value,
|
||||
});
|
||||
|
||||
|
@ -311,14 +472,14 @@ impl<'b> VirtualDom {
|
|||
idx: usize,
|
||||
) -> usize {
|
||||
// Allocate a dynamic element reference for this text node
|
||||
let id = self.next_element(template, template.template.node_paths[idx]);
|
||||
let id = self.next_element(template, template.template.get().node_paths[idx]);
|
||||
|
||||
// Make sure the text node is assigned to the correct element
|
||||
placeholder.id.set(Some(id));
|
||||
|
||||
// Assign the ID to the existing node in the template
|
||||
self.mutations.push(AssignId {
|
||||
path: &template.template.node_paths[idx][1..],
|
||||
path: &template.template.get().node_paths[idx][1..],
|
||||
id,
|
||||
});
|
||||
|
||||
|
@ -384,7 +545,7 @@ impl<'b> VirtualDom {
|
|||
};
|
||||
|
||||
// Since this is a boundary, use its placeholder within the template as the placeholder for the suspense tree
|
||||
let new_id = self.next_element(new, parent.template.node_paths[idx]);
|
||||
let new_id = self.next_element(new, parent.template.get().node_paths[idx]);
|
||||
|
||||
// Now connect everything to the boundary
|
||||
self.scopes[scope.0].placeholder.set(Some(new_id));
|
||||
|
@ -406,7 +567,7 @@ impl<'b> VirtualDom {
|
|||
// Now assign the placeholder in the DOM
|
||||
self.mutations.push(AssignId {
|
||||
id: new_id,
|
||||
path: &parent.template.node_paths[idx][1..],
|
||||
path: &parent.template.get().node_paths[idx][1..],
|
||||
});
|
||||
|
||||
0
|
||||
|
@ -423,7 +584,7 @@ impl<'b> VirtualDom {
|
|||
///
|
||||
/// IE simply assign an ID to the placeholder
|
||||
fn mount_async(&mut self, template: &VNode, idx: usize, scope: ScopeId) -> usize {
|
||||
let new_id = self.next_element(template, template.template.node_paths[idx]);
|
||||
let new_id = self.next_element(template, template.template.get().node_paths[idx]);
|
||||
|
||||
// Set the placeholder of the scope
|
||||
self.scopes[scope.0].placeholder.set(Some(new_id));
|
||||
|
@ -431,7 +592,7 @@ impl<'b> VirtualDom {
|
|||
// Since the placeholder is already in the DOM, we don't create any new nodes
|
||||
self.mutations.push(AssignId {
|
||||
id: new_id,
|
||||
path: &template.template.node_paths[idx][1..],
|
||||
path: &template.template.get().node_paths[idx][1..],
|
||||
});
|
||||
|
||||
0
|
||||
|
@ -443,24 +604,26 @@ impl<'b> VirtualDom {
|
|||
slot: &'b Cell<Option<ElementId>>,
|
||||
id: usize,
|
||||
) -> ElementId {
|
||||
let id = self.next_element(template, template.template.node_paths[id]);
|
||||
let id = self.next_element(template, template.template.get().node_paths[id]);
|
||||
slot.set(Some(id));
|
||||
id
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_dyn_node_range(
|
||||
dynamic_nodes: &mut Peekable<Enumerate<slice::Iter<&[u8]>>>,
|
||||
root_idx: usize,
|
||||
dynamic_nodes: &mut Peekable<impl Iterator<Item = ((usize, usize), &'static [u8])>>,
|
||||
root_idx: u8,
|
||||
) -> Option<(usize, usize)> {
|
||||
let start = match dynamic_nodes.peek() {
|
||||
Some((idx, p)) if p[0] == root_idx as u8 => *idx,
|
||||
Some(((_, idx), [first, ..])) if *first == root_idx => *idx,
|
||||
_ => return None,
|
||||
};
|
||||
|
||||
let mut end = start;
|
||||
|
||||
while let Some((idx, p)) = dynamic_nodes.next_if(|(_, p)| p[0] == root_idx as u8) {
|
||||
while let Some(((_, idx), p)) =
|
||||
dynamic_nodes.next_if(|(_, p)| matches!(p, [idx, ..] if *idx == root_idx))
|
||||
{
|
||||
if p.len() == 1 {
|
||||
continue;
|
||||
}
|
||||
|
|
|
@ -70,6 +70,21 @@ impl<'b> VirtualDom {
|
|||
}
|
||||
|
||||
fn diff_node(&mut self, left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) {
|
||||
// If hot reloading is enabled, we need to make sure we're using the latest template
|
||||
#[cfg(debug_assertions)]
|
||||
{
|
||||
let (path, byte_index) = right_template.template.get().name.rsplit_once(':').unwrap();
|
||||
if let Some(map) = self.templates.get(path) {
|
||||
let byte_index = byte_index.parse::<usize>().unwrap();
|
||||
if let Some(&template) = map.get(&byte_index) {
|
||||
right_template.template.set(template);
|
||||
if template != left_template.template.get() {
|
||||
return self.replace(left_template, [right_template]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If the templates are the same, we don't need to do anything, nor do we want to
|
||||
if templates_are_the_same(left_template, right_template) {
|
||||
return;
|
||||
|
@ -114,11 +129,7 @@ impl<'b> VirtualDom {
|
|||
});
|
||||
|
||||
// Make sure the roots get transferred over while we're here
|
||||
left_template
|
||||
.root_ids
|
||||
.iter()
|
||||
.zip(right_template.root_ids.iter())
|
||||
.for_each(|(left, right)| right.set(left.get()));
|
||||
right_template.root_ids.transfer(&left_template.root_ids);
|
||||
}
|
||||
|
||||
fn diff_dynamic_node(
|
||||
|
@ -663,6 +674,7 @@ impl<'b> VirtualDom {
|
|||
/// Push all the real nodes on the stack
|
||||
fn push_all_real_nodes(&mut self, node: &'b VNode<'b>) -> usize {
|
||||
node.template
|
||||
.get()
|
||||
.roots
|
||||
.iter()
|
||||
.enumerate()
|
||||
|
@ -671,7 +683,7 @@ impl<'b> VirtualDom {
|
|||
Some(node) => node,
|
||||
None => {
|
||||
self.mutations.push(Mutation::PushRoot {
|
||||
id: node.root_ids[idx].get().unwrap(),
|
||||
id: node.root_ids.get(idx).unwrap(),
|
||||
});
|
||||
return 1;
|
||||
}
|
||||
|
@ -803,11 +815,11 @@ impl<'b> VirtualDom {
|
|||
}
|
||||
|
||||
fn reclaim_roots(&mut self, node: &VNode, gen_muts: bool) {
|
||||
for idx in 0..node.template.roots.len() {
|
||||
for (idx, _) in node.template.get().roots.iter().enumerate() {
|
||||
if let Some(dy) = node.dynamic_root(idx) {
|
||||
self.remove_dynamic_node(dy, gen_muts);
|
||||
} else {
|
||||
let id = node.root_ids[idx].get().unwrap();
|
||||
let id = node.root_ids.get(idx).unwrap();
|
||||
if gen_muts {
|
||||
self.mutations.push(Mutation::Remove { id });
|
||||
}
|
||||
|
@ -820,7 +832,15 @@ impl<'b> VirtualDom {
|
|||
let mut id = None;
|
||||
for (idx, attr) in node.dynamic_attrs.iter().enumerate() {
|
||||
// We'll clean up the root nodes either way, so don't worry
|
||||
if node.template.attr_paths[idx].len() == 1 {
|
||||
let path_len = node
|
||||
.template
|
||||
.get()
|
||||
.attr_paths
|
||||
.get(idx)
|
||||
.map(|path| path.len());
|
||||
// if the path is 1 the attribute is in the root, so we don't need to clean it up
|
||||
// if the path is 0, the attribute is a not attached at all, so we don't need to clean it up
|
||||
if let Some(..=1) = path_len {
|
||||
continue;
|
||||
}
|
||||
|
||||
|
@ -838,12 +858,16 @@ impl<'b> VirtualDom {
|
|||
|
||||
fn remove_nested_dyn_nodes(&mut self, node: &VNode) {
|
||||
for (idx, dyn_node) in node.dynamic_nodes.iter().enumerate() {
|
||||
// Roots are cleaned up automatically above
|
||||
if node.template.node_paths[idx].len() == 1 {
|
||||
continue;
|
||||
let path_len = node
|
||||
.template
|
||||
.get()
|
||||
.node_paths
|
||||
.get(idx)
|
||||
.map(|path| path.len());
|
||||
// Roots are cleaned up automatically above and nodes with a empty path are placeholders
|
||||
if let Some(2..) = path_len {
|
||||
self.remove_dynamic_node(dyn_node, false)
|
||||
}
|
||||
|
||||
self.remove_dynamic_node(dyn_node, false);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -903,7 +927,7 @@ impl<'b> VirtualDom {
|
|||
|
||||
fn find_first_element(&self, node: &'b VNode<'b>) -> ElementId {
|
||||
match node.dynamic_root(0) {
|
||||
None => node.root_ids[0].get().unwrap(),
|
||||
None => node.root_ids.get(0).unwrap(),
|
||||
Some(Text(t)) => t.id.get().unwrap(),
|
||||
Some(Fragment(t)) => self.find_first_element(&t[0]),
|
||||
Some(Placeholder(t)) => t.id.get().unwrap(),
|
||||
|
@ -918,8 +942,8 @@ impl<'b> VirtualDom {
|
|||
}
|
||||
|
||||
fn find_last_element(&self, node: &'b VNode<'b>) -> ElementId {
|
||||
match node.dynamic_root(node.template.roots.len() - 1) {
|
||||
None => node.root_ids.last().unwrap().get().unwrap(),
|
||||
match node.dynamic_root(node.template.get().roots.len() - 1) {
|
||||
None => node.root_ids.last().unwrap(),
|
||||
Some(Text(t)) => t.id.get().unwrap(),
|
||||
Some(Fragment(t)) => self.find_last_element(t.last().unwrap()),
|
||||
Some(Placeholder(t)) => t.id.get().unwrap(),
|
||||
|
@ -941,27 +965,30 @@ impl<'b> VirtualDom {
|
|||
/// We use the pointer of the dynamic_node list in this case
|
||||
fn templates_are_the_same<'b>(left_template: &'b VNode<'b>, right_template: &'b VNode<'b>) -> bool {
|
||||
std::ptr::eq(left_template, right_template)
|
||||
|| std::ptr::eq(left_template.dynamic_nodes, right_template.dynamic_nodes)
|
||||
}
|
||||
|
||||
fn templates_are_different(left_template: &VNode, right_template: &VNode) -> bool {
|
||||
!std::ptr::eq(left_template.template.name, right_template.template.name)
|
||||
&& left_template.template.name != right_template.template.name
|
||||
let left_template_name = left_template.template.get().name;
|
||||
let right_template_name = right_template.template.get().name;
|
||||
// we want to re-create the node if the template name is different by pointer even if the value is the same so that we can detect when hot reloading changes the template
|
||||
!std::ptr::eq(left_template_name, right_template_name)
|
||||
}
|
||||
|
||||
fn matching_components<'a>(
|
||||
left: &'a VNode<'a>,
|
||||
right: &'a VNode<'a>,
|
||||
) -> Option<Vec<(&'a VComponent<'a>, &'a VComponent<'a>)>> {
|
||||
if left.template.roots.len() != right.template.roots.len() {
|
||||
let left_template = left.template.get();
|
||||
let right_template = right.template.get();
|
||||
if left_template.roots.len() != right_template.roots.len() {
|
||||
return None;
|
||||
}
|
||||
|
||||
// run through the components, ensuring they're the same
|
||||
left.template
|
||||
left_template
|
||||
.roots
|
||||
.iter()
|
||||
.zip(right.template.roots.iter())
|
||||
.zip(right_template.roots.iter())
|
||||
.map(|(l, r)| {
|
||||
let (l, r) = match (l, r) {
|
||||
(TemplateNode::Dynamic { id: l }, TemplateNode::Dynamic { id: r }) => (l, r),
|
||||
|
@ -986,11 +1013,12 @@ fn matching_components<'a>(
|
|||
/// - for appending children we can use AppendChildren
|
||||
#[allow(dead_code)]
|
||||
fn is_dyn_node_only_child(node: &VNode, idx: usize) -> bool {
|
||||
let path = node.template.node_paths[idx];
|
||||
let template = node.template.get();
|
||||
let path = template.node_paths[idx];
|
||||
|
||||
// use a loop to index every static node's children until the path has run out
|
||||
// only break if the last path index is a dynamic node
|
||||
let mut static_node = &node.template.roots[path[0] as usize];
|
||||
let mut static_node = &template.roots[path[0] as usize];
|
||||
|
||||
for i in 1..path.len() - 1 {
|
||||
match static_node {
|
||||
|
|
|
@ -31,8 +31,8 @@ pub fn Fragment<'a>(cx: Scope<'a, FragmentProps<'a>>) -> Element {
|
|||
Some(VNode {
|
||||
key: children.key,
|
||||
parent: children.parent,
|
||||
template: children.template,
|
||||
root_ids: children.root_ids,
|
||||
template: children.template.clone(),
|
||||
root_ids: children.root_ids.clone(),
|
||||
dynamic_nodes: children.dynamic_nodes,
|
||||
dynamic_attrs: children.dynamic_attrs,
|
||||
})
|
||||
|
|
|
@ -5,7 +5,7 @@ use bumpalo::boxed::Box as BumpBox;
|
|||
use bumpalo::Bump;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
cell::{Cell, RefCell},
|
||||
cell::{Cell, RefCell, UnsafeCell},
|
||||
fmt::Arguments,
|
||||
future::Future,
|
||||
};
|
||||
|
@ -54,11 +54,11 @@ pub struct VNode<'a> {
|
|||
pub parent: Option<ElementId>,
|
||||
|
||||
/// The static nodes and static descriptor of the template
|
||||
pub template: Template<'static>,
|
||||
pub template: Cell<Template<'static>>,
|
||||
|
||||
/// The IDs for the roots of this template - to be used when moving the template around and removing it from
|
||||
/// the actual Dom
|
||||
pub root_ids: &'a [Cell<Option<ElementId>>],
|
||||
pub root_ids: BoxedCellSlice,
|
||||
|
||||
/// The dynamic parts of the template
|
||||
pub dynamic_nodes: &'a [DynamicNode<'a>],
|
||||
|
@ -67,21 +67,120 @@ pub struct VNode<'a> {
|
|||
pub dynamic_attrs: &'a [Attribute<'a>],
|
||||
}
|
||||
|
||||
// Saftey: There is no way to get references to the internal data of this struct so no refrences will be invalidated by mutating the data with a immutable reference (The same principle behind Cell)
|
||||
#[derive(Debug, Default)]
|
||||
pub struct BoxedCellSlice(UnsafeCell<Option<Box<[ElementId]>>>);
|
||||
|
||||
impl Clone for BoxedCellSlice {
|
||||
fn clone(&self) -> Self {
|
||||
Self(UnsafeCell::new(unsafe { (*self.0.get()).clone() }))
|
||||
}
|
||||
}
|
||||
|
||||
impl BoxedCellSlice {
|
||||
pub fn last(&self) -> Option<ElementId> {
|
||||
unsafe {
|
||||
(*self.0.get())
|
||||
.as_ref()
|
||||
.and_then(|inner| inner.as_ref().last().copied())
|
||||
}
|
||||
}
|
||||
|
||||
pub fn get(&self, idx: usize) -> Option<ElementId> {
|
||||
unsafe {
|
||||
(*self.0.get())
|
||||
.as_ref()
|
||||
.and_then(|inner| inner.as_ref().get(idx).copied())
|
||||
}
|
||||
}
|
||||
|
||||
pub unsafe fn get_unchecked(&self, idx: usize) -> Option<ElementId> {
|
||||
(*self.0.get())
|
||||
.as_ref()
|
||||
.and_then(|inner| inner.as_ref().get(idx).copied())
|
||||
}
|
||||
|
||||
pub fn set(&self, idx: usize, new: ElementId) {
|
||||
unsafe {
|
||||
if let Some(inner) = &mut *self.0.get() {
|
||||
inner[idx] = new;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn intialize(&self, contents: Box<[ElementId]>) {
|
||||
unsafe {
|
||||
*self.0.get() = Some(contents);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn transfer(&self, other: &Self) {
|
||||
unsafe {
|
||||
*self.0.get() = (*other.0.get()).clone();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn take_from(&self, other: &Self) {
|
||||
unsafe {
|
||||
*self.0.get() = (*other.0.get()).take();
|
||||
}
|
||||
}
|
||||
|
||||
pub fn len(&self) -> usize {
|
||||
unsafe {
|
||||
(*self.0.get())
|
||||
.as_ref()
|
||||
.map(|inner| inner.len())
|
||||
.unwrap_or(0)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoIterator for &'a BoxedCellSlice {
|
||||
type Item = ElementId;
|
||||
|
||||
type IntoIter = BoxedCellSliceIter<'a>;
|
||||
|
||||
fn into_iter(self) -> Self::IntoIter {
|
||||
BoxedCellSliceIter {
|
||||
index: 0,
|
||||
borrow: self,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub struct BoxedCellSliceIter<'a> {
|
||||
index: usize,
|
||||
borrow: &'a BoxedCellSlice,
|
||||
}
|
||||
|
||||
impl Iterator for BoxedCellSliceIter<'_> {
|
||||
type Item = ElementId;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let result = self.borrow.get(self.index);
|
||||
if result.is_some() {
|
||||
self.index += 1;
|
||||
}
|
||||
result
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> VNode<'a> {
|
||||
/// Create a template with no nodes that will be skipped over during diffing
|
||||
pub fn empty() -> Element<'a> {
|
||||
Some(VNode {
|
||||
key: None,
|
||||
parent: None,
|
||||
root_ids: &[],
|
||||
root_ids: BoxedCellSlice::default(),
|
||||
dynamic_nodes: &[],
|
||||
dynamic_attrs: &[],
|
||||
template: Template {
|
||||
template: Cell::new(Template {
|
||||
name: "dioxus-empty",
|
||||
roots: &[],
|
||||
node_paths: &[],
|
||||
attr_paths: &[],
|
||||
},
|
||||
}),
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -89,7 +188,7 @@ impl<'a> VNode<'a> {
|
|||
///
|
||||
/// Returns [`None`] if the root is actually a static node (Element/Text)
|
||||
pub fn dynamic_root(&self, idx: usize) -> Option<&'a DynamicNode<'a>> {
|
||||
match &self.template.roots[idx] {
|
||||
match &self.template.get().roots[idx] {
|
||||
TemplateNode::Element { .. } | TemplateNode::Text { text: _ } => None,
|
||||
TemplateNode::Dynamic { id } | TemplateNode::DynamicText { id } => {
|
||||
Some(&self.dynamic_nodes[*id])
|
||||
|
@ -114,32 +213,85 @@ impl<'a> VNode<'a> {
|
|||
///
|
||||
/// For this to work properly, the [`Template::name`] *must* be unique across your entire project. This can be done via variety of
|
||||
/// ways, with the suggested approach being the unique code location (file, line, col, etc).
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize))]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
|
||||
pub struct Template<'a> {
|
||||
/// The name of the template. This must be unique across your entire program for template diffing to work properly
|
||||
///
|
||||
/// If two templates have the same name, it's likely that Dioxus will panic when diffing.
|
||||
#[cfg_attr(
|
||||
feature = "serialize",
|
||||
serde(deserialize_with = "deserialize_string_leaky")
|
||||
)]
|
||||
pub name: &'a str,
|
||||
|
||||
/// The list of template nodes that make up the template
|
||||
///
|
||||
/// Unlike react, calls to `rsx!` can have multiple roots. This list supports that paradigm.
|
||||
#[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
|
||||
pub roots: &'a [TemplateNode<'a>],
|
||||
|
||||
/// The paths of each node relative to the root of the template.
|
||||
///
|
||||
/// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
|
||||
/// topmost element, not the `roots` field.
|
||||
#[cfg_attr(
|
||||
feature = "serialize",
|
||||
serde(deserialize_with = "deserialize_bytes_leaky")
|
||||
)]
|
||||
pub node_paths: &'a [&'a [u8]],
|
||||
|
||||
/// The paths of each dynamic attribute relative to the root of the template
|
||||
///
|
||||
/// These will be one segment shorter than the path sent to the renderer since those paths are relative to the
|
||||
/// topmost element, not the `roots` field.
|
||||
#[cfg_attr(
|
||||
feature = "serialize",
|
||||
serde(deserialize_with = "deserialize_bytes_leaky")
|
||||
)]
|
||||
pub attr_paths: &'a [&'a [u8]],
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
fn deserialize_string_leaky<'a, 'de, D>(deserializer: D) -> Result<&'a str, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::Deserialize;
|
||||
|
||||
let deserialized = String::deserialize(deserializer)?;
|
||||
Ok(&*Box::leak(deserialized.into_boxed_str()))
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
fn deserialize_bytes_leaky<'a, 'de, D>(deserializer: D) -> Result<&'a [&'a [u8]], D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::Deserialize;
|
||||
|
||||
let deserialized = Vec::<Vec<u8>>::deserialize(deserializer)?;
|
||||
let deserialized = deserialized
|
||||
.into_iter()
|
||||
.map(|v| &*Box::leak(v.into_boxed_slice()))
|
||||
.collect::<Vec<_>>();
|
||||
Ok(&*Box::leak(deserialized.into_boxed_slice()))
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
fn deserialize_leaky<'a, 'de, T: serde::Deserialize<'de>, D>(
|
||||
deserializer: D,
|
||||
) -> Result<&'a [T], D::Error>
|
||||
where
|
||||
T: serde::Deserialize<'de>,
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
use serde::Deserialize;
|
||||
|
||||
let deserialized = Box::<[T]>::deserialize(deserializer)?;
|
||||
Ok(&*Box::leak(deserialized))
|
||||
}
|
||||
|
||||
impl<'a> Template<'a> {
|
||||
/// Is this template worth caching at all, since it's completely runtime?
|
||||
///
|
||||
|
@ -156,7 +308,11 @@ impl<'a> Template<'a> {
|
|||
///
|
||||
/// This can be created at compile time, saving the VirtualDom time when diffing the tree
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Hash, Eq, PartialOrd, Ord)]
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize), serde(tag = "type"))]
|
||||
#[cfg_attr(
|
||||
feature = "serialize",
|
||||
derive(serde::Serialize, serde::Deserialize),
|
||||
serde(tag = "type")
|
||||
)]
|
||||
pub enum TemplateNode<'a> {
|
||||
/// An statically known element in the dom.
|
||||
///
|
||||
|
@ -176,9 +332,11 @@ pub enum TemplateNode<'a> {
|
|||
/// A list of possibly dynamic attribues for this element
|
||||
///
|
||||
/// An attribute on a DOM node, such as `id="my-thing"` or `href="https://example.com"`.
|
||||
#[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
|
||||
attrs: &'a [TemplateAttribute<'a>],
|
||||
|
||||
/// A list of template nodes that define another set of template nodes
|
||||
#[cfg_attr(feature = "serialize", serde(deserialize_with = "deserialize_leaky"))]
|
||||
children: &'a [TemplateNode<'a>],
|
||||
},
|
||||
|
||||
|
@ -527,8 +685,8 @@ impl<'a> IntoDynNode<'a> for &'a VNode<'a> {
|
|||
fn into_vnode(self, _cx: &'a ScopeState) -> DynamicNode<'a> {
|
||||
DynamicNode::Fragment(_cx.bump().alloc([VNode {
|
||||
parent: self.parent,
|
||||
template: self.template,
|
||||
root_ids: self.root_ids,
|
||||
template: self.template.clone(),
|
||||
root_ids: self.root_ids.clone(),
|
||||
key: self.key,
|
||||
dynamic_nodes: self.dynamic_nodes,
|
||||
dynamic_attrs: self.dynamic_attrs,
|
||||
|
|
|
@ -142,7 +142,8 @@ use std::{any::Any, borrow::BorrowMut, cell::Cell, collections::BTreeSet, future
|
|||
/// }
|
||||
/// ```
|
||||
pub struct VirtualDom {
|
||||
pub(crate) templates: FxHashMap<TemplateId, Template<'static>>,
|
||||
// Maps a template path to a map of byteindexes to templates
|
||||
pub(crate) templates: FxHashMap<TemplateId, FxHashMap<usize, Template<'static>>>,
|
||||
pub(crate) scopes: Slab<Box<ScopeState>>,
|
||||
pub(crate) dirty_scopes: BTreeSet<DirtyScope>,
|
||||
pub(crate) scheduler: Rc<Scheduler>,
|
||||
|
@ -354,10 +355,11 @@ impl VirtualDom {
|
|||
while let Some(el_ref) = parent_path {
|
||||
// safety: we maintain references of all vnodes in the element slab
|
||||
let template = unsafe { &*el_ref.template };
|
||||
let node_template = template.template.get();
|
||||
let target_path = el_ref.path;
|
||||
|
||||
for (idx, attr) in template.dynamic_attrs.iter().enumerate() {
|
||||
let this_path = template.template.attr_paths[idx];
|
||||
let this_path = node_template.attr_paths[idx];
|
||||
|
||||
// listeners are required to be prefixed with "on", but they come back to the virtualdom with that missing
|
||||
// we should fix this so that we look for "onclick" instead of "click"
|
||||
|
@ -454,6 +456,30 @@ impl VirtualDom {
|
|||
}
|
||||
}
|
||||
|
||||
/// Replace a template at runtime. This will re-render all components that use this template.
|
||||
/// This is the primitive that enables hot-reloading.
|
||||
///
|
||||
/// The caller must ensure that the template refrences the same dynamic attributes and nodes as the original template.
|
||||
///
|
||||
/// This will only replace the the parent template, not any nested templates.
|
||||
pub fn replace_template(&mut self, template: Template<'static>) {
|
||||
self.register_template_first_byte_index(template);
|
||||
// iterating a slab is very inefficient, but this is a rare operation that will only happen during development so it's fine
|
||||
for (_, scope) in &self.scopes {
|
||||
if let Some(RenderReturn::Sync(Some(sync))) = scope.try_root_node() {
|
||||
if sync.template.get().name.rsplit_once(':').unwrap().0
|
||||
== template.name.rsplit_once(':').unwrap().0
|
||||
{
|
||||
let height = scope.height;
|
||||
self.dirty_scopes.insert(DirtyScope {
|
||||
height,
|
||||
id: scope.id,
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// Performs a *full* rebuild of the virtual dom, returning every edit required to generate the actual dom from scratch.
|
||||
///
|
||||
/// The mutations item expects the RealDom's stack to be the root of the application.
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
use crate::desktop_context::{DesktopContext, UserWindowEvent};
|
||||
use dioxus_core::*;
|
||||
use dioxus_html::HtmlEvent;
|
||||
use futures_channel::mpsc::{unbounded, UnboundedSender};
|
||||
use futures_channel::mpsc::{unbounded, UnboundedReceiver, UnboundedSender};
|
||||
use futures_util::StreamExt;
|
||||
#[cfg(target_os = "ios")]
|
||||
use objc::runtime::Object;
|
||||
|
@ -25,7 +25,10 @@ pub(super) struct DesktopController {
|
|||
pub(super) quit_app_on_close: bool,
|
||||
pub(super) is_ready: Arc<AtomicBool>,
|
||||
pub(super) proxy: EventLoopProxy<UserWindowEvent>,
|
||||
|
||||
pub(super) event_tx: UnboundedSender<serde_json::Value>,
|
||||
#[cfg(debug_assertions)]
|
||||
pub(super) templates_tx: UnboundedSender<Template<'static>>,
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
pub(super) views: Vec<*mut Object>,
|
||||
|
@ -41,6 +44,7 @@ impl DesktopController {
|
|||
) -> Self {
|
||||
let edit_queue = Arc::new(Mutex::new(Vec::new()));
|
||||
let (event_tx, mut event_rx) = unbounded();
|
||||
let (templates_tx, mut templates_rx) = unbounded();
|
||||
let proxy2 = proxy.clone();
|
||||
|
||||
let pending_edits = edit_queue.clone();
|
||||
|
@ -68,6 +72,18 @@ impl DesktopController {
|
|||
|
||||
loop {
|
||||
tokio::select! {
|
||||
template = {
|
||||
#[allow(unused)]
|
||||
fn maybe_future<'a>(templates_rx: &'a mut UnboundedReceiver<Template<'static>>) -> impl Future<Output = dioxus_core::Template<'static>> + 'a {
|
||||
#[cfg(debug_assertions)]
|
||||
return templates_rx.select_next_some();
|
||||
#[cfg(not(debug_assertions))]
|
||||
return std::future::pending();
|
||||
}
|
||||
maybe_future(&mut templates_rx)
|
||||
} => {
|
||||
dom.replace_template(template);
|
||||
}
|
||||
_ = dom.wait_for_work() => {}
|
||||
Some(json_value) = event_rx.next() => {
|
||||
if let Ok(value) = serde_json::from_value::<HtmlEvent>(json_value) {
|
||||
|
@ -103,6 +119,8 @@ impl DesktopController {
|
|||
quit_app_on_close: true,
|
||||
proxy: proxy2,
|
||||
event_tx,
|
||||
#[cfg(debug_assertions)]
|
||||
templates_tx,
|
||||
#[cfg(target_os = "ios")]
|
||||
views: vec![],
|
||||
}
|
||||
|
@ -133,8 +151,4 @@ impl DesktopController {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn set_template(&self, _serialized_template: String) {
|
||||
todo!("hot reloading currently WIP")
|
||||
}
|
||||
}
|
||||
|
|
|
@ -179,11 +179,6 @@ pub enum UserWindowEvent {
|
|||
DragWindow,
|
||||
FocusWindow,
|
||||
|
||||
/// Set a new Dioxus template for hot-reloading
|
||||
///
|
||||
/// Is a no-op in release builds. Must fit the right format for templates
|
||||
SetTemplate(String),
|
||||
|
||||
Visible(bool),
|
||||
Minimize(bool),
|
||||
Maximize(bool),
|
||||
|
@ -232,7 +227,6 @@ impl DesktopController {
|
|||
|
||||
match user_event {
|
||||
Initialize | EditsReady => self.try_load_ready_webviews(),
|
||||
SetTemplate(template) => self.set_template(template),
|
||||
CloseWindow => *control_flow = ControlFlow::Exit,
|
||||
DragWindow => {
|
||||
// if the drag_window has any errors, we don't do anything
|
||||
|
|
|
@ -1,6 +1,6 @@
|
|||
#![allow(dead_code)]
|
||||
|
||||
use dioxus_core::VirtualDom;
|
||||
use dioxus_core::Template;
|
||||
|
||||
use interprocess::local_socket::{LocalSocketListener, LocalSocketStream};
|
||||
use std::io::{BufRead, BufReader};
|
||||
|
@ -13,7 +13,7 @@ fn handle_error(connection: std::io::Result<LocalSocketStream>) -> Option<LocalS
|
|||
.ok()
|
||||
}
|
||||
|
||||
pub(crate) fn init(_dom: &VirtualDom) {
|
||||
pub(crate) fn init(proxy: futures_channel::mpsc::UnboundedSender<Template<'static>>) {
|
||||
let latest_in_connection: Arc<Mutex<Option<BufReader<LocalSocketStream>>>> =
|
||||
Arc::new(Mutex::new(None));
|
||||
|
||||
|
@ -36,11 +36,9 @@ pub(crate) fn init(_dom: &VirtualDom) {
|
|||
let mut buf = String::new();
|
||||
match conn.read_line(&mut buf) {
|
||||
Ok(_) => {
|
||||
todo!()
|
||||
// let msg: SetTemplateMsg = serde_json::from_str(&buf).unwrap();
|
||||
// channel
|
||||
// .start_send(SchedulerMsg::SetTemplate(Box::new(msg)))
|
||||
// .unwrap();
|
||||
let msg: Template<'static> =
|
||||
serde_json::from_str(Box::leak(buf.into_boxed_str())).unwrap();
|
||||
proxy.unbounded_send(msg).unwrap();
|
||||
}
|
||||
Err(err) => {
|
||||
if err.kind() != std::io::ErrorKind::WouldBlock {
|
||||
|
|
|
@ -106,6 +106,9 @@ pub fn launch_with_props<P: 'static + Send>(root: Component<P>, props: P, mut cf
|
|||
let event_loop = EventLoop::with_user_event();
|
||||
let mut desktop = DesktopController::new_on_tokio(root, props, event_loop.create_proxy());
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
hot_reload::init(desktop.templates_tx.clone());
|
||||
|
||||
event_loop.run(move |window_event, event_loop, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
|
|
|
@ -42,7 +42,7 @@ fn create_rows(c: &mut Criterion) {
|
|||
|
||||
c.bench_function("create rows", |b| {
|
||||
let mut dom = VirtualDom::new(app);
|
||||
dom.rebuild();
|
||||
let _ = dom.rebuild();
|
||||
|
||||
b.iter(|| {
|
||||
let g = dom.rebuild();
|
||||
|
|
|
@ -12,6 +12,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
|||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "../core", version = "^0.2.1" }
|
||||
dioxus-rsx = { path = "../rsx", optional = true }
|
||||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
serde_repr = { version = "0.1", optional = true }
|
||||
wasm-bindgen = { version = "0.2.79", optional = true }
|
||||
|
@ -47,3 +48,4 @@ serde_json = "*"
|
|||
default = ["serialize"]
|
||||
serialize = ["serde", "serde_repr", "euclid/serde", "keyboard-types/serde", "dioxus-core/serialize"]
|
||||
wasm-bind = ["web-sys", "wasm-bindgen"]
|
||||
hot-reload-context = ["dioxus-rsx"]
|
File diff suppressed because it is too large
Load diff
|
@ -1,4 +1,4 @@
|
|||
use std::{collections::HashMap, fmt::Debug, sync::Arc};
|
||||
use std::{collections::HashMap, fmt::Debug};
|
||||
|
||||
use dioxus_core::Event;
|
||||
|
||||
|
@ -13,7 +13,7 @@ pub struct FormData {
|
|||
pub values: HashMap<String, String>,
|
||||
|
||||
#[cfg_attr(feature = "serialize", serde(skip))]
|
||||
pub files: Option<Arc<dyn FileEngine>>,
|
||||
pub files: Option<std::sync::Arc<dyn FileEngine>>,
|
||||
}
|
||||
|
||||
impl PartialEq for FormData {
|
||||
|
|
|
@ -2,12 +2,43 @@
|
|||
|
||||
use crate::AttributeDiscription;
|
||||
|
||||
#[cfg(feature = "hot-reload-context")]
|
||||
macro_rules! trait_method_mapping {
|
||||
(
|
||||
$matching:ident;
|
||||
$(#[$attr:meta])*
|
||||
$name:ident;
|
||||
) => {
|
||||
if $matching == stringify!($name) {
|
||||
return Some((stringify!($name), None));
|
||||
}
|
||||
};
|
||||
(
|
||||
$matching:ident;
|
||||
$(#[$attr:meta])*
|
||||
$name:ident: $lit:literal;
|
||||
) => {
|
||||
if $matching == stringify!($name) {
|
||||
return Some(($lit, None));
|
||||
}
|
||||
};
|
||||
(
|
||||
$matching:ident;
|
||||
$(#[$attr:meta])*
|
||||
$name:ident: $lit:literal, $ns:literal;
|
||||
) => {
|
||||
if $matching == stringify!($name) {
|
||||
return Some(($lit, Some($ns)));
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! trait_methods {
|
||||
(
|
||||
@base
|
||||
|
||||
$(#[$trait_attr:meta])*
|
||||
$trait:ident;
|
||||
$fn:ident;
|
||||
$(
|
||||
$(#[$attr:meta])*
|
||||
$name:ident $(: $($arg:literal),*)*;
|
||||
|
@ -20,6 +51,17 @@ macro_rules! trait_methods {
|
|||
const $name: AttributeDiscription = trait_methods! { $name $(: $($arg),*)*; };
|
||||
)*
|
||||
}
|
||||
|
||||
#[cfg(feature = "hot-reload-context")]
|
||||
pub(crate) fn $fn(attr: &str) -> Option<(&'static str, Option<&'static str>)> {
|
||||
$(
|
||||
trait_method_mapping! {
|
||||
attr;
|
||||
$name$(: $($arg),*)*;
|
||||
}
|
||||
)*
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
// Rename the incoming ident and apply a custom namespace
|
||||
|
@ -36,6 +78,7 @@ trait_methods! {
|
|||
@base
|
||||
|
||||
GlobalAttributes;
|
||||
map_global_attributes;
|
||||
|
||||
/// Prevent the default action for this element.
|
||||
///
|
||||
|
@ -1545,8 +1588,8 @@ trait_methods! {
|
|||
|
||||
trait_methods! {
|
||||
@base
|
||||
|
||||
SvgAttributes;
|
||||
map_svg_attributes;
|
||||
|
||||
/// Prevent the default action for this element.
|
||||
///
|
||||
|
@ -1554,7 +1597,6 @@ trait_methods! {
|
|||
/// <https://developer.mozilla.org/en-US/docs/Web/API/Event/preventDefault>
|
||||
prevent_default: "dioxus-prevent-default";
|
||||
|
||||
|
||||
/// <https://developer.mozilla.org/en-US/docs/Web/SVG/Attribute/accent-height>
|
||||
accent_height: "accent-height";
|
||||
|
||||
|
|
|
@ -14,6 +14,8 @@
|
|||
//! Currently, we don't validate for structures, but do validate attributes.
|
||||
|
||||
mod elements;
|
||||
#[cfg(feature = "hot-reload-context")]
|
||||
pub use elements::HtmlCtx;
|
||||
pub mod events;
|
||||
pub mod geometry;
|
||||
mod global_attributes;
|
||||
|
|
|
@ -174,7 +174,7 @@ impl<S: State> RealDom<S> {
|
|||
use dioxus_core::Mutation::*;
|
||||
match e {
|
||||
AppendChildren { id, m } => {
|
||||
let children = self.stack.split_off(m);
|
||||
let children = self.stack.split_off(self.stack.len() - m);
|
||||
let parent = self.element_to_node_id(id);
|
||||
for child in children {
|
||||
self.add_child(parent, child);
|
||||
|
@ -222,10 +222,10 @@ impl<S: State> RealDom<S> {
|
|||
self.stack.push(clone_id);
|
||||
}
|
||||
ReplaceWith { id, m } => {
|
||||
let new_nodes = self.stack.split_off(m);
|
||||
let new_nodes = self.stack.split_off(self.stack.len() - m);
|
||||
let old_node_id = self.element_to_node_id(id);
|
||||
for new in new_nodes {
|
||||
self.tree.insert_after(old_node_id, new);
|
||||
self.tree.insert_before(old_node_id, new);
|
||||
mark_dirty(new, NodeMask::ALL, &mut nodes_updated);
|
||||
}
|
||||
self.tree.remove(old_node_id);
|
||||
|
@ -234,21 +234,21 @@ impl<S: State> RealDom<S> {
|
|||
let new_nodes = self.stack.split_off(self.stack.len() - m);
|
||||
let old_node_id = self.load_child(path);
|
||||
for new in new_nodes {
|
||||
self.tree.insert_after(old_node_id, new);
|
||||
self.tree.insert_before(old_node_id, new);
|
||||
mark_dirty(new, NodeMask::ALL, &mut nodes_updated);
|
||||
}
|
||||
self.tree.remove(old_node_id);
|
||||
}
|
||||
InsertAfter { id, m } => {
|
||||
let new_nodes = self.stack.split_off(m);
|
||||
let new_nodes = self.stack.split_off(self.stack.len() - m);
|
||||
let old_node_id = self.element_to_node_id(id);
|
||||
for new in new_nodes {
|
||||
for new in new_nodes.into_iter().rev() {
|
||||
self.tree.insert_after(old_node_id, new);
|
||||
mark_dirty(new, NodeMask::ALL, &mut nodes_updated);
|
||||
}
|
||||
}
|
||||
InsertBefore { id, m } => {
|
||||
let new_nodes = self.stack.split_off(m);
|
||||
let new_nodes = self.stack.split_off(self.stack.len() - m);
|
||||
let old_node_id = self.element_to_node_id(id);
|
||||
for new in new_nodes {
|
||||
self.tree.insert_before(old_node_id, new);
|
||||
|
|
|
@ -73,11 +73,11 @@ impl PersistantElementIter {
|
|||
.edits
|
||||
.iter()
|
||||
.filter_map(|m| {
|
||||
// nodes within templates will never be removedns
|
||||
if let Mutation::Remove { id } = m {
|
||||
Some(rdom.element_to_node_id(*id))
|
||||
} else {
|
||||
None
|
||||
// nodes within templates will never be removed
|
||||
match m {
|
||||
Mutation::Remove { id } => Some(rdom.element_to_node_id(*id)),
|
||||
Mutation::ReplaceWith { id, .. } => Some(rdom.element_to_node_id(*id)),
|
||||
_ => None,
|
||||
}
|
||||
})
|
||||
.collect();
|
||||
|
@ -358,12 +358,16 @@ fn persist_removes() {
|
|||
let mut rdom: RealDom<Empty> = RealDom::new();
|
||||
|
||||
let build = vdom.rebuild();
|
||||
println!("{:#?}", build);
|
||||
let _to_update = rdom.apply_mutations(build);
|
||||
|
||||
// this will end on the node that is removed
|
||||
let mut iter1 = PersistantElementIter::new();
|
||||
// this will end on the after node that is removed
|
||||
let mut iter2 = PersistantElementIter::new();
|
||||
// root
|
||||
iter1.next(&rdom).id();
|
||||
iter2.next(&rdom).id();
|
||||
// div
|
||||
iter1.next(&rdom).id();
|
||||
iter2.next(&rdom).id();
|
||||
|
@ -386,27 +390,24 @@ fn persist_removes() {
|
|||
|
||||
vdom.mark_dirty(ScopeId(0));
|
||||
let update = vdom.render_immediate();
|
||||
println!("{:#?}", update);
|
||||
iter1.prune(&update, &rdom);
|
||||
iter2.prune(&update, &rdom);
|
||||
let _to_update = rdom.apply_mutations(update);
|
||||
|
||||
let p_tag = "1".to_string();
|
||||
let root_tag = "Root".to_string();
|
||||
let idx = iter1.next(&rdom).id();
|
||||
dbg!(&rdom[idx].node_data.node_type);
|
||||
assert!(matches!(
|
||||
&rdom[idx].node_data.node_type,
|
||||
NodeType::Element { tag: p_tag, .. }
|
||||
NodeType::Element { tag: root_tag, .. }
|
||||
));
|
||||
let text = "2".to_string();
|
||||
let idx = iter1.next(&rdom).id();
|
||||
assert!(matches!(
|
||||
&rdom[idx].node_data.node_type,
|
||||
NodeType::Text { text, .. }
|
||||
));
|
||||
let div_tag = "div".to_string();
|
||||
|
||||
let idx = iter2.next(&rdom).id();
|
||||
dbg!(&rdom[idx].node_data.node_type);
|
||||
assert!(matches!(
|
||||
&rdom[idx].node_data.node_type,
|
||||
NodeType::Element { tag: div_tag, .. }
|
||||
NodeType::Element { tag: root_tag, .. }
|
||||
));
|
||||
}
|
||||
|
||||
|
|
|
@ -13,6 +13,8 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
|||
|
||||
[dependencies]
|
||||
proc-macro2 = { version = "1.0", features = ["span-locations"] }
|
||||
dioxus-core = { path = "../core" }
|
||||
syn = { version = "1.0", features = ["full", "extra-traits"] }
|
||||
quote = { version = "1.0" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
internment = "0.7.0"
|
|
@ -1,892 +0,0 @@
|
|||
use crate::elements::*;
|
||||
|
||||
// map the rsx name of the attribute to the html name of the attribute, the namespace that contains it, and if the attribute is volitile
|
||||
pub fn attrbute_to_static_str(
|
||||
attr: &str,
|
||||
element: &'static str,
|
||||
namespace: Option<&'static str>,
|
||||
) -> Option<(&'static str, Option<&'static str>, bool)> {
|
||||
if namespace == Some("http://www.w3.org/2000/svg") {
|
||||
svg::MAPPED_ATTRIBUTES
|
||||
.iter()
|
||||
.find(|(a, _)| *a == attr)
|
||||
.map(|(_, b)| (*b, None, false))
|
||||
} else {
|
||||
NO_NAMESPACE_ATTRIBUTES
|
||||
.iter()
|
||||
.find(|&a| *a == attr)
|
||||
.map(|a| (*a, None, false))
|
||||
.or_else(|| {
|
||||
STYLE_ATTRIBUTES
|
||||
.iter()
|
||||
.find(|(a, _)| *a == attr)
|
||||
.map(|(_, b)| (*b, Some("style"), false))
|
||||
})
|
||||
.or_else(|| {
|
||||
MAPPED_ATTRIBUTES
|
||||
.iter()
|
||||
.find(|(a, _)| *a == attr)
|
||||
.map(|(_, b)| (*b, None, false))
|
||||
})
|
||||
}
|
||||
.or_else(|| {
|
||||
ELEMENTS_WITH_MAPPED_ATTRIBUTES
|
||||
.iter()
|
||||
.find_map(|(el, attrs)| {
|
||||
(element == *el)
|
||||
.then(|| {
|
||||
attrs
|
||||
.iter()
|
||||
.find(|(a, _, _)| *a == attr)
|
||||
.map(|(_, b, volitile)| (*b, None, *volitile))
|
||||
})
|
||||
.flatten()
|
||||
})
|
||||
})
|
||||
.or_else(|| {
|
||||
ELEMENTS_WITH_NAMESPACE.iter().find_map(|(el, ns, attrs)| {
|
||||
(element == *el && namespace == Some(*ns))
|
||||
.then(|| {
|
||||
attrs
|
||||
.iter()
|
||||
.find(|a| **a == attr)
|
||||
.map(|a| (*a, None, false))
|
||||
})
|
||||
.flatten()
|
||||
})
|
||||
})
|
||||
.or_else(|| {
|
||||
ELEMENTS_WITHOUT_NAMESPACE.iter().find_map(|(el, attrs)| {
|
||||
(element == *el)
|
||||
.then(|| {
|
||||
attrs
|
||||
.iter()
|
||||
.find(|a| **a == attr)
|
||||
.map(|a| (*a, None, false))
|
||||
})
|
||||
.flatten()
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
macro_rules! no_namespace_trait_methods {
|
||||
(
|
||||
$(
|
||||
$(#[$attr:meta])*
|
||||
$name:ident;
|
||||
)*
|
||||
) => {
|
||||
pub const NO_NAMESPACE_ATTRIBUTES: &'static [&'static str] = &[
|
||||
$(
|
||||
stringify!($name),
|
||||
)*
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! style_trait_methods {
|
||||
(
|
||||
$(
|
||||
$(#[$attr:meta])*
|
||||
$name:ident: $lit:literal,
|
||||
)*
|
||||
) => {
|
||||
pub const STYLE_ATTRIBUTES: &'static [(&'static str, &'static str)] = &[
|
||||
$(
|
||||
(stringify!($name), $lit),
|
||||
)*
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
macro_rules! mapped_trait_methods {
|
||||
(
|
||||
$(
|
||||
$(#[$attr:meta])*
|
||||
$name:ident: $lit:literal,
|
||||
)*
|
||||
) => {
|
||||
pub const MAPPED_ATTRIBUTES: &'static [(&'static str, &'static str)] = &[
|
||||
$(
|
||||
(stringify!($name), $lit),
|
||||
)*
|
||||
("prevent_default", "dioxus-prevent-default"),
|
||||
];
|
||||
};
|
||||
}
|
||||
|
||||
no_namespace_trait_methods! {
|
||||
accesskey;
|
||||
|
||||
/// The HTML class attribute is used to specify a class for an HTML element.
|
||||
///
|
||||
/// ## Details
|
||||
/// Multiple HTML elements can share the same class.
|
||||
///
|
||||
/// The class global attribute is a space-separated list of the case-sensitive classes of the element.
|
||||
/// Classes allow CSS and Javascript to select and access specific elements via the class selectors or
|
||||
/// functions like the DOM method document.getElementsByClassName.
|
||||
///
|
||||
/// ## Example
|
||||
///
|
||||
/// ### HTML:
|
||||
/// ```html
|
||||
/// <p class="note editorial">Above point sounds a bit obvious. Remove/rewrite?</p>
|
||||
/// ```
|
||||
///
|
||||
/// ### CSS:
|
||||
/// ```css
|
||||
/// .note {
|
||||
/// font-style: italic;
|
||||
/// font-weight: bold;
|
||||
/// }
|
||||
///
|
||||
/// .editorial {
|
||||
/// background: rgb(255, 0, 0, .25);
|
||||
/// padding: 10px;
|
||||
/// }
|
||||
/// ```
|
||||
class;
|
||||
contenteditable;
|
||||
data;
|
||||
dir;
|
||||
draggable;
|
||||
hidden;
|
||||
id;
|
||||
lang;
|
||||
spellcheck;
|
||||
style;
|
||||
tabindex;
|
||||
title;
|
||||
translate;
|
||||
|
||||
role;
|
||||
|
||||
/// dangerous_inner_html is Dioxus's replacement for using innerHTML in the browser DOM. In general, setting
|
||||
/// HTML from code is risky because it’s easy to inadvertently expose your users to a cross-site scripting (XSS)
|
||||
/// attack. So, you can set HTML directly from Dioxus, but you have to type out dangerous_inner_html to remind
|
||||
/// yourself that it’s dangerous
|
||||
dangerous_inner_html;
|
||||
}
|
||||
|
||||
// This macro creates an explicit method call for each of the style attributes.
|
||||
//
|
||||
// The left token specifies the name of the attribute in the rsx! macro, and the right string literal specifies the
|
||||
// actual name of the attribute generated.
|
||||
//
|
||||
// This roughly follows the html spec
|
||||
style_trait_methods! {
|
||||
align_content: "align-content",
|
||||
align_items: "align-items",
|
||||
align_self: "align-self",
|
||||
alignment_adjust: "alignment-adjust",
|
||||
alignment_baseline: "alignment-baseline",
|
||||
all: "all",
|
||||
alt: "alt",
|
||||
animation: "animation",
|
||||
animation_delay: "animation-delay",
|
||||
animation_direction: "animation-direction",
|
||||
animation_duration: "animation-duration",
|
||||
animation_fill_mode: "animation-fill-mode",
|
||||
animation_iteration_count: "animation-iteration-count",
|
||||
animation_name: "animation-name",
|
||||
animation_play_state: "animation-play-state",
|
||||
animation_timing_function: "animation-timing-function",
|
||||
azimuth: "azimuth",
|
||||
backface_visibility: "backface-visibility",
|
||||
background: "background",
|
||||
background_attachment: "background-attachment",
|
||||
background_clip: "background-clip",
|
||||
background_color: "background-color",
|
||||
background_image: "background-image",
|
||||
background_origin: "background-origin",
|
||||
background_position: "background-position",
|
||||
background_repeat: "background-repeat",
|
||||
background_size: "background-size",
|
||||
background_blend_mode: "background-blend-mode",
|
||||
baseline_shift: "baseline-shift",
|
||||
bleed: "bleed",
|
||||
bookmark_label: "bookmark-label",
|
||||
bookmark_level: "bookmark-level",
|
||||
bookmark_state: "bookmark-state",
|
||||
border: "border",
|
||||
border_color: "border-color",
|
||||
border_style: "border-style",
|
||||
border_width: "border-width",
|
||||
border_bottom: "border-bottom",
|
||||
border_bottom_color: "border-bottom-color",
|
||||
border_bottom_style: "border-bottom-style",
|
||||
border_bottom_width: "border-bottom-width",
|
||||
border_left: "border-left",
|
||||
border_left_color: "border-left-color",
|
||||
border_left_style: "border-left-style",
|
||||
border_left_width: "border-left-width",
|
||||
border_right: "border-right",
|
||||
border_right_color: "border-right-color",
|
||||
border_right_style: "border-right-style",
|
||||
border_right_width: "border-right-width",
|
||||
border_top: "border-top",
|
||||
border_top_color: "border-top-color",
|
||||
border_top_style: "border-top-style",
|
||||
border_top_width: "border-top-width",
|
||||
border_collapse: "border-collapse",
|
||||
border_image: "border-image",
|
||||
border_image_outset: "border-image-outset",
|
||||
border_image_repeat: "border-image-repeat",
|
||||
border_image_slice: "border-image-slice",
|
||||
border_image_source: "border-image-source",
|
||||
border_image_width: "border-image-width",
|
||||
border_radius: "border-radius",
|
||||
border_bottom_left_radius: "border-bottom-left-radius",
|
||||
border_bottom_right_radius: "border-bottom-right-radius",
|
||||
border_top_left_radius: "border-top-left-radius",
|
||||
border_top_right_radius: "border-top-right-radius",
|
||||
border_spacing: "border-spacing",
|
||||
bottom: "bottom",
|
||||
box_decoration_break: "box-decoration-break",
|
||||
box_shadow: "box-shadow",
|
||||
box_sizing: "box-sizing",
|
||||
box_snap: "box-snap",
|
||||
break_after: "break-after",
|
||||
break_before: "break-before",
|
||||
break_inside: "break-inside",
|
||||
buffered_rendering: "buffered-rendering",
|
||||
caption_side: "caption-side",
|
||||
clear: "clear",
|
||||
clear_side: "clear-side",
|
||||
clip: "clip",
|
||||
clip_path: "clip-path",
|
||||
clip_rule: "clip-rule",
|
||||
color: "color",
|
||||
color_adjust: "color-adjust",
|
||||
color_correction: "color-correction",
|
||||
color_interpolation: "color-interpolation",
|
||||
color_interpolation_filters: "color-interpolation-filters",
|
||||
color_profile: "color-profile",
|
||||
color_rendering: "color-rendering",
|
||||
column_fill: "column-fill",
|
||||
column_gap: "column-gap",
|
||||
column_rule: "column-rule",
|
||||
column_rule_color: "column-rule-color",
|
||||
column_rule_style: "column-rule-style",
|
||||
column_rule_width: "column-rule-width",
|
||||
column_span: "column-span",
|
||||
columns: "columns",
|
||||
column_count: "column-count",
|
||||
column_width: "column-width",
|
||||
contain: "contain",
|
||||
content: "content",
|
||||
counter_increment: "counter-increment",
|
||||
counter_reset: "counter-reset",
|
||||
counter_set: "counter-set",
|
||||
cue: "cue",
|
||||
cue_after: "cue-after",
|
||||
cue_before: "cue-before",
|
||||
cursor: "cursor",
|
||||
direction: "direction",
|
||||
display: "display",
|
||||
display_inside: "display-inside",
|
||||
display_outside: "display-outside",
|
||||
display_extras: "display-extras",
|
||||
display_box: "display-box",
|
||||
dominant_baseline: "dominant-baseline",
|
||||
elevation: "elevation",
|
||||
empty_cells: "empty-cells",
|
||||
enable_background: "enable-background",
|
||||
fill: "fill",
|
||||
fill_opacity: "fill-opacity",
|
||||
fill_rule: "fill-rule",
|
||||
filter: "filter",
|
||||
float: "float",
|
||||
float_defer_column: "float-defer-column",
|
||||
float_defer_page: "float-defer-page",
|
||||
float_offset: "float-offset",
|
||||
float_wrap: "float-wrap",
|
||||
flow_into: "flow-into",
|
||||
flow_from: "flow-from",
|
||||
flex: "flex",
|
||||
flex_basis: "flex-basis",
|
||||
flex_grow: "flex-grow",
|
||||
flex_shrink: "flex-shrink",
|
||||
flex_flow: "flex-flow",
|
||||
flex_direction: "flex-direction",
|
||||
flex_wrap: "flex-wrap",
|
||||
flood_color: "flood-color",
|
||||
flood_opacity: "flood-opacity",
|
||||
font: "font",
|
||||
font_family: "font-family",
|
||||
font_size: "font-size",
|
||||
font_stretch: "font-stretch",
|
||||
font_style: "font-style",
|
||||
font_weight: "font-weight",
|
||||
font_feature_settings: "font-feature-settings",
|
||||
font_kerning: "font-kerning",
|
||||
font_language_override: "font-language-override",
|
||||
font_size_adjust: "font-size-adjust",
|
||||
font_synthesis: "font-synthesis",
|
||||
font_variant: "font-variant",
|
||||
font_variant_alternates: "font-variant-alternates",
|
||||
font_variant_caps: "font-variant-caps",
|
||||
font_variant_east_asian: "font-variant-east-asian",
|
||||
font_variant_ligatures: "font-variant-ligatures",
|
||||
font_variant_numeric: "font-variant-numeric",
|
||||
font_variant_position: "font-variant-position",
|
||||
footnote_policy: "footnote-policy",
|
||||
glyph_orientation_horizontal: "glyph-orientation-horizontal",
|
||||
glyph_orientation_vertical: "glyph-orientation-vertical",
|
||||
grid: "grid",
|
||||
grid_auto_flow: "grid-auto-flow",
|
||||
grid_auto_columns: "grid-auto-columns",
|
||||
grid_auto_rows: "grid-auto-rows",
|
||||
grid_template: "grid-template",
|
||||
grid_template_areas: "grid-template-areas",
|
||||
grid_template_columns: "grid-template-columns",
|
||||
grid_template_rows: "grid-template-rows",
|
||||
grid_area: "grid-area",
|
||||
grid_column: "grid-column",
|
||||
grid_column_start: "grid-column-start",
|
||||
grid_column_end: "grid-column-end",
|
||||
grid_row: "grid-row",
|
||||
grid_row_start: "grid-row-start",
|
||||
grid_row_end: "grid-row-end",
|
||||
hanging_punctuation: "hanging-punctuation",
|
||||
height: "height",
|
||||
hyphenate_character: "hyphenate-character",
|
||||
hyphenate_limit_chars: "hyphenate-limit-chars",
|
||||
hyphenate_limit_last: "hyphenate-limit-last",
|
||||
hyphenate_limit_lines: "hyphenate-limit-lines",
|
||||
hyphenate_limit_zone: "hyphenate-limit-zone",
|
||||
hyphens: "hyphens",
|
||||
icon: "icon",
|
||||
image_orientation: "image-orientation",
|
||||
image_resolution: "image-resolution",
|
||||
image_rendering: "image-rendering",
|
||||
ime: "ime",
|
||||
ime_align: "ime-align",
|
||||
ime_mode: "ime-mode",
|
||||
ime_offset: "ime-offset",
|
||||
ime_width: "ime-width",
|
||||
initial_letters: "initial-letters",
|
||||
inline_box_align: "inline-box-align",
|
||||
isolation: "isolation",
|
||||
justify_content: "justify-content",
|
||||
justify_items: "justify-items",
|
||||
justify_self: "justify-self",
|
||||
kerning: "kerning",
|
||||
left: "left",
|
||||
letter_spacing: "letter-spacing",
|
||||
lighting_color: "lighting-color",
|
||||
line_box_contain: "line-box-contain",
|
||||
line_break: "line-break",
|
||||
line_grid: "line-grid",
|
||||
line_height: "line-height",
|
||||
line_slack: "line-slack",
|
||||
line_snap: "line-snap",
|
||||
list_style: "list-style",
|
||||
list_style_image: "list-style-image",
|
||||
list_style_position: "list-style-position",
|
||||
list_style_type: "list-style-type",
|
||||
margin: "margin",
|
||||
margin_bottom: "margin-bottom",
|
||||
margin_left: "margin-left",
|
||||
margin_right: "margin-right",
|
||||
margin_top: "margin-top",
|
||||
marker: "marker",
|
||||
marker_end: "marker-end",
|
||||
marker_mid: "marker-mid",
|
||||
marker_pattern: "marker-pattern",
|
||||
marker_segment: "marker-segment",
|
||||
marker_start: "marker-start",
|
||||
marker_knockout_left: "marker-knockout-left",
|
||||
marker_knockout_right: "marker-knockout-right",
|
||||
marker_side: "marker-side",
|
||||
marks: "marks",
|
||||
marquee_direction: "marquee-direction",
|
||||
marquee_play_count: "marquee-play-count",
|
||||
marquee_speed: "marquee-speed",
|
||||
marquee_style: "marquee-style",
|
||||
mask: "mask",
|
||||
mask_image: "mask-image",
|
||||
mask_repeat: "mask-repeat",
|
||||
mask_position: "mask-position",
|
||||
mask_clip: "mask-clip",
|
||||
mask_origin: "mask-origin",
|
||||
mask_size: "mask-size",
|
||||
mask_box: "mask-box",
|
||||
mask_box_outset: "mask-box-outset",
|
||||
mask_box_repeat: "mask-box-repeat",
|
||||
mask_box_slice: "mask-box-slice",
|
||||
mask_box_source: "mask-box-source",
|
||||
mask_box_width: "mask-box-width",
|
||||
mask_type: "mask-type",
|
||||
max_height: "max-height",
|
||||
max_lines: "max-lines",
|
||||
max_width: "max-width",
|
||||
min_height: "min-height",
|
||||
min_width: "min-width",
|
||||
mix_blend_mode: "mix-blend-mode",
|
||||
nav_down: "nav-down",
|
||||
nav_index: "nav-index",
|
||||
nav_left: "nav-left",
|
||||
nav_right: "nav-right",
|
||||
nav_up: "nav-up",
|
||||
object_fit: "object-fit",
|
||||
object_position: "object-position",
|
||||
offset_after: "offset-after",
|
||||
offset_before: "offset-before",
|
||||
offset_end: "offset-end",
|
||||
offset_start: "offset-start",
|
||||
opacity: "opacity",
|
||||
order: "order",
|
||||
orphans: "orphans",
|
||||
outline: "outline",
|
||||
outline_color: "outline-color",
|
||||
outline_style: "outline-style",
|
||||
outline_width: "outline-width",
|
||||
outline_offset: "outline-offset",
|
||||
overflow: "overflow",
|
||||
overflow_x: "overflow-x",
|
||||
overflow_y: "overflow-y",
|
||||
overflow_style: "overflow-style",
|
||||
overflow_wrap: "overflow-wrap",
|
||||
padding: "padding",
|
||||
padding_bottom: "padding-bottom",
|
||||
padding_left: "padding-left",
|
||||
padding_right: "padding-right",
|
||||
padding_top: "padding-top",
|
||||
page: "page",
|
||||
page_break_after: "page-break-after",
|
||||
page_break_before: "page-break-before",
|
||||
page_break_inside: "page-break-inside",
|
||||
paint_order: "paint-order",
|
||||
pause: "pause",
|
||||
pause_after: "pause-after",
|
||||
pause_before: "pause-before",
|
||||
perspective: "perspective",
|
||||
perspective_origin: "perspective-origin",
|
||||
pitch: "pitch",
|
||||
pitch_range: "pitch-range",
|
||||
play_during: "play-during",
|
||||
pointer_events: "pointer-events",
|
||||
position: "position",
|
||||
quotes: "quotes",
|
||||
region_fragment: "region-fragment",
|
||||
resize: "resize",
|
||||
rest: "rest",
|
||||
rest_after: "rest-after",
|
||||
rest_before: "rest-before",
|
||||
richness: "richness",
|
||||
right: "right",
|
||||
ruby_align: "ruby-align",
|
||||
ruby_merge: "ruby-merge",
|
||||
ruby_position: "ruby-position",
|
||||
scroll_behavior: "scroll-behavior",
|
||||
scroll_snap_coordinate: "scroll-snap-coordinate",
|
||||
scroll_snap_destination: "scroll-snap-destination",
|
||||
scroll_snap_points_x: "scroll-snap-points-x",
|
||||
scroll_snap_points_y: "scroll-snap-points-y",
|
||||
scroll_snap_type: "scroll-snap-type",
|
||||
shape_image_threshold: "shape-image-threshold",
|
||||
shape_inside: "shape-inside",
|
||||
shape_margin: "shape-margin",
|
||||
shape_outside: "shape-outside",
|
||||
shape_padding: "shape-padding",
|
||||
shape_rendering: "shape-rendering",
|
||||
size: "size",
|
||||
speak: "speak",
|
||||
speak_as: "speak-as",
|
||||
speak_header: "speak-header",
|
||||
speak_numeral: "speak-numeral",
|
||||
speak_punctuation: "speak-punctuation",
|
||||
speech_rate: "speech-rate",
|
||||
stop_color: "stop-color",
|
||||
stop_opacity: "stop-opacity",
|
||||
stress: "stress",
|
||||
string_set: "string-set",
|
||||
stroke: "stroke",
|
||||
stroke_dasharray: "stroke-dasharray",
|
||||
stroke_dashoffset: "stroke-dashoffset",
|
||||
stroke_linecap: "stroke-linecap",
|
||||
stroke_linejoin: "stroke-linejoin",
|
||||
stroke_miterlimit: "stroke-miterlimit",
|
||||
stroke_opacity: "stroke-opacity",
|
||||
stroke_width: "stroke-width",
|
||||
tab_size: "tab-size",
|
||||
table_layout: "table-layout",
|
||||
text_align: "text-align",
|
||||
text_align_all: "text-align-all",
|
||||
text_align_last: "text-align-last",
|
||||
text_anchor: "text-anchor",
|
||||
text_combine_upright: "text-combine-upright",
|
||||
text_decoration: "text-decoration",
|
||||
text_decoration_color: "text-decoration-color",
|
||||
text_decoration_line: "text-decoration-line",
|
||||
text_decoration_style: "text-decoration-style",
|
||||
text_decoration_skip: "text-decoration-skip",
|
||||
text_emphasis: "text-emphasis",
|
||||
text_emphasis_color: "text-emphasis-color",
|
||||
text_emphasis_style: "text-emphasis-style",
|
||||
text_emphasis_position: "text-emphasis-position",
|
||||
text_emphasis_skip: "text-emphasis-skip",
|
||||
text_height: "text-height",
|
||||
text_indent: "text-indent",
|
||||
text_justify: "text-justify",
|
||||
text_orientation: "text-orientation",
|
||||
text_overflow: "text-overflow",
|
||||
text_rendering: "text-rendering",
|
||||
text_shadow: "text-shadow",
|
||||
text_size_adjust: "text-size-adjust",
|
||||
text_space_collapse: "text-space-collapse",
|
||||
text_spacing: "text-spacing",
|
||||
text_transform: "text-transform",
|
||||
text_underline_position: "text-underline-position",
|
||||
text_wrap: "text-wrap",
|
||||
top: "top",
|
||||
touch_action: "touch-action",
|
||||
transform: "transform",
|
||||
transform_box: "transform-box",
|
||||
transform_origin: "transform-origin",
|
||||
transform_style: "transform-style",
|
||||
transition: "transition",
|
||||
transition_delay: "transition-delay",
|
||||
transition_duration: "transition-duration",
|
||||
transition_property: "transition-property",
|
||||
unicode_bidi: "unicode-bidi",
|
||||
vector_effect: "vector-effect",
|
||||
vertical_align: "vertical-align",
|
||||
visibility: "visibility",
|
||||
voice_balance: "voice-balance",
|
||||
voice_duration: "voice-duration",
|
||||
voice_family: "voice-family",
|
||||
voice_pitch: "voice-pitch",
|
||||
voice_range: "voice-range",
|
||||
voice_rate: "voice-rate",
|
||||
voice_stress: "voice-stress",
|
||||
voice_volumn: "voice-volumn",
|
||||
volume: "volume",
|
||||
white_space: "white-space",
|
||||
widows: "widows",
|
||||
width: "width",
|
||||
will_change: "will-change",
|
||||
word_break: "word-break",
|
||||
word_spacing: "word-spacing",
|
||||
word_wrap: "word-wrap",
|
||||
wrap_flow: "wrap-flow",
|
||||
wrap_through: "wrap-through",
|
||||
writing_mode: "writing-mode",
|
||||
gap: "gap",
|
||||
list_styler_type: "list-style-type",
|
||||
row_gap: "row-gap",
|
||||
transition_timing_function: "transition-timing-function",
|
||||
user_select: "user-select",
|
||||
webkit_user_select: "-webkit-user-select",
|
||||
z_index : "z-index",
|
||||
}
|
||||
mapped_trait_methods! {
|
||||
aria_current: "aria-current",
|
||||
aria_details: "aria-details",
|
||||
aria_disabled: "aria-disabled",
|
||||
aria_hidden: "aria-hidden",
|
||||
aria_invalid: "aria-invalid",
|
||||
aria_keyshortcuts: "aria-keyshortcuts",
|
||||
aria_label: "aria-label",
|
||||
aria_roledescription: "aria-roledescription",
|
||||
// Widget Attributes
|
||||
aria_autocomplete: "aria-autocomplete",
|
||||
aria_checked: "aria-checked",
|
||||
aria_expanded: "aria-expanded",
|
||||
aria_haspopup: "aria-haspopup",
|
||||
aria_level: "aria-level",
|
||||
aria_modal: "aria-modal",
|
||||
aria_multiline: "aria-multiline",
|
||||
aria_multiselectable: "aria-multiselectable",
|
||||
aria_orientation: "aria-orientation",
|
||||
aria_placeholder: "aria-placeholder",
|
||||
aria_pressed: "aria-pressed",
|
||||
aria_readonly: "aria-readonly",
|
||||
aria_required: "aria-required",
|
||||
aria_selected: "aria-selected",
|
||||
aria_sort: "aria-sort",
|
||||
aria_valuemax: "aria-valuemax",
|
||||
aria_valuemin: "aria-valuemin",
|
||||
aria_valuenow: "aria-valuenow",
|
||||
aria_valuetext: "aria-valuetext",
|
||||
// Live Region Attributes
|
||||
aria_atomic: "aria-atomic",
|
||||
aria_busy: "aria-busy",
|
||||
aria_live: "aria-live",
|
||||
aria_relevant: "aria-relevant",
|
||||
|
||||
aria_dropeffect: "aria-dropeffect",
|
||||
aria_grabbed: "aria-grabbed",
|
||||
// Relationship Attributes
|
||||
aria_activedescendant: "aria-activedescendant",
|
||||
aria_colcount: "aria-colcount",
|
||||
aria_colindex: "aria-colindex",
|
||||
aria_colspan: "aria-colspan",
|
||||
aria_controls: "aria-controls",
|
||||
aria_describedby: "aria-describedby",
|
||||
aria_errormessage: "aria-errormessage",
|
||||
aria_flowto: "aria-flowto",
|
||||
aria_labelledby: "aria-labelledby",
|
||||
aria_owns: "aria-owns",
|
||||
aria_posinset: "aria-posinset",
|
||||
aria_rowcount: "aria-rowcount",
|
||||
aria_rowindex: "aria-rowindex",
|
||||
aria_rowspan: "aria-rowspan",
|
||||
aria_setsize: "aria-setsize",
|
||||
}
|
||||
|
||||
pub mod svg {
|
||||
mapped_trait_methods! {
|
||||
accent_height: "accent-height",
|
||||
accumulate: "accumulate",
|
||||
additive: "additive",
|
||||
alignment_baseline: "alignment-baseline",
|
||||
alphabetic: "alphabetic",
|
||||
amplitude: "amplitude",
|
||||
arabic_form: "arabic-form",
|
||||
ascent: "ascent",
|
||||
attributeName: "attributeName",
|
||||
attributeType: "attributeType",
|
||||
azimuth: "azimuth",
|
||||
baseFrequency: "baseFrequency",
|
||||
baseline_shift: "baseline-shift",
|
||||
baseProfile: "baseProfile",
|
||||
bbox: "bbox",
|
||||
begin: "begin",
|
||||
bias: "bias",
|
||||
by: "by",
|
||||
calcMode: "calcMode",
|
||||
cap_height: "cap-height",
|
||||
class: "class",
|
||||
clip: "clip",
|
||||
clipPathUnits: "clipPathUnits",
|
||||
clip_path: "clip-path",
|
||||
clip_rule: "clip-rule",
|
||||
color: "color",
|
||||
color_interpolation: "color-interpolation",
|
||||
color_interpolation_filters: "color-interpolation-filters",
|
||||
color_profile: "color-profile",
|
||||
color_rendering: "color-rendering",
|
||||
contentScriptType: "contentScriptType",
|
||||
contentStyleType: "contentStyleType",
|
||||
crossorigin: "crossorigin",
|
||||
cursor: "cursor",
|
||||
cx: "cx",
|
||||
cy: "cy",
|
||||
d: "d",
|
||||
decelerate: "decelerate",
|
||||
descent: "descent",
|
||||
diffuseConstant: "diffuseConstant",
|
||||
direction: "direction",
|
||||
display: "display",
|
||||
divisor: "divisor",
|
||||
dominant_baseline: "dominant-baseline",
|
||||
dur: "dur",
|
||||
dx: "dx",
|
||||
dy: "dy",
|
||||
edgeMode: "edgeMode",
|
||||
elevation: "elevation",
|
||||
enable_background: "enable-background",
|
||||
end: "end",
|
||||
exponent: "exponent",
|
||||
fill: "fill",
|
||||
fill_opacity: "fill-opacity",
|
||||
fill_rule: "fill-rule",
|
||||
filter: "filter",
|
||||
filterRes: "filterRes",
|
||||
filterUnits: "filterUnits",
|
||||
flood_color: "flood-color",
|
||||
flood_opacity: "flood-opacity",
|
||||
font_family: "font-family",
|
||||
font_size: "font-size",
|
||||
font_size_adjust: "font-size-adjust",
|
||||
font_stretch: "font-stretch",
|
||||
font_style: "font-style",
|
||||
font_variant: "font-variant",
|
||||
font_weight: "font-weight",
|
||||
format: "format",
|
||||
from: "from",
|
||||
fr: "fr",
|
||||
fx: "fx",
|
||||
fy: "fy",
|
||||
g1: "g1",
|
||||
g2: "g2",
|
||||
glyph_name: "glyph-name",
|
||||
glyph_orientation_horizontal: "glyph-orientation-horizontal",
|
||||
glyph_orientation_vertical: "glyph-orientation-vertical",
|
||||
glyphRef: "glyphRef",
|
||||
gradientTransform: "gradientTransform",
|
||||
gradientUnits: "gradientUnits",
|
||||
hanging: "hanging",
|
||||
height: "height",
|
||||
href: "href",
|
||||
hreflang: "hreflang",
|
||||
horiz_adv_x: "horiz-adv-x",
|
||||
horiz_origin_x: "horiz-origin-x",
|
||||
id: "id",
|
||||
ideographic: "ideographic",
|
||||
image_rendering: "image-rendering",
|
||||
_in: "_in",
|
||||
in2: "in2",
|
||||
intercept: "intercept",
|
||||
k: "k",
|
||||
k1: "k1",
|
||||
k2: "k2",
|
||||
k3: "k3",
|
||||
k4: "k4",
|
||||
kernelMatrix: "kernelMatrix",
|
||||
kernelUnitLength: "kernelUnitLength",
|
||||
kerning: "kerning",
|
||||
keyPoints: "keyPoints",
|
||||
keySplines: "keySplines",
|
||||
keyTimes: "keyTimes",
|
||||
lang: "lang",
|
||||
lengthAdjust: "lengthAdjust",
|
||||
letter_spacing: "letter-spacing",
|
||||
lighting_color: "lighting-color",
|
||||
limitingConeAngle: "limitingConeAngle",
|
||||
local: "local",
|
||||
marker_end: "marker-end",
|
||||
marker_mid: "marker-mid",
|
||||
marker_start: "marker_start",
|
||||
markerHeight: "markerHeight",
|
||||
markerUnits: "markerUnits",
|
||||
markerWidth: "markerWidth",
|
||||
mask: "mask",
|
||||
maskContentUnits: "maskContentUnits",
|
||||
maskUnits: "maskUnits",
|
||||
mathematical: "mathematical",
|
||||
max: "max",
|
||||
media: "media",
|
||||
method: "method",
|
||||
min: "min",
|
||||
mode: "mode",
|
||||
name: "name",
|
||||
numOctaves: "numOctaves",
|
||||
offset: "offset",
|
||||
opacity: "opacity",
|
||||
operator: "operator",
|
||||
order: "order",
|
||||
orient: "orient",
|
||||
orientation: "orientation",
|
||||
origin: "origin",
|
||||
overflow: "overflow",
|
||||
overline_position: "overline-position",
|
||||
overline_thickness: "overline-thickness",
|
||||
panose_1: "panose-1",
|
||||
paint_order: "paint-order",
|
||||
path: "path",
|
||||
pathLength: "pathLength",
|
||||
patternContentUnits: "patternContentUnits",
|
||||
patternTransform: "patternTransform",
|
||||
patternUnits: "patternUnits",
|
||||
ping: "ping",
|
||||
pointer_events: "pointer-events",
|
||||
points: "points",
|
||||
pointsAtX: "pointsAtX",
|
||||
pointsAtY: "pointsAtY",
|
||||
pointsAtZ: "pointsAtZ",
|
||||
preserveAlpha: "preserveAlpha",
|
||||
preserveAspectRatio: "preserveAspectRatio",
|
||||
primitiveUnits: "primitiveUnits",
|
||||
r: "r",
|
||||
radius: "radius",
|
||||
referrerPolicy: "referrerPolicy",
|
||||
refX: "refX",
|
||||
refY: "refY",
|
||||
rel: "rel",
|
||||
rendering_intent: "rendering-intent",
|
||||
repeatCount: "repeatCount",
|
||||
repeatDur: "repeatDur",
|
||||
requiredExtensions: "requiredExtensions",
|
||||
requiredFeatures: "requiredFeatures",
|
||||
restart: "restart",
|
||||
result: "result",
|
||||
role: "role",
|
||||
rotate: "rotate",
|
||||
rx: "rx",
|
||||
ry: "ry",
|
||||
scale: "scale",
|
||||
seed: "seed",
|
||||
shape_rendering: "shape-rendering",
|
||||
slope: "slope",
|
||||
spacing: "spacing",
|
||||
specularConstant: "specularConstant",
|
||||
specularExponent: "specularExponent",
|
||||
speed: "speed",
|
||||
spreadMethod: "spreadMethod",
|
||||
startOffset: "startOffset",
|
||||
stdDeviation: "stdDeviation",
|
||||
stemh: "stemh",
|
||||
stemv: "stemv",
|
||||
stitchTiles: "stitchTiles",
|
||||
stop_color: "stop_color",
|
||||
stop_opacity: "stop_opacity",
|
||||
strikethrough_position: "strikethrough-position",
|
||||
strikethrough_thickness: "strikethrough-thickness",
|
||||
string: "string",
|
||||
stroke: "stroke",
|
||||
stroke_dasharray: "stroke-dasharray",
|
||||
stroke_dashoffset: "stroke-dashoffset",
|
||||
stroke_linecap: "stroke-linecap",
|
||||
stroke_linejoin: "stroke-linejoin",
|
||||
stroke_miterlimit: "stroke-miterlimit",
|
||||
stroke_opacity: "stroke-opacity",
|
||||
stroke_width: "stroke-width",
|
||||
style: "style",
|
||||
surfaceScale: "surfaceScale",
|
||||
systemLanguage: "systemLanguage",
|
||||
tabindex: "tabindex",
|
||||
tableValues: "tableValues",
|
||||
target: "target",
|
||||
targetX: "targetX",
|
||||
targetY: "targetY",
|
||||
text_anchor: "text-anchor",
|
||||
text_decoration: "text-decoration",
|
||||
text_rendering: "text-rendering",
|
||||
textLength: "textLength",
|
||||
to: "to",
|
||||
transform: "transform",
|
||||
transform_origin: "transform-origin",
|
||||
r#type: "_type",
|
||||
u1: "u1",
|
||||
u2: "u2",
|
||||
underline_position: "underline-position",
|
||||
underline_thickness: "underline-thickness",
|
||||
unicode: "unicode",
|
||||
unicode_bidi: "unicode-bidi",
|
||||
unicode_range: "unicode-range",
|
||||
units_per_em: "units-per-em",
|
||||
v_alphabetic: "v-alphabetic",
|
||||
v_hanging: "v-hanging",
|
||||
v_ideographic: "v-ideographic",
|
||||
v_mathematical: "v-mathematical",
|
||||
values: "values",
|
||||
vector_effect: "vector-effect",
|
||||
version: "version",
|
||||
vert_adv_y: "vert-adv-y",
|
||||
vert_origin_x: "vert-origin-x",
|
||||
vert_origin_y: "vert-origin-y",
|
||||
view_box: "viewBox",
|
||||
view_target: "viewTarget",
|
||||
visibility: "visibility",
|
||||
width: "width",
|
||||
widths: "widths",
|
||||
word_spacing: "word-spacing",
|
||||
writing_mode: "writing-mode",
|
||||
x: "x",
|
||||
x_height: "x-height",
|
||||
x1: "x1",
|
||||
x2: "x2",
|
||||
xmlns: "xmlns",
|
||||
x_channel_selector: "xChannelSelector",
|
||||
y: "y",
|
||||
y1: "y1",
|
||||
y2: "y2",
|
||||
y_channel_selector: "yChannelSelector",
|
||||
z: "z",
|
||||
zoomAndPan: "zoomAndPan",
|
||||
}
|
||||
}
|
|
@ -165,7 +165,7 @@ impl ToTokens for Component {
|
|||
}
|
||||
|
||||
if !self.children.is_empty() {
|
||||
let renderer = TemplateRenderer {
|
||||
let renderer: TemplateRenderer = TemplateRenderer {
|
||||
roots: &self.children,
|
||||
};
|
||||
|
||||
|
|
637
packages/rsx/src/hot_reload/hot_reload_diff.rs
Normal file
637
packages/rsx/src/hot_reload/hot_reload_diff.rs
Normal file
|
@ -0,0 +1,637 @@
|
|||
use proc_macro2::TokenStream;
|
||||
use syn::{File, Macro};
|
||||
|
||||
pub enum DiffResult {
|
||||
CodeChanged,
|
||||
RsxChanged(Vec<(Macro, TokenStream)>),
|
||||
}
|
||||
|
||||
/// Find any rsx calls in the given file and return a list of all the rsx calls that have changed.
|
||||
pub fn find_rsx(new: &File, old: &File) -> DiffResult {
|
||||
let mut rsx_calls = Vec::new();
|
||||
if new.items.len() != old.items.len() {
|
||||
return DiffResult::CodeChanged;
|
||||
}
|
||||
for (new, old) in new.items.iter().zip(old.items.iter()) {
|
||||
if find_rsx_item(new, old, &mut rsx_calls) {
|
||||
return DiffResult::CodeChanged;
|
||||
}
|
||||
}
|
||||
DiffResult::RsxChanged(rsx_calls)
|
||||
}
|
||||
|
||||
fn find_rsx_item(
|
||||
new: &syn::Item,
|
||||
old: &syn::Item,
|
||||
rsx_calls: &mut Vec<(Macro, TokenStream)>,
|
||||
) -> bool {
|
||||
match (new, old) {
|
||||
(syn::Item::Const(new_item), syn::Item::Const(old_item)) => {
|
||||
find_rsx_expr(&new_item.expr, &old_item.expr, rsx_calls)
|
||||
|| new_item.attrs != old_item.attrs
|
||||
|| new_item.vis != old_item.vis
|
||||
|| new_item.const_token != old_item.const_token
|
||||
|| new_item.ident != old_item.ident
|
||||
|| new_item.colon_token != old_item.colon_token
|
||||
|| new_item.ty != old_item.ty
|
||||
|| new_item.eq_token != old_item.eq_token
|
||||
|| new_item.semi_token != old_item.semi_token
|
||||
}
|
||||
(syn::Item::Enum(new_item), syn::Item::Enum(old_item)) => {
|
||||
if new_item.variants.len() != old_item.variants.len() {
|
||||
return true;
|
||||
}
|
||||
for (new_varient, old_varient) in new_item.variants.iter().zip(old_item.variants.iter())
|
||||
{
|
||||
match (&new_varient.discriminant, &old_varient.discriminant) {
|
||||
(Some((new_eq, new_expr)), Some((old_eq, old_expr))) => {
|
||||
if find_rsx_expr(new_expr, old_expr, rsx_calls) || new_eq != old_eq {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
(None, None) => (),
|
||||
_ => return true,
|
||||
}
|
||||
if new_varient.attrs != old_varient.attrs
|
||||
|| new_varient.ident != old_varient.ident
|
||||
|| new_varient.fields != old_varient.fields
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new_item.attrs != old_item.attrs
|
||||
|| new_item.vis != old_item.vis
|
||||
|| new_item.enum_token != old_item.enum_token
|
||||
|| new_item.ident != old_item.ident
|
||||
|| new_item.generics != old_item.generics
|
||||
|| new_item.brace_token != old_item.brace_token
|
||||
}
|
||||
(syn::Item::ExternCrate(new_item), syn::Item::ExternCrate(old_item)) => {
|
||||
old_item != new_item
|
||||
}
|
||||
(syn::Item::Fn(new_item), syn::Item::Fn(old_item)) => {
|
||||
find_rsx_block(&new_item.block, &old_item.block, rsx_calls)
|
||||
|| new_item.attrs != old_item.attrs
|
||||
|| new_item.vis != old_item.vis
|
||||
|| new_item.sig != old_item.sig
|
||||
}
|
||||
(syn::Item::ForeignMod(new_item), syn::Item::ForeignMod(old_item)) => old_item != new_item,
|
||||
(syn::Item::Impl(new_item), syn::Item::Impl(old_item)) => {
|
||||
if new_item.items.len() != old_item.items.len() {
|
||||
return true;
|
||||
}
|
||||
for (new_item, old_item) in new_item.items.iter().zip(old_item.items.iter()) {
|
||||
if match (new_item, old_item) {
|
||||
(syn::ImplItem::Const(new_item), syn::ImplItem::Const(old_item)) => {
|
||||
find_rsx_expr(&new_item.expr, &old_item.expr, rsx_calls)
|
||||
}
|
||||
(syn::ImplItem::Method(new_item), syn::ImplItem::Method(old_item)) => {
|
||||
find_rsx_block(&new_item.block, &old_item.block, rsx_calls)
|
||||
}
|
||||
(syn::ImplItem::Type(new_item), syn::ImplItem::Type(old_item)) => {
|
||||
old_item != new_item
|
||||
}
|
||||
(syn::ImplItem::Macro(new_item), syn::ImplItem::Macro(old_item)) => {
|
||||
old_item != new_item
|
||||
}
|
||||
_ => true,
|
||||
} {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new_item.attrs != old_item.attrs
|
||||
|| new_item.defaultness != old_item.defaultness
|
||||
|| new_item.unsafety != old_item.unsafety
|
||||
|| new_item.impl_token != old_item.impl_token
|
||||
|| new_item.generics != old_item.generics
|
||||
|| new_item.trait_ != old_item.trait_
|
||||
|| new_item.self_ty != old_item.self_ty
|
||||
|| new_item.brace_token != old_item.brace_token
|
||||
}
|
||||
(syn::Item::Macro(new_item), syn::Item::Macro(old_item)) => {
|
||||
find_rsx_macro(&new_item.mac, &old_item.mac, rsx_calls)
|
||||
|| new_item.attrs != old_item.attrs
|
||||
|| new_item.semi_token != old_item.semi_token
|
||||
|| new_item.ident != old_item.ident
|
||||
}
|
||||
(syn::Item::Macro2(new_item), syn::Item::Macro2(old_item)) => old_item != new_item,
|
||||
(syn::Item::Mod(new_item), syn::Item::Mod(old_item)) => {
|
||||
match (&new_item.content, &old_item.content) {
|
||||
(Some((_, new_items)), Some((_, old_items))) => {
|
||||
if new_items.len() != old_items.len() {
|
||||
return true;
|
||||
}
|
||||
for (new_item, old_item) in new_items.iter().zip(old_items.iter()) {
|
||||
if find_rsx_item(new_item, old_item, rsx_calls) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new_item.attrs != old_item.attrs
|
||||
|| new_item.vis != old_item.vis
|
||||
|| new_item.mod_token != old_item.mod_token
|
||||
|| new_item.ident != old_item.ident
|
||||
|| new_item.semi != old_item.semi
|
||||
}
|
||||
(None, None) => {
|
||||
new_item.attrs != old_item.attrs
|
||||
|| new_item.vis != old_item.vis
|
||||
|| new_item.mod_token != old_item.mod_token
|
||||
|| new_item.ident != old_item.ident
|
||||
|| new_item.semi != old_item.semi
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
(syn::Item::Static(new_item), syn::Item::Static(old_item)) => {
|
||||
find_rsx_expr(&new_item.expr, &old_item.expr, rsx_calls)
|
||||
|| new_item.attrs != old_item.attrs
|
||||
|| new_item.vis != old_item.vis
|
||||
|| new_item.static_token != old_item.static_token
|
||||
|| new_item.mutability != old_item.mutability
|
||||
|| new_item.ident != old_item.ident
|
||||
|| new_item.colon_token != old_item.colon_token
|
||||
|| new_item.ty != old_item.ty
|
||||
|| new_item.eq_token != old_item.eq_token
|
||||
|| new_item.semi_token != old_item.semi_token
|
||||
}
|
||||
(syn::Item::Struct(new_item), syn::Item::Struct(old_item)) => old_item != new_item,
|
||||
(syn::Item::Trait(new_item), syn::Item::Trait(old_item)) => {
|
||||
find_rsx_trait(new_item, old_item, rsx_calls)
|
||||
}
|
||||
(syn::Item::TraitAlias(new_item), syn::Item::TraitAlias(old_item)) => old_item != new_item,
|
||||
(syn::Item::Type(new_item), syn::Item::Type(old_item)) => old_item != new_item,
|
||||
(syn::Item::Union(new_item), syn::Item::Union(old_item)) => old_item != new_item,
|
||||
(syn::Item::Use(new_item), syn::Item::Use(old_item)) => old_item != new_item,
|
||||
(syn::Item::Verbatim(_), syn::Item::Verbatim(_)) => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn find_rsx_trait(
|
||||
new_item: &syn::ItemTrait,
|
||||
old_item: &syn::ItemTrait,
|
||||
rsx_calls: &mut Vec<(Macro, TokenStream)>,
|
||||
) -> bool {
|
||||
if new_item.items.len() != old_item.items.len() {
|
||||
return true;
|
||||
}
|
||||
for (new_item, old_item) in new_item.items.iter().zip(old_item.items.iter()) {
|
||||
if match (new_item, old_item) {
|
||||
(syn::TraitItem::Const(new_item), syn::TraitItem::Const(old_item)) => {
|
||||
if let (Some((_, new_expr)), Some((_, old_expr))) =
|
||||
(&new_item.default, &old_item.default)
|
||||
{
|
||||
find_rsx_expr(new_expr, old_expr, rsx_calls)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
(syn::TraitItem::Method(new_item), syn::TraitItem::Method(old_item)) => {
|
||||
if let (Some(new_block), Some(old_block)) = (&new_item.default, &old_item.default) {
|
||||
find_rsx_block(new_block, old_block, rsx_calls)
|
||||
} else {
|
||||
true
|
||||
}
|
||||
}
|
||||
(syn::TraitItem::Type(new_item), syn::TraitItem::Type(old_item)) => {
|
||||
old_item != new_item
|
||||
}
|
||||
(syn::TraitItem::Macro(new_item), syn::TraitItem::Macro(old_item)) => {
|
||||
old_item != new_item
|
||||
}
|
||||
_ => true,
|
||||
} {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new_item.attrs != old_item.attrs
|
||||
|| new_item.vis != old_item.vis
|
||||
|| new_item.unsafety != old_item.unsafety
|
||||
|| new_item.auto_token != old_item.auto_token
|
||||
|| new_item.ident != old_item.ident
|
||||
|| new_item.generics != old_item.generics
|
||||
|| new_item.colon_token != old_item.colon_token
|
||||
|| new_item.supertraits != old_item.supertraits
|
||||
|| new_item.brace_token != old_item.brace_token
|
||||
}
|
||||
|
||||
fn find_rsx_block(
|
||||
new_block: &syn::Block,
|
||||
old_block: &syn::Block,
|
||||
rsx_calls: &mut Vec<(Macro, TokenStream)>,
|
||||
) -> bool {
|
||||
if new_block.stmts.len() != old_block.stmts.len() {
|
||||
return true;
|
||||
}
|
||||
for (new_stmt, old_stmt) in new_block.stmts.iter().zip(old_block.stmts.iter()) {
|
||||
if find_rsx_stmt(new_stmt, old_stmt, rsx_calls) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new_block.brace_token != old_block.brace_token
|
||||
}
|
||||
|
||||
fn find_rsx_stmt(
|
||||
new_stmt: &syn::Stmt,
|
||||
old_stmt: &syn::Stmt,
|
||||
rsx_calls: &mut Vec<(Macro, TokenStream)>,
|
||||
) -> bool {
|
||||
match (new_stmt, old_stmt) {
|
||||
(syn::Stmt::Local(new_local), syn::Stmt::Local(old_local)) => {
|
||||
(match (&new_local.init, &old_local.init) {
|
||||
(Some((new_eq, new_expr)), Some((old_eq, old_expr))) => {
|
||||
find_rsx_expr(new_expr, old_expr, rsx_calls) || new_eq != old_eq
|
||||
}
|
||||
(None, None) => false,
|
||||
_ => true,
|
||||
} || new_local.attrs != old_local.attrs
|
||||
|| new_local.let_token != old_local.let_token
|
||||
|| new_local.pat != old_local.pat
|
||||
|| new_local.semi_token != old_local.semi_token)
|
||||
}
|
||||
(syn::Stmt::Item(new_item), syn::Stmt::Item(old_item)) => {
|
||||
find_rsx_item(new_item, old_item, rsx_calls)
|
||||
}
|
||||
(syn::Stmt::Expr(new_expr), syn::Stmt::Expr(old_expr)) => {
|
||||
find_rsx_expr(new_expr, old_expr, rsx_calls)
|
||||
}
|
||||
(syn::Stmt::Semi(new_expr, new_semi), syn::Stmt::Semi(old_expr, old_semi)) => {
|
||||
find_rsx_expr(new_expr, old_expr, rsx_calls) || new_semi != old_semi
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn find_rsx_expr(
|
||||
new_expr: &syn::Expr,
|
||||
old_expr: &syn::Expr,
|
||||
rsx_calls: &mut Vec<(Macro, TokenStream)>,
|
||||
) -> bool {
|
||||
match (new_expr, old_expr) {
|
||||
(syn::Expr::Array(new_expr), syn::Expr::Array(old_expr)) => {
|
||||
if new_expr.elems.len() != old_expr.elems.len() {
|
||||
return true;
|
||||
}
|
||||
for (new_el, old_el) in new_expr.elems.iter().zip(old_expr.elems.iter()) {
|
||||
if find_rsx_expr(new_el, old_el, rsx_calls) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new_expr.attrs != old_expr.attrs || new_expr.bracket_token != old_expr.bracket_token
|
||||
}
|
||||
(syn::Expr::Assign(new_expr), syn::Expr::Assign(old_expr)) => {
|
||||
find_rsx_expr(&new_expr.left, &old_expr.left, rsx_calls)
|
||||
|| find_rsx_expr(&new_expr.right, &old_expr.right, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.eq_token != old_expr.eq_token
|
||||
}
|
||||
(syn::Expr::AssignOp(new_expr), syn::Expr::AssignOp(old_expr)) => {
|
||||
find_rsx_expr(&new_expr.left, &old_expr.left, rsx_calls)
|
||||
|| find_rsx_expr(&new_expr.right, &old_expr.right, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.op != old_expr.op
|
||||
}
|
||||
(syn::Expr::Async(new_expr), syn::Expr::Async(old_expr)) => {
|
||||
find_rsx_block(&new_expr.block, &old_expr.block, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.async_token != old_expr.async_token
|
||||
|| new_expr.capture != old_expr.capture
|
||||
}
|
||||
(syn::Expr::Await(new_expr), syn::Expr::Await(old_expr)) => {
|
||||
find_rsx_expr(&new_expr.base, &old_expr.base, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.dot_token != old_expr.dot_token
|
||||
|| new_expr.await_token != old_expr.await_token
|
||||
}
|
||||
(syn::Expr::Binary(new_expr), syn::Expr::Binary(old_expr)) => {
|
||||
find_rsx_expr(&new_expr.left, &old_expr.left, rsx_calls)
|
||||
|| find_rsx_expr(&new_expr.right, &old_expr.right, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.op != old_expr.op
|
||||
}
|
||||
(syn::Expr::Block(new_expr), syn::Expr::Block(old_expr)) => {
|
||||
find_rsx_block(&new_expr.block, &old_expr.block, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.label != old_expr.label
|
||||
}
|
||||
(syn::Expr::Box(new_expr), syn::Expr::Box(old_expr)) => {
|
||||
find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.box_token != old_expr.box_token
|
||||
}
|
||||
(syn::Expr::Break(new_expr), syn::Expr::Break(old_expr)) => {
|
||||
match (&new_expr.expr, &old_expr.expr) {
|
||||
(Some(new_inner), Some(old_inner)) => {
|
||||
find_rsx_expr(new_inner, old_inner, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.break_token != old_expr.break_token
|
||||
|| new_expr.label != old_expr.label
|
||||
}
|
||||
(None, None) => {
|
||||
new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.break_token != old_expr.break_token
|
||||
|| new_expr.label != old_expr.label
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
(syn::Expr::Call(new_expr), syn::Expr::Call(old_expr)) => {
|
||||
find_rsx_expr(&new_expr.func, &old_expr.func, rsx_calls);
|
||||
if new_expr.args.len() != old_expr.args.len() {
|
||||
return true;
|
||||
}
|
||||
for (new_arg, old_arg) in new_expr.args.iter().zip(old_expr.args.iter()) {
|
||||
if find_rsx_expr(new_arg, old_arg, rsx_calls) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new_expr.attrs != old_expr.attrs || new_expr.paren_token != old_expr.paren_token
|
||||
}
|
||||
(syn::Expr::Cast(new_expr), syn::Expr::Cast(old_expr)) => {
|
||||
find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.as_token != old_expr.as_token
|
||||
|| new_expr.ty != old_expr.ty
|
||||
}
|
||||
(syn::Expr::Closure(new_expr), syn::Expr::Closure(old_expr)) => {
|
||||
find_rsx_expr(&new_expr.body, &old_expr.body, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.movability != old_expr.movability
|
||||
|| new_expr.asyncness != old_expr.asyncness
|
||||
|| new_expr.capture != old_expr.capture
|
||||
|| new_expr.or1_token != old_expr.or1_token
|
||||
|| new_expr.inputs != old_expr.inputs
|
||||
|| new_expr.or2_token != old_expr.or2_token
|
||||
|| new_expr.output != old_expr.output
|
||||
}
|
||||
(syn::Expr::Continue(new_expr), syn::Expr::Continue(old_expr)) => old_expr != new_expr,
|
||||
(syn::Expr::Field(new_expr), syn::Expr::Field(old_expr)) => {
|
||||
find_rsx_expr(&new_expr.base, &old_expr.base, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.dot_token != old_expr.dot_token
|
||||
|| new_expr.member != old_expr.member
|
||||
}
|
||||
(syn::Expr::ForLoop(new_expr), syn::Expr::ForLoop(old_expr)) => {
|
||||
find_rsx_block(&new_expr.body, &old_expr.body, rsx_calls)
|
||||
|| find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.label != old_expr.label
|
||||
|| new_expr.for_token != old_expr.for_token
|
||||
|| new_expr.pat != old_expr.pat
|
||||
|| new_expr.in_token != old_expr.in_token
|
||||
}
|
||||
(syn::Expr::Group(new_expr), syn::Expr::Group(old_expr)) => {
|
||||
find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls)
|
||||
}
|
||||
(syn::Expr::If(new_expr), syn::Expr::If(old_expr)) => {
|
||||
if find_rsx_expr(&new_expr.cond, &old_expr.cond, rsx_calls)
|
||||
|| find_rsx_block(&new_expr.then_branch, &old_expr.then_branch, rsx_calls)
|
||||
{
|
||||
return true;
|
||||
}
|
||||
match (&new_expr.else_branch, &old_expr.else_branch) {
|
||||
(Some((new_tok, new_else)), Some((old_tok, old_else))) => {
|
||||
find_rsx_expr(new_else, old_else, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.if_token != old_expr.if_token
|
||||
|| new_expr.cond != old_expr.cond
|
||||
|| new_tok != old_tok
|
||||
}
|
||||
(None, None) => {
|
||||
new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.if_token != old_expr.if_token
|
||||
|| new_expr.cond != old_expr.cond
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
(syn::Expr::Index(new_expr), syn::Expr::Index(old_expr)) => {
|
||||
find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls)
|
||||
|| find_rsx_expr(&new_expr.index, &old_expr.index, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.bracket_token != old_expr.bracket_token
|
||||
}
|
||||
(syn::Expr::Let(new_expr), syn::Expr::Let(old_expr)) => {
|
||||
find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.let_token != old_expr.let_token
|
||||
|| new_expr.pat != old_expr.pat
|
||||
|| new_expr.eq_token != old_expr.eq_token
|
||||
}
|
||||
(syn::Expr::Lit(new_expr), syn::Expr::Lit(old_expr)) => old_expr != new_expr,
|
||||
(syn::Expr::Loop(new_expr), syn::Expr::Loop(old_expr)) => {
|
||||
find_rsx_block(&new_expr.body, &old_expr.body, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.label != old_expr.label
|
||||
|| new_expr.loop_token != old_expr.loop_token
|
||||
}
|
||||
(syn::Expr::Macro(new_expr), syn::Expr::Macro(old_expr)) => {
|
||||
find_rsx_macro(&new_expr.mac, &old_expr.mac, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
}
|
||||
(syn::Expr::Match(new_expr), syn::Expr::Match(old_expr)) => {
|
||||
if find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls) {
|
||||
return true;
|
||||
}
|
||||
for (new_arm, old_arm) in new_expr.arms.iter().zip(old_expr.arms.iter()) {
|
||||
match (&new_arm.guard, &old_arm.guard) {
|
||||
(Some((new_tok, new_expr)), Some((old_tok, old_expr))) => {
|
||||
if find_rsx_expr(new_expr, old_expr, rsx_calls) || new_tok != old_tok {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
(None, None) => (),
|
||||
_ => return true,
|
||||
}
|
||||
if find_rsx_expr(&new_arm.body, &old_arm.body, rsx_calls)
|
||||
|| new_arm.attrs != old_arm.attrs
|
||||
|| new_arm.pat != old_arm.pat
|
||||
|| new_arm.fat_arrow_token != old_arm.fat_arrow_token
|
||||
|| new_arm.comma != old_arm.comma
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.match_token != old_expr.match_token
|
||||
|| new_expr.brace_token != old_expr.brace_token
|
||||
}
|
||||
(syn::Expr::MethodCall(new_expr), syn::Expr::MethodCall(old_expr)) => {
|
||||
if find_rsx_expr(&new_expr.receiver, &old_expr.receiver, rsx_calls) {
|
||||
return true;
|
||||
}
|
||||
for (new_arg, old_arg) in new_expr.args.iter().zip(old_expr.args.iter()) {
|
||||
if find_rsx_expr(new_arg, old_arg, rsx_calls) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.dot_token != old_expr.dot_token
|
||||
|| new_expr.method != old_expr.method
|
||||
|| new_expr.turbofish != old_expr.turbofish
|
||||
|| new_expr.paren_token != old_expr.paren_token
|
||||
}
|
||||
(syn::Expr::Paren(new_expr), syn::Expr::Paren(old_expr)) => {
|
||||
find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.paren_token != old_expr.paren_token
|
||||
}
|
||||
(syn::Expr::Path(new_expr), syn::Expr::Path(old_expr)) => old_expr != new_expr,
|
||||
(syn::Expr::Range(new_expr), syn::Expr::Range(old_expr)) => {
|
||||
match (&new_expr.from, &old_expr.from) {
|
||||
(Some(new_expr), Some(old_expr)) => {
|
||||
if find_rsx_expr(new_expr, old_expr, rsx_calls) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
(None, None) => (),
|
||||
_ => return true,
|
||||
}
|
||||
match (&new_expr.to, &old_expr.to) {
|
||||
(Some(new_inner), Some(old_inner)) => {
|
||||
find_rsx_expr(new_inner, old_inner, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.limits != old_expr.limits
|
||||
}
|
||||
(None, None) => {
|
||||
new_expr.attrs != old_expr.attrs || new_expr.limits != old_expr.limits
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
(syn::Expr::Reference(new_expr), syn::Expr::Reference(old_expr)) => {
|
||||
find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.and_token != old_expr.and_token
|
||||
|| new_expr.mutability != old_expr.mutability
|
||||
}
|
||||
(syn::Expr::Repeat(new_expr), syn::Expr::Repeat(old_expr)) => {
|
||||
find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls)
|
||||
|| find_rsx_expr(&new_expr.len, &old_expr.len, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.bracket_token != old_expr.bracket_token
|
||||
|| new_expr.semi_token != old_expr.semi_token
|
||||
}
|
||||
(syn::Expr::Return(new_expr), syn::Expr::Return(old_expr)) => {
|
||||
match (&new_expr.expr, &old_expr.expr) {
|
||||
(Some(new_inner), Some(old_inner)) => {
|
||||
find_rsx_expr(new_inner, old_inner, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.return_token != old_expr.return_token
|
||||
}
|
||||
(None, None) => {
|
||||
new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.return_token != old_expr.return_token
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
(syn::Expr::Struct(new_expr), syn::Expr::Struct(old_expr)) => {
|
||||
match (&new_expr.rest, &old_expr.rest) {
|
||||
(Some(new_expr), Some(old_expr)) => {
|
||||
if find_rsx_expr(new_expr, old_expr, rsx_calls) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
(None, None) => (),
|
||||
_ => return true,
|
||||
}
|
||||
for (new_field, old_field) in new_expr.fields.iter().zip(old_expr.fields.iter()) {
|
||||
if find_rsx_expr(&new_field.expr, &old_field.expr, rsx_calls)
|
||||
|| new_field.attrs != old_field.attrs
|
||||
|| new_field.member != old_field.member
|
||||
|| new_field.colon_token != old_field.colon_token
|
||||
{
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.path != old_expr.path
|
||||
|| new_expr.brace_token != old_expr.brace_token
|
||||
|| new_expr.dot2_token != old_expr.dot2_token
|
||||
}
|
||||
(syn::Expr::Try(new_expr), syn::Expr::Try(old_expr)) => {
|
||||
find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.question_token != old_expr.question_token
|
||||
}
|
||||
(syn::Expr::TryBlock(new_expr), syn::Expr::TryBlock(old_expr)) => {
|
||||
find_rsx_block(&new_expr.block, &old_expr.block, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.try_token != old_expr.try_token
|
||||
}
|
||||
(syn::Expr::Tuple(new_expr), syn::Expr::Tuple(old_expr)) => {
|
||||
for (new_el, old_el) in new_expr.elems.iter().zip(old_expr.elems.iter()) {
|
||||
if find_rsx_expr(new_el, old_el, rsx_calls) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
new_expr.attrs != old_expr.attrs || new_expr.paren_token != old_expr.paren_token
|
||||
}
|
||||
(syn::Expr::Type(new_expr), syn::Expr::Type(old_expr)) => {
|
||||
find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.colon_token != old_expr.colon_token
|
||||
|| new_expr.ty != old_expr.ty
|
||||
}
|
||||
(syn::Expr::Unary(new_expr), syn::Expr::Unary(old_expr)) => {
|
||||
find_rsx_expr(&new_expr.expr, &old_expr.expr, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.op != old_expr.op
|
||||
}
|
||||
(syn::Expr::Unsafe(new_expr), syn::Expr::Unsafe(old_expr)) => {
|
||||
find_rsx_block(&new_expr.block, &old_expr.block, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.unsafe_token != old_expr.unsafe_token
|
||||
}
|
||||
(syn::Expr::While(new_expr), syn::Expr::While(old_expr)) => {
|
||||
find_rsx_expr(&new_expr.cond, &old_expr.cond, rsx_calls)
|
||||
|| find_rsx_block(&new_expr.body, &old_expr.body, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.label != old_expr.label
|
||||
|| new_expr.while_token != old_expr.while_token
|
||||
}
|
||||
(syn::Expr::Yield(new_expr), syn::Expr::Yield(old_expr)) => {
|
||||
match (&new_expr.expr, &old_expr.expr) {
|
||||
(Some(new_inner), Some(old_inner)) => {
|
||||
find_rsx_expr(new_inner, old_inner, rsx_calls)
|
||||
|| new_expr.attrs != old_expr.attrs
|
||||
|| new_expr.yield_token != old_expr.yield_token
|
||||
}
|
||||
(None, None) => {
|
||||
new_expr.attrs != old_expr.attrs || new_expr.yield_token != old_expr.yield_token
|
||||
}
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
(syn::Expr::Verbatim(_), syn::Expr::Verbatim(_)) => false,
|
||||
_ => true,
|
||||
}
|
||||
}
|
||||
|
||||
fn find_rsx_macro(
|
||||
new_mac: &syn::Macro,
|
||||
old_mac: &syn::Macro,
|
||||
rsx_calls: &mut Vec<(Macro, TokenStream)>,
|
||||
) -> bool {
|
||||
if matches!(
|
||||
new_mac
|
||||
.path
|
||||
.get_ident()
|
||||
.map(|ident| ident.to_string())
|
||||
.as_deref(),
|
||||
Some("rsx" | "render")
|
||||
) && matches!(
|
||||
old_mac
|
||||
.path
|
||||
.get_ident()
|
||||
.map(|ident| ident.to_string())
|
||||
.as_deref(),
|
||||
Some("rsx" | "render")
|
||||
) {
|
||||
rsx_calls.push((old_mac.clone(), new_mac.tokens.clone()));
|
||||
false
|
||||
} else {
|
||||
new_mac != old_mac
|
||||
}
|
||||
}
|
19
packages/rsx/src/hot_reload/hot_reloading_context.rs
Normal file
19
packages/rsx/src/hot_reload/hot_reloading_context.rs
Normal file
|
@ -0,0 +1,19 @@
|
|||
pub trait HotReloadingContext {
|
||||
fn map_attribute(
|
||||
element_name_rust: &str,
|
||||
attribute_name_rust: &str,
|
||||
) -> Option<(&'static str, Option<&'static str>)>;
|
||||
fn map_element(element_name_rust: &str) -> Option<(&'static str, Option<&'static str>)>;
|
||||
}
|
||||
|
||||
pub struct Empty;
|
||||
|
||||
impl HotReloadingContext for Empty {
|
||||
fn map_attribute(_: &str, _: &str) -> Option<(&'static str, Option<&'static str>)> {
|
||||
None
|
||||
}
|
||||
|
||||
fn map_element(_: &str) -> Option<(&'static str, Option<&'static str>)> {
|
||||
None
|
||||
}
|
||||
}
|
122
packages/rsx/src/hot_reload/hot_reloading_file_map.rs
Normal file
122
packages/rsx/src/hot_reload/hot_reloading_file_map.rs
Normal file
|
@ -0,0 +1,122 @@
|
|||
use crate::{CallBody, HotReloadingContext};
|
||||
use dioxus_core::Template;
|
||||
pub use proc_macro2::TokenStream;
|
||||
pub use std::collections::HashMap;
|
||||
use std::path::PathBuf;
|
||||
pub use std::sync::Mutex;
|
||||
pub use std::time::SystemTime;
|
||||
pub use std::{fs, io, path::Path};
|
||||
pub use std::{fs::File, io::Read};
|
||||
pub use syn::__private::ToTokens;
|
||||
use syn::spanned::Spanned;
|
||||
|
||||
use super::hot_reload_diff::{find_rsx, DiffResult};
|
||||
|
||||
pub enum UpdateResult {
|
||||
UpdatedRsx(Vec<Template<'static>>),
|
||||
NeedsRebuild,
|
||||
}
|
||||
|
||||
pub struct FileMap<Ctx: HotReloadingContext> {
|
||||
pub map: HashMap<PathBuf, (String, Option<Template<'static>>)>,
|
||||
phantom: std::marker::PhantomData<Ctx>,
|
||||
}
|
||||
|
||||
impl<Ctx: HotReloadingContext> FileMap<Ctx> {
|
||||
/// Create a new FileMap from a crate directory
|
||||
pub fn new(path: PathBuf) -> Self {
|
||||
fn find_rs_files(
|
||||
root: PathBuf,
|
||||
) -> io::Result<HashMap<PathBuf, (String, Option<Template<'static>>)>> {
|
||||
let mut files = HashMap::new();
|
||||
if root.is_dir() {
|
||||
for entry in (fs::read_dir(root)?).flatten() {
|
||||
let path = entry.path();
|
||||
files.extend(find_rs_files(path)?);
|
||||
}
|
||||
} else if root.extension().and_then(|s| s.to_str()) == Some("rs") {
|
||||
if let Ok(mut file) = File::open(root.clone()) {
|
||||
let mut src = String::new();
|
||||
file.read_to_string(&mut src).expect("Unable to read file");
|
||||
files.insert(root, (src, None));
|
||||
}
|
||||
}
|
||||
Ok(files)
|
||||
}
|
||||
|
||||
let result = Self {
|
||||
map: find_rs_files(path).unwrap(),
|
||||
phantom: std::marker::PhantomData,
|
||||
};
|
||||
result
|
||||
}
|
||||
|
||||
/// Try to update the rsx in a file
|
||||
pub fn update_rsx(&mut self, file_path: &Path, crate_dir: &Path) -> UpdateResult {
|
||||
let mut file = File::open(file_path).unwrap();
|
||||
let mut src = String::new();
|
||||
file.read_to_string(&mut src).expect("Unable to read file");
|
||||
if let Ok(syntax) = syn::parse_file(&src) {
|
||||
if let Some((old_src, template_slot)) = self.map.get_mut(file_path) {
|
||||
if let Ok(old) = syn::parse_file(old_src) {
|
||||
match find_rsx(&syntax, &old) {
|
||||
DiffResult::CodeChanged => {
|
||||
self.map.insert(file_path.to_path_buf(), (src, None));
|
||||
}
|
||||
DiffResult::RsxChanged(changed) => {
|
||||
let mut messages: Vec<Template<'static>> = Vec::new();
|
||||
for (old, new) in changed.into_iter() {
|
||||
let old_start = old.span().start();
|
||||
|
||||
if let (Ok(old_call_body), Ok(new_call_body)) = (
|
||||
syn::parse2::<CallBody>(old.tokens),
|
||||
syn::parse2::<CallBody>(new),
|
||||
) {
|
||||
if let Ok(file) = file_path.strip_prefix(crate_dir) {
|
||||
let line = old_start.line;
|
||||
let column = old_start.column + 1;
|
||||
let location = file.display().to_string()
|
||||
+ ":"
|
||||
+ &line.to_string()
|
||||
+ ":"
|
||||
+ &column.to_string()
|
||||
// the byte index doesn't matter, but dioxus needs it
|
||||
+ ":0";
|
||||
|
||||
if let Some(template) = new_call_body
|
||||
.update_template::<Ctx>(
|
||||
Some(old_call_body),
|
||||
Box::leak(location.into_boxed_str()),
|
||||
)
|
||||
{
|
||||
// dioxus cannot handle empty templates
|
||||
if template.roots.is_empty() {
|
||||
return UpdateResult::NeedsRebuild;
|
||||
} else {
|
||||
// if the template is the same, don't send it
|
||||
if let Some(old_template) = template_slot {
|
||||
if old_template == &template {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
*template_slot = Some(template);
|
||||
messages.push(template);
|
||||
}
|
||||
} else {
|
||||
return UpdateResult::NeedsRebuild;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return UpdateResult::UpdatedRsx(messages);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// if this is a new file, rebuild the project
|
||||
*self = FileMap::new(crate_dir.to_path_buf());
|
||||
}
|
||||
}
|
||||
UpdateResult::NeedsRebuild
|
||||
}
|
||||
}
|
6
packages/rsx/src/hot_reload/mod.rs
Normal file
6
packages/rsx/src/hot_reload/mod.rs
Normal file
|
@ -0,0 +1,6 @@
|
|||
mod hot_reload_diff;
|
||||
pub use hot_reload_diff::*;
|
||||
mod hot_reloading_context;
|
||||
pub use hot_reloading_context::*;
|
||||
mod hot_reloading_file_map;
|
||||
pub use hot_reloading_file_map::*;
|
|
@ -21,7 +21,7 @@ pub struct IfmtInput {
|
|||
|
||||
impl IfmtInput {
|
||||
pub fn is_static(&self) -> bool {
|
||||
matches!(self.segments.as_slice(), &[Segment::Literal(_)])
|
||||
matches!(self.segments.as_slice(), &[Segment::Literal(_)] | &[])
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -15,14 +15,19 @@
|
|||
mod errors;
|
||||
mod component;
|
||||
mod element;
|
||||
pub mod hot_reload;
|
||||
mod ifmt;
|
||||
mod node;
|
||||
mod template;
|
||||
|
||||
use std::{collections::HashMap, fmt::Debug, hash::Hash};
|
||||
|
||||
// Re-export the namespaces into each other
|
||||
pub use component::*;
|
||||
use dioxus_core::{Template, TemplateAttribute, TemplateNode};
|
||||
pub use element::*;
|
||||
pub use hot_reload::HotReloadingContext;
|
||||
pub use ifmt::*;
|
||||
use internment::Intern;
|
||||
pub use node::*;
|
||||
|
||||
// imports
|
||||
|
@ -33,12 +38,32 @@ use syn::{
|
|||
Result, Token,
|
||||
};
|
||||
|
||||
// interns a object into a static object, resusing the value if it already exists
|
||||
fn intern<T: Eq + Hash + Send + Sync + ?Sized + 'static>(s: impl Into<Intern<T>>) -> &'static T {
|
||||
s.into().as_ref()
|
||||
}
|
||||
|
||||
/// Fundametnally, every CallBody is a template
|
||||
#[derive(Default, Debug)]
|
||||
pub struct CallBody {
|
||||
pub roots: Vec<BodyNode>,
|
||||
}
|
||||
|
||||
impl CallBody {
|
||||
/// This will try to create a new template from the current body and the previous body. This will return None if the rsx has some dynamic part that has changed.
|
||||
/// This function intentionally leaks memory to create a static template.
|
||||
/// Keeping the template static allows us to simplify the core of dioxus and leaking memory in dev mode is less of an issue.
|
||||
/// the previous_location is the location of the previous template at the time the template was originally compiled.
|
||||
pub fn update_template<Ctx: HotReloadingContext>(
|
||||
&self,
|
||||
template: Option<CallBody>,
|
||||
location: &'static str,
|
||||
) -> Option<Template<'static>> {
|
||||
let mut renderer: TemplateRenderer = TemplateRenderer { roots: &self.roots };
|
||||
renderer.update_template::<Ctx>(template, location)
|
||||
}
|
||||
}
|
||||
|
||||
impl Parse for CallBody {
|
||||
fn parse(input: ParseStream) -> Result<Self> {
|
||||
let mut roots = Vec::new();
|
||||
|
@ -75,7 +100,7 @@ pub struct RenderCallBody(pub CallBody);
|
|||
|
||||
impl ToTokens for RenderCallBody {
|
||||
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
|
||||
let body = TemplateRenderer {
|
||||
let body: TemplateRenderer = TemplateRenderer {
|
||||
roots: &self.0.roots,
|
||||
};
|
||||
|
||||
|
@ -92,15 +117,49 @@ pub struct TemplateRenderer<'a> {
|
|||
pub roots: &'a [BodyNode],
|
||||
}
|
||||
|
||||
impl<'a> TemplateRenderer<'a> {
|
||||
fn update_template<Ctx: HotReloadingContext>(
|
||||
&mut self,
|
||||
previous_call: Option<CallBody>,
|
||||
location: &'static str,
|
||||
) -> Option<Template<'static>> {
|
||||
let mut mapping = previous_call.map(|call| DynamicMapping::from(call.roots));
|
||||
|
||||
let mut context = DynamicContext::default();
|
||||
|
||||
let mut roots = Vec::new();
|
||||
for (idx, root) in self.roots.iter().enumerate() {
|
||||
context.current_path.push(idx as u8);
|
||||
roots.push(context.update_node::<Ctx>(root, &mut mapping)?);
|
||||
context.current_path.pop();
|
||||
}
|
||||
|
||||
Some(Template {
|
||||
name: location,
|
||||
roots: intern(roots.as_slice()),
|
||||
node_paths: intern(
|
||||
context
|
||||
.node_paths
|
||||
.into_iter()
|
||||
.map(|path| intern(path.as_slice()))
|
||||
.collect::<Vec<_>>()
|
||||
.as_slice(),
|
||||
),
|
||||
attr_paths: intern(
|
||||
context
|
||||
.attr_paths
|
||||
.into_iter()
|
||||
.map(|path| intern(path.as_slice()))
|
||||
.collect::<Vec<_>>()
|
||||
.as_slice(),
|
||||
),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> ToTokens for TemplateRenderer<'a> {
|
||||
fn to_tokens(&self, out_tokens: &mut TokenStream2) {
|
||||
let mut context = DynamicContext {
|
||||
dynamic_nodes: vec![],
|
||||
dynamic_attributes: vec![],
|
||||
current_path: vec![],
|
||||
attr_paths: vec![],
|
||||
node_paths: vec![],
|
||||
};
|
||||
let mut context = DynamicContext::default();
|
||||
|
||||
let key = match self.roots.get(0) {
|
||||
Some(BodyNode::Element(el)) if self.roots.len() == 1 => el.key.clone(),
|
||||
|
@ -127,7 +186,6 @@ impl<'a> ToTokens for TemplateRenderer<'a> {
|
|||
});
|
||||
|
||||
// Render and release the mutable borrow on context
|
||||
let num_roots = self.roots.len();
|
||||
let roots = quote! { #( #root_printer ),* };
|
||||
let node_printer = &context.dynamic_nodes;
|
||||
let dyn_attr_printer = &context.dynamic_attributes;
|
||||
|
@ -152,16 +210,106 @@ impl<'a> ToTokens for TemplateRenderer<'a> {
|
|||
::dioxus::core::VNode {
|
||||
parent: None,
|
||||
key: #key_tokens,
|
||||
template: TEMPLATE,
|
||||
root_ids: std::cell::Cell::from_mut( __cx.bump().alloc([None; #num_roots]) as &mut _).as_slice_of_cells(),
|
||||
template: std::cell::Cell::new(TEMPLATE),
|
||||
root_ids: Default::default(),
|
||||
dynamic_nodes: __cx.bump().alloc([ #( #node_printer ),* ]),
|
||||
dynamic_attrs: __cx.bump().alloc([ #( #dyn_attr_printer ),* ]),
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
// As we print out the dynamic nodes, we want to keep track of them in a linear fashion
|
||||
// We'll use the size of the vecs to determine the index of the dynamic node in the final
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
struct DynamicMapping {
|
||||
attribute_to_idx: HashMap<ElementAttr, Vec<usize>>,
|
||||
last_attribute_idx: usize,
|
||||
node_to_idx: HashMap<BodyNode, Vec<usize>>,
|
||||
last_element_idx: usize,
|
||||
}
|
||||
|
||||
impl DynamicMapping {
|
||||
fn from(nodes: Vec<BodyNode>) -> Self {
|
||||
let mut new = Self::default();
|
||||
for node in nodes {
|
||||
new.add_node(node);
|
||||
}
|
||||
new
|
||||
}
|
||||
|
||||
fn get_attribute_idx(&mut self, attr: &ElementAttr) -> Option<usize> {
|
||||
self.attribute_to_idx
|
||||
.get_mut(attr)
|
||||
.and_then(|idxs| idxs.pop())
|
||||
}
|
||||
|
||||
fn get_node_idx(&mut self, node: &BodyNode) -> Option<usize> {
|
||||
self.node_to_idx.get_mut(node).and_then(|idxs| idxs.pop())
|
||||
}
|
||||
|
||||
fn insert_attribute(&mut self, attr: ElementAttr) -> usize {
|
||||
let idx = self.last_attribute_idx;
|
||||
self.last_attribute_idx += 1;
|
||||
|
||||
self.attribute_to_idx
|
||||
.entry(attr)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(idx);
|
||||
|
||||
idx
|
||||
}
|
||||
|
||||
fn insert_node(&mut self, node: BodyNode) -> usize {
|
||||
let idx = self.last_element_idx;
|
||||
self.last_element_idx += 1;
|
||||
|
||||
self.node_to_idx
|
||||
.entry(node)
|
||||
.or_insert_with(Vec::new)
|
||||
.push(idx);
|
||||
|
||||
idx
|
||||
}
|
||||
|
||||
fn add_node(&mut self, node: BodyNode) {
|
||||
match node {
|
||||
BodyNode::Element(el) => {
|
||||
for attr in el.attributes {
|
||||
match &attr.attr {
|
||||
ElementAttr::CustomAttrText { value, .. }
|
||||
| ElementAttr::AttrText { value, .. }
|
||||
if value.is_static() => {}
|
||||
|
||||
ElementAttr::AttrExpression { .. }
|
||||
| ElementAttr::AttrText { .. }
|
||||
| ElementAttr::CustomAttrText { .. }
|
||||
| ElementAttr::CustomAttrExpression { .. }
|
||||
| ElementAttr::EventTokens { .. } => {
|
||||
self.insert_attribute(attr.attr);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for child in el.children {
|
||||
self.add_node(child);
|
||||
}
|
||||
}
|
||||
|
||||
BodyNode::Text(text) if text.is_static() => {}
|
||||
|
||||
BodyNode::RawExpr(_)
|
||||
| BodyNode::Text(_)
|
||||
| BodyNode::ForLoop(_)
|
||||
| BodyNode::IfChain(_)
|
||||
| BodyNode::Component(_) => {
|
||||
self.insert_node(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// As we create the dynamic nodes, we want to keep track of them in a linear fashion
|
||||
// We'll use the size of the vecs to determine the index of the dynamic node in the final output
|
||||
#[derive(Default, Debug)]
|
||||
pub struct DynamicContext<'a> {
|
||||
dynamic_nodes: Vec<&'a BodyNode>,
|
||||
dynamic_attributes: Vec<&'a ElementAttrNamed>,
|
||||
|
@ -172,20 +320,112 @@ pub struct DynamicContext<'a> {
|
|||
}
|
||||
|
||||
impl<'a> DynamicContext<'a> {
|
||||
fn update_node<Ctx: HotReloadingContext>(
|
||||
&mut self,
|
||||
root: &'a BodyNode,
|
||||
mapping: &mut Option<DynamicMapping>,
|
||||
) -> Option<TemplateNode<'static>> {
|
||||
match root {
|
||||
BodyNode::Element(el) => {
|
||||
let element_name_rust = el.name.to_string();
|
||||
|
||||
let mut static_attrs = Vec::new();
|
||||
for attr in &el.attributes {
|
||||
match &attr.attr {
|
||||
ElementAttr::AttrText { name, value } if value.is_static() => {
|
||||
let value = value.source.as_ref().unwrap();
|
||||
let attribute_name_rust = name.to_string();
|
||||
let (name, namespace) =
|
||||
Ctx::map_attribute(&element_name_rust, &attribute_name_rust)
|
||||
.unwrap_or((intern(attribute_name_rust.as_str()), None));
|
||||
static_attrs.push(TemplateAttribute::Static {
|
||||
name,
|
||||
namespace,
|
||||
value: intern(value.value().as_str()),
|
||||
})
|
||||
}
|
||||
|
||||
ElementAttr::CustomAttrText { name, value } if value.is_static() => {
|
||||
let value = value.source.as_ref().unwrap();
|
||||
static_attrs.push(TemplateAttribute::Static {
|
||||
name: intern(name.value().as_str()),
|
||||
namespace: None,
|
||||
value: intern(value.value().as_str()),
|
||||
})
|
||||
}
|
||||
|
||||
ElementAttr::AttrExpression { .. }
|
||||
| ElementAttr::AttrText { .. }
|
||||
| ElementAttr::CustomAttrText { .. }
|
||||
| ElementAttr::CustomAttrExpression { .. }
|
||||
| ElementAttr::EventTokens { .. } => {
|
||||
let idx = match mapping {
|
||||
Some(mapping) => mapping.get_attribute_idx(&attr.attr)?,
|
||||
None => self.dynamic_attributes.len(),
|
||||
};
|
||||
self.dynamic_attributes.push(attr);
|
||||
|
||||
if self.attr_paths.len() <= idx {
|
||||
self.attr_paths.resize_with(idx + 1, Vec::new);
|
||||
}
|
||||
self.attr_paths[idx] = self.current_path.clone();
|
||||
static_attrs.push(TemplateAttribute::Dynamic { id: idx })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let mut children = Vec::new();
|
||||
for (idx, root) in el.children.iter().enumerate() {
|
||||
self.current_path.push(idx as u8);
|
||||
children.push(self.update_node::<Ctx>(root, mapping)?);
|
||||
self.current_path.pop();
|
||||
}
|
||||
|
||||
let (tag, namespace) = Ctx::map_element(&element_name_rust)
|
||||
.unwrap_or((intern(element_name_rust.as_str()), None));
|
||||
Some(TemplateNode::Element {
|
||||
tag,
|
||||
namespace,
|
||||
attrs: intern(static_attrs.into_boxed_slice()),
|
||||
children: intern(children.as_slice()),
|
||||
})
|
||||
}
|
||||
|
||||
BodyNode::Text(text) if text.is_static() => {
|
||||
let text = text.source.as_ref().unwrap();
|
||||
Some(TemplateNode::Text {
|
||||
text: intern(text.value().as_str()),
|
||||
})
|
||||
}
|
||||
|
||||
BodyNode::RawExpr(_)
|
||||
| BodyNode::Text(_)
|
||||
| BodyNode::ForLoop(_)
|
||||
| BodyNode::IfChain(_)
|
||||
| BodyNode::Component(_) => {
|
||||
let idx = match mapping {
|
||||
Some(mapping) => mapping.get_node_idx(root)?,
|
||||
None => self.dynamic_nodes.len(),
|
||||
};
|
||||
self.dynamic_nodes.push(root);
|
||||
|
||||
if self.node_paths.len() <= idx {
|
||||
self.node_paths.resize_with(idx + 1, Vec::new);
|
||||
}
|
||||
self.node_paths[idx] = self.current_path.clone();
|
||||
|
||||
Some(match root {
|
||||
BodyNode::Text(_) => TemplateNode::DynamicText { id: idx },
|
||||
_ => TemplateNode::Dynamic { id: idx },
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_static_node(&mut self, root: &'a BodyNode) -> TokenStream2 {
|
||||
match root {
|
||||
BodyNode::Element(el) => {
|
||||
let el_name = &el.name;
|
||||
|
||||
// dynamic attributes
|
||||
// [0]
|
||||
// [0, 1]
|
||||
// [0, 1]
|
||||
// [0, 1]
|
||||
// [0, 1, 2]
|
||||
// [0, 2]
|
||||
// [0, 2, 1]
|
||||
|
||||
let static_attrs = el.attributes.iter().map(|attr| match &attr.attr {
|
||||
ElementAttr::AttrText { name, value } if value.is_static() => {
|
||||
let value = value.to_static().unwrap();
|
||||
|
@ -273,3 +513,209 @@ impl<'a> DynamicContext<'a> {
|
|||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn create_template() {
|
||||
let input = quote! {
|
||||
svg {
|
||||
width: 100,
|
||||
height: "100px",
|
||||
"width2": 100,
|
||||
"height2": "100px",
|
||||
p {
|
||||
"hello world"
|
||||
}
|
||||
(0..10).map(|i| rsx!{"{i}"})
|
||||
}
|
||||
};
|
||||
|
||||
struct Mock;
|
||||
|
||||
impl HotReloadingContext for Mock {
|
||||
fn map_attribute(
|
||||
element_name_rust: &str,
|
||||
attribute_name_rust: &str,
|
||||
) -> Option<(&'static str, Option<&'static str>)> {
|
||||
match element_name_rust {
|
||||
"svg" => match attribute_name_rust {
|
||||
"width" => Some(("width", Some("style"))),
|
||||
"height" => Some(("height", Some("style"))),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_element(element_name_rust: &str) -> Option<(&'static str, Option<&'static str>)> {
|
||||
match element_name_rust {
|
||||
"svg" => Some(("svg", Some("svg"))),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let call_body: CallBody = syn::parse2(input).unwrap();
|
||||
|
||||
let template = call_body.update_template::<Mock>(None, "testing").unwrap();
|
||||
|
||||
dbg!(template);
|
||||
|
||||
assert_eq!(
|
||||
template,
|
||||
Template {
|
||||
name: "testing",
|
||||
roots: &[TemplateNode::Element {
|
||||
tag: "svg",
|
||||
namespace: Some("svg"),
|
||||
attrs: &[
|
||||
TemplateAttribute::Dynamic { id: 0 },
|
||||
TemplateAttribute::Static {
|
||||
name: "height",
|
||||
namespace: Some("style"),
|
||||
value: "100px",
|
||||
},
|
||||
TemplateAttribute::Dynamic { id: 1 },
|
||||
TemplateAttribute::Static {
|
||||
name: "height2",
|
||||
namespace: None,
|
||||
value: "100px",
|
||||
},
|
||||
],
|
||||
children: &[
|
||||
TemplateNode::Element {
|
||||
tag: "p",
|
||||
namespace: None,
|
||||
attrs: &[],
|
||||
children: &[TemplateNode::Text {
|
||||
text: "hello world",
|
||||
}],
|
||||
},
|
||||
TemplateNode::Dynamic { id: 0 }
|
||||
],
|
||||
}],
|
||||
node_paths: &[&[0, 1,],],
|
||||
attr_paths: &[&[0,], &[0,],],
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn diff_template() {
|
||||
use dioxus_core::Scope;
|
||||
#[allow(unused, non_snake_case)]
|
||||
fn Comp(_: Scope) -> dioxus_core::Element {
|
||||
None
|
||||
}
|
||||
|
||||
let input = quote! {
|
||||
svg {
|
||||
width: 100,
|
||||
height: "100px",
|
||||
"width2": 100,
|
||||
"height2": "100px",
|
||||
p {
|
||||
"hello world"
|
||||
}
|
||||
(0..10).map(|i| rsx!{"{i}"}),
|
||||
(0..10).map(|i| rsx!{"{i}"}),
|
||||
(0..11).map(|i| rsx!{"{i}"}),
|
||||
Comp{}
|
||||
}
|
||||
};
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Mock;
|
||||
|
||||
impl HotReloadingContext for Mock {
|
||||
fn map_attribute(
|
||||
element_name_rust: &str,
|
||||
attribute_name_rust: &str,
|
||||
) -> Option<(&'static str, Option<&'static str>)> {
|
||||
match element_name_rust {
|
||||
"svg" => match attribute_name_rust {
|
||||
"width" => Some(("width", Some("style"))),
|
||||
"height" => Some(("height", Some("style"))),
|
||||
_ => None,
|
||||
},
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
fn map_element(element_name_rust: &str) -> Option<(&'static str, Option<&'static str>)> {
|
||||
match element_name_rust {
|
||||
"svg" => Some(("svg", Some("svg"))),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let call_body1: CallBody = syn::parse2(input).unwrap();
|
||||
|
||||
let template = call_body1.update_template::<Mock>(None, "testing").unwrap();
|
||||
dbg!(template);
|
||||
|
||||
// scrambling the attributes should not cause a full rebuild
|
||||
let input = quote! {
|
||||
div {
|
||||
"width2": 100,
|
||||
height: "100px",
|
||||
"height2": "100px",
|
||||
width: 100,
|
||||
Comp{}
|
||||
(0..11).map(|i| rsx!{"{i}"}),
|
||||
(0..10).map(|i| rsx!{"{i}"}),
|
||||
(0..10).map(|i| rsx!{"{i}"}),
|
||||
p {
|
||||
"hello world"
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let call_body2: CallBody = syn::parse2(input).unwrap();
|
||||
|
||||
let template = call_body2
|
||||
.update_template::<Mock>(Some(call_body1), "testing")
|
||||
.unwrap();
|
||||
dbg!(template);
|
||||
|
||||
assert_eq!(
|
||||
template,
|
||||
Template {
|
||||
name: "testing",
|
||||
roots: &[TemplateNode::Element {
|
||||
tag: "div",
|
||||
namespace: None,
|
||||
attrs: &[
|
||||
TemplateAttribute::Dynamic { id: 1 },
|
||||
TemplateAttribute::Static {
|
||||
name: "height",
|
||||
namespace: None,
|
||||
value: "100px",
|
||||
},
|
||||
TemplateAttribute::Static {
|
||||
name: "height2",
|
||||
namespace: None,
|
||||
value: "100px",
|
||||
},
|
||||
TemplateAttribute::Dynamic { id: 0 },
|
||||
],
|
||||
children: &[
|
||||
TemplateNode::Dynamic { id: 3 },
|
||||
TemplateNode::Dynamic { id: 2 },
|
||||
TemplateNode::Dynamic { id: 1 },
|
||||
TemplateNode::Dynamic { id: 0 },
|
||||
TemplateNode::Element {
|
||||
tag: "p",
|
||||
namespace: None,
|
||||
attrs: &[],
|
||||
children: &[TemplateNode::Text {
|
||||
text: "hello world",
|
||||
}],
|
||||
},
|
||||
],
|
||||
}],
|
||||
node_paths: &[&[0, 3], &[0, 2], &[0, 1], &[0, 0]],
|
||||
attr_paths: &[&[0], &[0]]
|
||||
},
|
||||
)
|
||||
}
|
||||
|
|
|
@ -1,5 +1,3 @@
|
|||
// use crate::{raw_expr::RawExprNode, text::TextNode};
|
||||
|
||||
use super::*;
|
||||
|
||||
use proc_macro2::{Span, TokenStream as TokenStream2};
|
||||
|
@ -143,7 +141,7 @@ impl ToTokens for BodyNode {
|
|||
pat, expr, body, ..
|
||||
} = exp;
|
||||
|
||||
let renderer = TemplateRenderer { roots: body };
|
||||
let renderer: TemplateRenderer = TemplateRenderer { roots: body };
|
||||
|
||||
tokens.append_all(quote! {
|
||||
__cx.make_node(
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -36,13 +36,13 @@ impl StringCache {
|
|||
|
||||
let mut cur_path = vec![];
|
||||
|
||||
for (root_idx, root) in template.template.roots.iter().enumerate() {
|
||||
for (root_idx, root) in template.template.get().roots.iter().enumerate() {
|
||||
Self::recurse(root, &mut cur_path, root_idx, &mut chain)?;
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
segments: chain.segments,
|
||||
template: template.template,
|
||||
template: template.template.get(),
|
||||
})
|
||||
}
|
||||
|
||||
|
|
|
@ -66,7 +66,7 @@ impl Renderer {
|
|||
) -> std::fmt::Result {
|
||||
let entry = self
|
||||
.template_cache
|
||||
.entry(template.template.name)
|
||||
.entry(template.template.get().name)
|
||||
.or_insert_with(|| Rc::new(StringCache::from_template(template).unwrap()))
|
||||
.clone();
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@ fn main() {
|
|||
fn app(cx: Scope) -> Element {
|
||||
let hue = use_state(cx, || 0.0);
|
||||
let brightness = use_state(cx, || 0.0);
|
||||
let tui_query: &Query = cx.consume_context().unwrap();
|
||||
let tui_query: Query = cx.consume_context().unwrap();
|
||||
// disable templates so that every node has an id and can be queried
|
||||
cx.render(rsx! {
|
||||
div{
|
||||
|
@ -18,7 +18,7 @@ fn app(cx: Scope) -> Element {
|
|||
background_color: "hsl({hue}, 70%, {brightness}%)",
|
||||
onmousemove: move |evt| {
|
||||
if let RenderReturn::Ready(node) = cx.root_node() {
|
||||
if let Some(id) = node.root_ids[0].get() {
|
||||
if let Some(id) = node.root_ids.get(0){
|
||||
let node = tui_query.get(id);
|
||||
let Size{width, height} = node.size().unwrap();
|
||||
let pos = evt.inner().element_coordinates();
|
||||
|
|
|
@ -11,7 +11,7 @@ pub use input::*;
|
|||
|
||||
pub(crate) fn get_root_id<T>(cx: Scope<T>) -> Option<ElementId> {
|
||||
if let RenderReturn::Ready(sync) = cx.root_node() {
|
||||
sync.root_ids.get(0).and_then(|id| id.get())
|
||||
sync.root_ids.get(0)
|
||||
} else {
|
||||
None
|
||||
}
|
||||
|
|
|
@ -2,12 +2,13 @@
|
|||
|
||||
use futures_channel::mpsc::UnboundedReceiver;
|
||||
|
||||
use dioxus_core::Template;
|
||||
use wasm_bindgen::closure::Closure;
|
||||
use wasm_bindgen::JsCast;
|
||||
use web_sys::{MessageEvent, WebSocket};
|
||||
|
||||
#[cfg(not(debug_assertions))]
|
||||
pub(crate) fn init() -> UnboundedReceiver<String> {
|
||||
pub(crate) fn init() -> UnboundedReceiver<Template<'static>> {
|
||||
let (tx, rx) = futures_channel::mpsc::unbounded();
|
||||
|
||||
std::mem::forget(tx);
|
||||
|
@ -16,7 +17,8 @@ pub(crate) fn init() -> UnboundedReceiver<String> {
|
|||
}
|
||||
|
||||
#[cfg(debug_assertions)]
|
||||
pub(crate) fn init() -> UnboundedReceiver<String> {
|
||||
pub(crate) fn init() -> UnboundedReceiver<Template<'static>> {
|
||||
use core::panic;
|
||||
use std::convert::TryInto;
|
||||
|
||||
let window = web_sys::window().unwrap();
|
||||
|
@ -39,8 +41,12 @@ pub(crate) fn init() -> UnboundedReceiver<String> {
|
|||
// change the rsx when new data is received
|
||||
let cl = Closure::wrap(Box::new(move |e: MessageEvent| {
|
||||
if let Ok(text) = e.data().dyn_into::<js_sys::JsString>() {
|
||||
if let Ok(val) = text.try_into() {
|
||||
_ = tx.unbounded_send(val);
|
||||
let text: Result<String, _> = text.try_into();
|
||||
if let Ok(string) = text {
|
||||
match serde_json::from_str(Box::leak(string.into_boxed_str())) {
|
||||
Ok(template) => _ = tx.unbounded_send(template),
|
||||
Err(e) => panic!("Failed to parse template: {}", e),
|
||||
}
|
||||
}
|
||||
}
|
||||
}) as Box<dyn FnMut(MessageEvent)>);
|
||||
|
|
|
@ -202,19 +202,23 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
|
|||
|
||||
// if virtualdom has nothing, wait for it to have something before requesting idle time
|
||||
// if there is work then this future resolves immediately.
|
||||
let mut res = {
|
||||
let (mut res, template) = {
|
||||
let work = dom.wait_for_work().fuse();
|
||||
pin_mut!(work);
|
||||
|
||||
futures_util::select! {
|
||||
_ = work => None,
|
||||
_new_template = hotreload_rx.next() => {
|
||||
todo!("Implement hot reload");
|
||||
_ = work => (None, None),
|
||||
new_template = hotreload_rx.next() => {
|
||||
(None, new_template)
|
||||
}
|
||||
evt = rx.next() => evt
|
||||
evt = rx.next() => (evt, None)
|
||||
}
|
||||
};
|
||||
|
||||
if let Some(template) = template {
|
||||
dom.replace_template(template);
|
||||
}
|
||||
|
||||
// Dequeue all of the events from the channel in send order
|
||||
// todo: we should re-order these if possible
|
||||
while let Some(evt) = res {
|
||||
|
|
Loading…
Add table
Reference in a new issue