Merge branch 'master' into jk/add-miri-to-ci
This commit is contained in:
commit
13e58daa20
156 files changed
+3596
-2656
No files matched your search
@@ -1,12 +1,12 @@
|
||||
[package]
|
||||
name = "dioxus-autofmt"
|
||||
version = "0.0.0"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus-rsx = { path = "../rsx" }
|
||||
dioxus-rsx = { path = "../rsx", version = "0.0.2"}
|
||||
proc-macro2 = { version = "1.0.6", features = ["span-locations"] }
|
||||
quote = "1.0"
|
||||
syn = { version = "1.0.11", features = ["full", "extra-traits"] }
|
||||
|
||||
+14
-175
@@ -1,33 +1,18 @@
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
fmt::{Result, Write},
|
||||
};
|
||||
//! The output buffer that supports some helpful methods
|
||||
//! These are separate from the input so we can lend references between the two
|
||||
//!
|
||||
//!
|
||||
//!
|
||||
|
||||
use dioxus_rsx::{BodyNode, ElementAttr, ElementAttrNamed, IfmtInput};
|
||||
use proc_macro2::{LineColumn, Span};
|
||||
use syn::{spanned::Spanned, Expr};
|
||||
use std::fmt::{Result, Write};
|
||||
|
||||
#[derive(Default, Debug)]
|
||||
use dioxus_rsx::IfmtInput;
|
||||
|
||||
/// The output buffer that tracks indent and string
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Buffer {
|
||||
pub src: Vec<String>,
|
||||
pub cached_formats: HashMap<Location, String>,
|
||||
pub buf: String,
|
||||
pub indent: usize,
|
||||
pub comments: VecDeque<usize>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
|
||||
pub struct Location {
|
||||
pub line: usize,
|
||||
pub col: usize,
|
||||
}
|
||||
impl Location {
|
||||
pub fn new(start: LineColumn) -> Self {
|
||||
Self {
|
||||
line: start.line,
|
||||
col: start.column,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
@@ -62,160 +47,14 @@ impl Buffer {
|
||||
writeln!(self.buf)
|
||||
}
|
||||
|
||||
// Expects to be written directly into place
|
||||
pub fn write_ident(&mut self, node: &BodyNode) -> Result {
|
||||
match node {
|
||||
BodyNode::Element(el) => self.write_element(el),
|
||||
BodyNode::Component(component) => self.write_component(component),
|
||||
BodyNode::Text(text) => self.write_text(text),
|
||||
BodyNode::RawExpr(exp) => self.write_raw_expr(exp),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn write_text(&mut self, text: &IfmtInput) -> Result {
|
||||
write!(self.buf, "\"{}\"", text.source.as_ref().unwrap().value())
|
||||
}
|
||||
|
||||
pub fn consume(self) -> Option<String> {
|
||||
Some(self.buf)
|
||||
}
|
||||
|
||||
pub fn write_comments(&mut self, child: Span) -> Result {
|
||||
// collect all comments upwards
|
||||
let start = child.start();
|
||||
let line_start = start.line - 1;
|
||||
|
||||
for (id, line) in self.src[..line_start].iter().enumerate().rev() {
|
||||
if line.trim().starts_with("//") || line.is_empty() {
|
||||
if id != 0 {
|
||||
self.comments.push_front(id);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut last_was_empty = false;
|
||||
while let Some(comment_line) = self.comments.pop_front() {
|
||||
let line = &self.src[comment_line];
|
||||
if line.is_empty() {
|
||||
if !last_was_empty {
|
||||
self.new_line()?;
|
||||
}
|
||||
last_was_empty = true;
|
||||
} else {
|
||||
last_was_empty = false;
|
||||
self.tabbed_line()?;
|
||||
write!(self.buf, "{}", self.src[comment_line].trim())?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Push out the indent level and write each component, line by line
|
||||
pub fn write_body_indented(&mut self, children: &[BodyNode]) -> Result {
|
||||
self.indent += 1;
|
||||
|
||||
self.write_body_no_indent(children)?;
|
||||
|
||||
self.indent -= 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_body_no_indent(&mut self, children: &[BodyNode]) -> Result {
|
||||
let last_child = children.len();
|
||||
|
||||
for (idx, child) in children.iter().enumerate() {
|
||||
match child {
|
||||
// check if the expr is a short
|
||||
BodyNode::RawExpr { .. } => {
|
||||
self.tabbed_line()?;
|
||||
self.write_ident(child)?;
|
||||
if idx != last_child - 1 {
|
||||
write!(self.buf, ",")?;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
if self.current_span_is_primary(child.span()) {
|
||||
self.write_comments(child.span())?;
|
||||
}
|
||||
self.tabbed_line()?;
|
||||
self.write_ident(child)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn is_short_attrs(&mut self, attributes: &[ElementAttrNamed]) -> usize {
|
||||
let mut total = 0;
|
||||
|
||||
for attr in attributes {
|
||||
if self.current_span_is_primary(attr.attr.start()) {
|
||||
'line: for line in self.src[..attr.attr.start().start().line - 1].iter().rev() {
|
||||
match (line.trim().starts_with("//"), line.is_empty()) {
|
||||
(true, _) => return 100000,
|
||||
(_, true) => continue 'line,
|
||||
_ => break 'line,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
total += match &attr.attr {
|
||||
ElementAttr::AttrText { value, name } => {
|
||||
value.source.as_ref().unwrap().value().len() + name.span().line_length() + 3
|
||||
}
|
||||
ElementAttr::AttrExpression { name, value } => {
|
||||
value.span().line_length() + name.span().line_length() + 3
|
||||
}
|
||||
ElementAttr::CustomAttrText { value, name } => {
|
||||
value.source.as_ref().unwrap().value().len() + name.value().len() + 3
|
||||
}
|
||||
ElementAttr::CustomAttrExpression { name, value } => {
|
||||
name.value().len() + value.span().line_length() + 3
|
||||
}
|
||||
ElementAttr::EventTokens { tokens, name } => {
|
||||
let location = Location::new(tokens.span().start());
|
||||
|
||||
let len = if let std::collections::hash_map::Entry::Vacant(e) =
|
||||
self.cached_formats.entry(location)
|
||||
{
|
||||
let formatted = prettyplease::unparse_expr(tokens);
|
||||
let len = if formatted.contains('\n') {
|
||||
10000
|
||||
} else {
|
||||
formatted.len()
|
||||
};
|
||||
e.insert(formatted);
|
||||
len
|
||||
} else {
|
||||
self.cached_formats[&location].len()
|
||||
};
|
||||
|
||||
len + name.span().line_length() + 3
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
total
|
||||
}
|
||||
|
||||
pub fn retrieve_formatted_expr(&mut self, expr: &Expr) -> &str {
|
||||
self.cached_formats
|
||||
.entry(Location::new(expr.span().start()))
|
||||
.or_insert_with(|| prettyplease::unparse_expr(expr))
|
||||
.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
trait SpanLength {
|
||||
fn line_length(&self) -> usize;
|
||||
}
|
||||
impl SpanLength for Span {
|
||||
fn line_length(&self) -> usize {
|
||||
self.end().line - self.start().line
|
||||
impl std::fmt::Write for Buffer {
|
||||
fn write_str(&mut self, s: &str) -> std::fmt::Result {
|
||||
self.buf.push_str(s);
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
use crate::{buffer::Location, Buffer};
|
||||
use crate::{writer::Location, Writer};
|
||||
use dioxus_rsx::*;
|
||||
use quote::ToTokens;
|
||||
use std::fmt::{Result, Write};
|
||||
@@ -19,7 +19,7 @@ enum ShortOptimization {
|
||||
NoOpt,
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
impl Writer {
|
||||
pub fn write_component(
|
||||
&mut self,
|
||||
Component {
|
||||
@@ -28,6 +28,7 @@ impl Buffer {
|
||||
children,
|
||||
manual_props,
|
||||
prop_gen_args,
|
||||
..
|
||||
}: &Component,
|
||||
) -> Result {
|
||||
self.write_component_name(name, prop_gen_args)?;
|
||||
@@ -82,46 +83,46 @@ impl Buffer {
|
||||
match opt_level {
|
||||
ShortOptimization::Empty => {}
|
||||
ShortOptimization::Oneliner => {
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
|
||||
self.write_component_fields(fields, manual_props, true)?;
|
||||
|
||||
if !children.is_empty() && !fields.is_empty() {
|
||||
write!(self.buf, ", ")?;
|
||||
write!(self.out, ", ")?;
|
||||
}
|
||||
|
||||
for child in children {
|
||||
self.write_ident(child)?;
|
||||
}
|
||||
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
|
||||
ShortOptimization::PropsOnTop => {
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
self.write_component_fields(fields, manual_props, true)?;
|
||||
|
||||
if !children.is_empty() && !fields.is_empty() {
|
||||
write!(self.buf, ",")?;
|
||||
write!(self.out, ",")?;
|
||||
}
|
||||
|
||||
self.write_body_indented(children)?;
|
||||
self.tabbed_line()?;
|
||||
self.out.tabbed_line()?;
|
||||
}
|
||||
|
||||
ShortOptimization::NoOpt => {
|
||||
self.write_component_fields(fields, manual_props, false)?;
|
||||
|
||||
if !children.is_empty() && !fields.is_empty() {
|
||||
write!(self.buf, ",")?;
|
||||
write!(self.out, ",")?;
|
||||
}
|
||||
|
||||
self.write_body_indented(children)?;
|
||||
self.tabbed_line()?;
|
||||
self.out.tabbed_line()?;
|
||||
}
|
||||
}
|
||||
|
||||
write!(self.buf, "}}")?;
|
||||
write!(self.out, "}}")?;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
@@ -133,16 +134,16 @@ impl Buffer {
|
||||
let mut name = name.to_token_stream().to_string();
|
||||
name.retain(|c| !c.is_whitespace());
|
||||
|
||||
write!(self.buf, "{name}")?;
|
||||
write!(self.out, "{name}")?;
|
||||
|
||||
if let Some(generics) = generics {
|
||||
let mut written = generics.to_token_stream().to_string();
|
||||
written.retain(|c| !c.is_whitespace());
|
||||
|
||||
write!(self.buf, "{}", written)?;
|
||||
write!(self.out, "{}", written)?;
|
||||
}
|
||||
|
||||
write!(self.buf, " {{")?;
|
||||
write!(self.out, " {{")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -157,18 +158,18 @@ impl Buffer {
|
||||
|
||||
while let Some(field) = field_iter.next() {
|
||||
if !sameline {
|
||||
self.indented_tabbed_line()?;
|
||||
self.out.indented_tabbed_line()?;
|
||||
}
|
||||
|
||||
let name = &field.name;
|
||||
match &field.content {
|
||||
ContentField::ManExpr(exp) => {
|
||||
let out = prettyplease::unparse_expr(exp);
|
||||
write!(self.buf, "{}: {}", name, out)?;
|
||||
write!(self.out, "{}: {}", name, out)?;
|
||||
}
|
||||
ContentField::Formatted(s) => {
|
||||
write!(
|
||||
self.buf,
|
||||
self.out,
|
||||
"{}: \"{}\"",
|
||||
name,
|
||||
s.source.as_ref().unwrap().value()
|
||||
@@ -178,27 +179,27 @@ impl Buffer {
|
||||
let out = prettyplease::unparse_expr(exp);
|
||||
let mut lines = out.split('\n').peekable();
|
||||
let first = lines.next().unwrap();
|
||||
write!(self.buf, "{}: {}", name, first)?;
|
||||
write!(self.out, "{}: {}", name, first)?;
|
||||
for line in lines {
|
||||
self.new_line()?;
|
||||
self.indented_tab()?;
|
||||
write!(self.buf, "{}", line)?;
|
||||
self.out.new_line()?;
|
||||
self.out.indented_tab()?;
|
||||
write!(self.out, "{}", line)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if field_iter.peek().is_some() || manual_props.is_some() {
|
||||
write!(self.buf, ",")?;
|
||||
write!(self.out, ",")?;
|
||||
|
||||
if sameline {
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if let Some(exp) = manual_props {
|
||||
if !sameline {
|
||||
self.indented_tabbed_line()?;
|
||||
self.out.indented_tabbed_line()?;
|
||||
}
|
||||
self.write_manual_props(exp)?;
|
||||
}
|
||||
@@ -258,10 +259,10 @@ impl Buffer {
|
||||
|
||||
let first_line = lines.next().unwrap();
|
||||
|
||||
write!(self.buf, "..{first_line}")?;
|
||||
write!(self.out, "..{first_line}")?;
|
||||
for line in lines {
|
||||
self.indented_tabbed_line()?;
|
||||
write!(self.buf, "{line}")?;
|
||||
self.out.indented_tabbed_line()?;
|
||||
write!(self.out, "{line}")?;
|
||||
}
|
||||
|
||||
Ok(())
|
||||
|
||||
+101
-45
@@ -1,35 +1,56 @@
|
||||
use crate::Buffer;
|
||||
use crate::Writer;
|
||||
use dioxus_rsx::*;
|
||||
use proc_macro2::Span;
|
||||
use std::{fmt::Result, fmt::Write};
|
||||
use syn::{spanned::Spanned, Expr};
|
||||
use std::{
|
||||
fmt::Result,
|
||||
fmt::{self, Write},
|
||||
};
|
||||
use syn::{spanned::Spanned, token::Brace, Expr};
|
||||
|
||||
#[derive(Debug)]
|
||||
enum ShortOptimization {
|
||||
// Special because we want to print the closing bracket immediately
|
||||
/// Special because we want to print the closing bracket immediately
|
||||
///
|
||||
/// IE
|
||||
/// `div {}` instead of `div { }`
|
||||
Empty,
|
||||
|
||||
// Special optimization to put everything on the same line
|
||||
/// Special optimization to put everything on the same line and add some buffer spaces
|
||||
///
|
||||
/// IE
|
||||
///
|
||||
/// `div { "asdasd" }` instead of a multiline variant
|
||||
Oneliner,
|
||||
|
||||
// Optimization where children flow but props remain fixed on top
|
||||
/// Optimization where children flow but props remain fixed on top
|
||||
PropsOnTop,
|
||||
|
||||
// The noisiest optimization where everything flows
|
||||
/// The noisiest optimization where everything flows
|
||||
NoOpt,
|
||||
}
|
||||
|
||||
impl Buffer {
|
||||
pub fn write_element(
|
||||
&mut self,
|
||||
Element {
|
||||
/*
|
||||
// whitespace
|
||||
div {
|
||||
// some whitespace
|
||||
class: "asdasd"
|
||||
|
||||
// whjiot
|
||||
asdasd // whitespace
|
||||
}
|
||||
*/
|
||||
|
||||
impl Writer {
|
||||
pub fn write_element(&mut self, el: &Element) -> Result {
|
||||
let Element {
|
||||
name,
|
||||
key,
|
||||
attributes,
|
||||
children,
|
||||
_is_static,
|
||||
}: &Element,
|
||||
) -> Result {
|
||||
brace,
|
||||
} = el;
|
||||
|
||||
/*
|
||||
1. Write the tag
|
||||
2. Write the key
|
||||
@@ -37,7 +58,7 @@ impl Buffer {
|
||||
4. Write the children
|
||||
*/
|
||||
|
||||
write!(self.buf, "{name} {{")?;
|
||||
write!(self.out, "{name} {{")?;
|
||||
|
||||
// decide if we have any special optimizations
|
||||
// Default with none, opt the cases in one-by-one
|
||||
@@ -70,6 +91,9 @@ impl Buffer {
|
||||
// If there's nothing at all, empty optimization
|
||||
if attributes.is_empty() && children.is_empty() && key.is_none() {
|
||||
opt_level = ShortOptimization::Empty;
|
||||
|
||||
// Write comments if they exist
|
||||
self.write_todo_body(brace)?;
|
||||
}
|
||||
|
||||
// multiline handlers bump everything down
|
||||
@@ -80,55 +104,56 @@ impl Buffer {
|
||||
match opt_level {
|
||||
ShortOptimization::Empty => {}
|
||||
ShortOptimization::Oneliner => {
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
|
||||
self.write_attributes(attributes, key, true)?;
|
||||
|
||||
if !children.is_empty() && (!attributes.is_empty() || key.is_some()) {
|
||||
write!(self.buf, ", ")?;
|
||||
write!(self.out, ", ")?;
|
||||
}
|
||||
|
||||
for (id, child) in children.iter().enumerate() {
|
||||
self.write_ident(child)?;
|
||||
if id != children.len() - 1 && children.len() > 1 {
|
||||
write!(self.buf, ", ")?;
|
||||
write!(self.out, ", ")?;
|
||||
}
|
||||
}
|
||||
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
|
||||
ShortOptimization::PropsOnTop => {
|
||||
if !attributes.is_empty() || key.is_some() {
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
self.write_attributes(attributes, key, true)?;
|
||||
|
||||
if !children.is_empty() && (!attributes.is_empty() || key.is_some()) {
|
||||
write!(self.buf, ",")?;
|
||||
write!(self.out, ",")?;
|
||||
}
|
||||
|
||||
if !children.is_empty() {
|
||||
self.write_body_indented(children)?;
|
||||
}
|
||||
self.tabbed_line()?;
|
||||
self.out.tabbed_line()?;
|
||||
}
|
||||
|
||||
ShortOptimization::NoOpt => {
|
||||
self.write_attributes(attributes, key, false)?;
|
||||
|
||||
if !children.is_empty() && (!attributes.is_empty() || key.is_some()) {
|
||||
write!(self.buf, ",")?;
|
||||
write!(self.out, ",")?;
|
||||
}
|
||||
|
||||
if !children.is_empty() {
|
||||
self.write_body_indented(children)?;
|
||||
}
|
||||
self.tabbed_line()?;
|
||||
|
||||
self.out.tabbed_line()?;
|
||||
}
|
||||
}
|
||||
|
||||
write!(self.buf, "}}")?;
|
||||
write!(self.out, "}}")?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
@@ -143,39 +168,39 @@ impl Buffer {
|
||||
|
||||
if let Some(key) = key {
|
||||
if !sameline {
|
||||
self.indented_tabbed_line()?;
|
||||
self.out.indented_tabbed_line()?;
|
||||
}
|
||||
write!(
|
||||
self.buf,
|
||||
self.out,
|
||||
"key: \"{}\"",
|
||||
key.source.as_ref().unwrap().value()
|
||||
)?;
|
||||
if !attributes.is_empty() {
|
||||
write!(self.buf, ",")?;
|
||||
write!(self.out, ",")?;
|
||||
if sameline {
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
while let Some(attr) = attr_iter.next() {
|
||||
self.indent += 1;
|
||||
self.out.indent += 1;
|
||||
if !sameline {
|
||||
self.write_comments(attr.attr.start())?;
|
||||
}
|
||||
self.indent -= 1;
|
||||
self.out.indent -= 1;
|
||||
|
||||
if !sameline {
|
||||
self.indented_tabbed_line()?;
|
||||
self.out.indented_tabbed_line()?;
|
||||
}
|
||||
|
||||
self.write_attribute(attr)?;
|
||||
|
||||
if attr_iter.peek().is_some() {
|
||||
write!(self.buf, ",")?;
|
||||
write!(self.out, ",")?;
|
||||
|
||||
if sameline {
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -187,19 +212,19 @@ impl Buffer {
|
||||
match &attr.attr {
|
||||
ElementAttr::AttrText { name, value } => {
|
||||
write!(
|
||||
self.buf,
|
||||
self.out,
|
||||
"{name}: \"{value}\"",
|
||||
value = value.source.as_ref().unwrap().value()
|
||||
)?;
|
||||
}
|
||||
ElementAttr::AttrExpression { name, value } => {
|
||||
let out = prettyplease::unparse_expr(value);
|
||||
write!(self.buf, "{}: {}", name, out)?;
|
||||
write!(self.out, "{}: {}", name, out)?;
|
||||
}
|
||||
|
||||
ElementAttr::CustomAttrText { name, value } => {
|
||||
write!(
|
||||
self.buf,
|
||||
self.out,
|
||||
"\"{name}\": \"{value}\"",
|
||||
name = name.value(),
|
||||
value = value.source.as_ref().unwrap().value()
|
||||
@@ -208,7 +233,7 @@ impl Buffer {
|
||||
|
||||
ElementAttr::CustomAttrExpression { name, value } => {
|
||||
let out = prettyplease::unparse_expr(value);
|
||||
write!(self.buf, "\"{}\": {}", name.value(), out)?;
|
||||
write!(self.out, "\"{}\": {}", name.value(), out)?;
|
||||
}
|
||||
|
||||
ElementAttr::EventTokens { name, tokens } => {
|
||||
@@ -220,17 +245,17 @@ impl Buffer {
|
||||
// a one-liner for whatever reason
|
||||
// Does not need a new line
|
||||
if lines.peek().is_none() {
|
||||
write!(self.buf, "{}: {}", name, first)?;
|
||||
write!(self.out, "{}: {}", name, first)?;
|
||||
} else {
|
||||
writeln!(self.buf, "{}: {}", name, first)?;
|
||||
writeln!(self.out, "{}: {}", name, first)?;
|
||||
|
||||
while let Some(line) = lines.next() {
|
||||
self.indented_tab()?;
|
||||
write!(self.buf, "{}", line)?;
|
||||
self.out.indented_tab()?;
|
||||
write!(self.out, "{}", line)?;
|
||||
if lines.peek().is_none() {
|
||||
write!(self.buf, "")?;
|
||||
write!(self.out, "")?;
|
||||
} else {
|
||||
writeln!(self.buf)?;
|
||||
writeln!(self.out)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -254,8 +279,6 @@ impl Buffer {
|
||||
""
|
||||
};
|
||||
|
||||
// dbg!(beginning);
|
||||
|
||||
beginning.is_empty()
|
||||
}
|
||||
|
||||
@@ -268,6 +291,10 @@ impl Buffer {
|
||||
if children.is_empty() {
|
||||
// todo: allow elements with comments but no children
|
||||
// like div { /* comment */ }
|
||||
// or
|
||||
// div {
|
||||
// // some helpful
|
||||
// }
|
||||
return Some(0);
|
||||
}
|
||||
|
||||
@@ -342,6 +369,35 @@ impl Buffer {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/// empty everything except for some comments
|
||||
fn write_todo_body(&mut self, brace: &Brace) -> fmt::Result {
|
||||
let span = brace.span.span();
|
||||
let start = span.start();
|
||||
let end = span.end();
|
||||
|
||||
if start.line == end.line {
|
||||
return Ok(());
|
||||
}
|
||||
|
||||
writeln!(self.out)?;
|
||||
|
||||
for idx in start.line..end.line {
|
||||
let line = &self.src[idx];
|
||||
if line.trim().starts_with("//") {
|
||||
for _ in 0..self.out.indent + 1 {
|
||||
write!(self.out, " ")?
|
||||
}
|
||||
writeln!(self.out, "{}", line.trim()).unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
for _ in 0..self.out.indent {
|
||||
write!(self.out, " ")?
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
fn get_expr_length(expr: &Expr) -> Option<usize> {
|
||||
|
||||
@@ -1,45 +1,23 @@
|
||||
//! pretty printer for rsx!
|
||||
use std::fmt::{Result, Write};
|
||||
|
||||
use crate::Buffer;
|
||||
use crate::Writer;
|
||||
|
||||
impl Buffer {
|
||||
impl Writer {
|
||||
pub fn write_raw_expr(&mut self, exp: &syn::Expr) -> Result {
|
||||
/*
|
||||
We want to normalize the expr to the appropriate indent level.
|
||||
*/
|
||||
|
||||
// in a perfect world, just fire up the rust pretty printer
|
||||
// pretty_print_rust_code_as_if_it_were_rustfmt()
|
||||
|
||||
use syn::spanned::Spanned;
|
||||
let placement = exp.span();
|
||||
let start = placement.start();
|
||||
let end = placement.end();
|
||||
// let num_spaces_desired = (self.indent * 4) as isize;
|
||||
|
||||
// print comments
|
||||
// let mut queued_comments = vec![];
|
||||
// let mut offset = 2;
|
||||
// loop {
|
||||
// let line = &self.src[start.line - offset];
|
||||
// if line.trim_start().starts_with("//") {
|
||||
// queued_comments.push(line);
|
||||
// } else {
|
||||
// break;
|
||||
// }
|
||||
|
||||
// offset += 1;
|
||||
// }
|
||||
// let had_comments = !queued_comments.is_empty();
|
||||
// for comment in queued_comments.into_iter().rev() {
|
||||
// writeln!(self.buf, "{}", comment)?;
|
||||
// }
|
||||
|
||||
// if the expr is on one line, just write it directly
|
||||
if start.line == end.line {
|
||||
write!(
|
||||
self.buf,
|
||||
self.out,
|
||||
"{}",
|
||||
&self.src[start.line - 1][start.column - 1..end.column].trim()
|
||||
)?;
|
||||
@@ -50,7 +28,7 @@ impl Buffer {
|
||||
// This involves unshifting the first line if it's aligned
|
||||
let first_line = &self.src[start.line - 1];
|
||||
write!(
|
||||
self.buf,
|
||||
self.out,
|
||||
"{}",
|
||||
&first_line[start.column - 1..first_line.len()].trim()
|
||||
)?;
|
||||
@@ -66,7 +44,7 @@ impl Buffer {
|
||||
};
|
||||
|
||||
for (id, line) in self.src[start.line..end.line].iter().enumerate() {
|
||||
writeln!(self.buf)?;
|
||||
writeln!(self.out)?;
|
||||
// trim the leading whitespace
|
||||
let line = match id {
|
||||
x if x == (end.line - start.line) - 1 => &line[..end.column],
|
||||
@@ -75,52 +53,17 @@ impl Buffer {
|
||||
|
||||
if offset < 0 {
|
||||
for _ in 0..-offset {
|
||||
write!(self.buf, " ")?;
|
||||
write!(self.out, " ")?;
|
||||
}
|
||||
|
||||
write!(self.buf, "{}", line)?;
|
||||
write!(self.out, "{}", line)?;
|
||||
} else {
|
||||
let offset = offset as usize;
|
||||
let right = &line[offset..];
|
||||
write!(self.buf, "{}", right)?;
|
||||
write!(self.out, "{}", right)?;
|
||||
}
|
||||
}
|
||||
|
||||
// let first = &self.src[start.line - 1];
|
||||
// let num_spaces_real = first.chars().take_while(|c| c.is_whitespace()).count() as isize;
|
||||
// let offset = num_spaces_real - num_spaces_desired;
|
||||
|
||||
// for (row, line) in self.src[start.line - 1..end.line].iter().enumerate() {
|
||||
// let line = match row {
|
||||
// 0 => &line[start.column - 1..],
|
||||
// a if a == (end.line - start.line) => &line[..end.column - 1],
|
||||
// _ => line,
|
||||
// };
|
||||
|
||||
// writeln!(self.buf)?;
|
||||
// // trim the leading whitespace
|
||||
// if offset < 0 {
|
||||
// for _ in 0..-offset {
|
||||
// write!(self.buf, " ")?;
|
||||
// }
|
||||
|
||||
// write!(self.buf, "{}", line)?;
|
||||
// } else {
|
||||
// let offset = offset as usize;
|
||||
// let right = &line[offset..];
|
||||
// write!(self.buf, "{}", right)?;
|
||||
// }
|
||||
// }
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
// :(
|
||||
// fn pretty_print_rust_code_as_if_it_were_rustfmt(code: &str) -> String {
|
||||
// let formatted = prettyplease::unparse_expr(exp);
|
||||
// for line in formatted.lines() {
|
||||
// write!(self.buf, "{}", line)?;
|
||||
// self.new_line()?;
|
||||
// }
|
||||
// }
|
||||
@@ -1,13 +1,14 @@
|
||||
use dioxus_rsx::CallBody;
|
||||
|
||||
use crate::buffer::*;
|
||||
use crate::util::*;
|
||||
use crate::writer::*;
|
||||
|
||||
mod buffer;
|
||||
mod component;
|
||||
mod element;
|
||||
mod expr;
|
||||
mod util;
|
||||
mod writer;
|
||||
|
||||
/// A modification to the original file to be applied by an IDE
|
||||
///
|
||||
@@ -101,10 +102,9 @@ pub fn fmt_file(contents: &str) -> Vec<FormattedBlock> {
|
||||
}
|
||||
|
||||
pub fn write_block_out(body: CallBody) -> Option<String> {
|
||||
let mut buf = Buffer {
|
||||
let mut buf = Writer {
|
||||
src: vec!["".to_string()],
|
||||
indent: 0,
|
||||
..Buffer::default()
|
||||
..Writer::default()
|
||||
};
|
||||
|
||||
// Oneliner optimization
|
||||
@@ -120,12 +120,13 @@ pub fn write_block_out(body: CallBody) -> Option<String> {
|
||||
pub fn fmt_block(block: &str, indent_level: usize) -> Option<String> {
|
||||
let body = syn::parse_str::<dioxus_rsx::CallBody>(block).ok()?;
|
||||
|
||||
let mut buf = Buffer {
|
||||
let mut buf = Writer {
|
||||
src: block.lines().map(|f| f.to_string()).collect(),
|
||||
indent: indent_level,
|
||||
..Buffer::default()
|
||||
..Writer::default()
|
||||
};
|
||||
|
||||
buf.out.indent = indent_level;
|
||||
|
||||
// Oneliner optimization
|
||||
if buf.is_short_children(&body.roots).is_some() {
|
||||
buf.write_ident(&body.roots[0]).unwrap();
|
||||
@@ -134,8 +135,8 @@ pub fn fmt_block(block: &str, indent_level: usize) -> Option<String> {
|
||||
}
|
||||
|
||||
// writing idents leaves the final line ended at the end of the last ident
|
||||
if buf.buf.contains('\n') {
|
||||
buf.new_line().unwrap();
|
||||
if buf.out.buf.contains('\n') {
|
||||
buf.out.new_line().unwrap();
|
||||
}
|
||||
|
||||
buf.consume()
|
||||
|
||||
@@ -0,0 +1,188 @@
|
||||
use dioxus_rsx::{BodyNode, ElementAttr, ElementAttrNamed};
|
||||
use proc_macro2::{LineColumn, Span};
|
||||
use std::{
|
||||
collections::{HashMap, VecDeque},
|
||||
fmt::{Result, Write},
|
||||
};
|
||||
use syn::{spanned::Spanned, Expr};
|
||||
|
||||
use crate::buffer::Buffer;
|
||||
|
||||
#[derive(Debug, Default)]
|
||||
pub struct Writer {
|
||||
pub src: Vec<String>,
|
||||
pub cached_formats: HashMap<Location, String>,
|
||||
pub comments: VecDeque<usize>,
|
||||
pub out: Buffer,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Hash, PartialEq, Eq, Debug)]
|
||||
pub struct Location {
|
||||
pub line: usize,
|
||||
pub col: usize,
|
||||
}
|
||||
impl Location {
|
||||
pub fn new(start: LineColumn) -> Self {
|
||||
Self {
|
||||
line: start.line,
|
||||
col: start.column,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Writer {
|
||||
// Expects to be written directly into place
|
||||
pub fn write_ident(&mut self, node: &BodyNode) -> Result {
|
||||
match node {
|
||||
BodyNode::Element(el) => self.write_element(el),
|
||||
BodyNode::Component(component) => self.write_component(component),
|
||||
BodyNode::Text(text) => self.out.write_text(text),
|
||||
BodyNode::RawExpr(exp) => self.write_raw_expr(exp),
|
||||
_ => Ok(()),
|
||||
}
|
||||
}
|
||||
|
||||
pub fn consume(self) -> Option<String> {
|
||||
Some(self.out.buf)
|
||||
}
|
||||
|
||||
pub fn write_comments(&mut self, child: Span) -> Result {
|
||||
// collect all comments upwards
|
||||
let start = child.start();
|
||||
let line_start = start.line - 1;
|
||||
|
||||
for (id, line) in self.src[..line_start].iter().enumerate().rev() {
|
||||
if line.trim().starts_with("//") || line.is_empty() {
|
||||
if id != 0 {
|
||||
self.comments.push_front(id);
|
||||
}
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
let mut last_was_empty = false;
|
||||
while let Some(comment_line) = self.comments.pop_front() {
|
||||
let line = &self.src[comment_line];
|
||||
if line.is_empty() {
|
||||
if !last_was_empty {
|
||||
self.out.new_line()?;
|
||||
}
|
||||
last_was_empty = true;
|
||||
} else {
|
||||
last_was_empty = false;
|
||||
self.out.tabbed_line()?;
|
||||
write!(self.out, "{}", self.src[comment_line].trim())?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Push out the indent level and write each component, line by line
|
||||
pub fn write_body_indented(&mut self, children: &[BodyNode]) -> Result {
|
||||
self.out.indent += 1;
|
||||
|
||||
self.write_body_no_indent(children)?;
|
||||
|
||||
self.out.indent -= 1;
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn write_body_no_indent(&mut self, children: &[BodyNode]) -> Result {
|
||||
let last_child = children.len();
|
||||
let iter = children.iter().peekable().enumerate();
|
||||
|
||||
for (idx, child) in iter {
|
||||
if self.current_span_is_primary(child.span()) {
|
||||
self.write_comments(child.span())?;
|
||||
}
|
||||
|
||||
match child {
|
||||
// check if the expr is a short
|
||||
BodyNode::RawExpr { .. } => {
|
||||
self.out.tabbed_line()?;
|
||||
self.write_ident(child)?;
|
||||
if idx != last_child - 1 {
|
||||
write!(self.out, ",")?;
|
||||
}
|
||||
}
|
||||
_ => {
|
||||
self.out.tabbed_line()?;
|
||||
self.write_ident(child)?;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub(crate) fn is_short_attrs(&mut self, attributes: &[ElementAttrNamed]) -> usize {
|
||||
let mut total = 0;
|
||||
|
||||
for attr in attributes {
|
||||
if self.current_span_is_primary(attr.attr.start()) {
|
||||
'line: for line in self.src[..attr.attr.start().start().line - 1].iter().rev() {
|
||||
match (line.trim().starts_with("//"), line.is_empty()) {
|
||||
(true, _) => return 100000,
|
||||
(_, true) => continue 'line,
|
||||
_ => break 'line,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
total += match &attr.attr {
|
||||
ElementAttr::AttrText { value, name } => {
|
||||
value.source.as_ref().unwrap().value().len() + name.span().line_length() + 3
|
||||
}
|
||||
ElementAttr::AttrExpression { name, value } => {
|
||||
value.span().line_length() + name.span().line_length() + 3
|
||||
}
|
||||
ElementAttr::CustomAttrText { value, name } => {
|
||||
value.source.as_ref().unwrap().value().len() + name.value().len() + 3
|
||||
}
|
||||
ElementAttr::CustomAttrExpression { name, value } => {
|
||||
name.value().len() + value.span().line_length() + 3
|
||||
}
|
||||
ElementAttr::EventTokens { tokens, name } => {
|
||||
let location = Location::new(tokens.span().start());
|
||||
|
||||
let len = if let std::collections::hash_map::Entry::Vacant(e) =
|
||||
self.cached_formats.entry(location)
|
||||
{
|
||||
let formatted = prettyplease::unparse_expr(tokens);
|
||||
let len = if formatted.contains('\n') {
|
||||
10000
|
||||
} else {
|
||||
formatted.len()
|
||||
};
|
||||
e.insert(formatted);
|
||||
len
|
||||
} else {
|
||||
self.cached_formats[&location].len()
|
||||
};
|
||||
|
||||
len + name.span().line_length() + 3
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
total
|
||||
}
|
||||
|
||||
pub fn retrieve_formatted_expr(&mut self, expr: &Expr) -> &str {
|
||||
self.cached_formats
|
||||
.entry(Location::new(expr.span().start()))
|
||||
.or_insert_with(|| prettyplease::unparse_expr(expr))
|
||||
.as_str()
|
||||
}
|
||||
}
|
||||
|
||||
trait SpanLength {
|
||||
fn line_length(&self) -> usize;
|
||||
}
|
||||
impl SpanLength for Span {
|
||||
fn line_length(&self) -> usize {
|
||||
self.end().line - self.start().line
|
||||
}
|
||||
}
|
||||
@@ -30,3 +30,6 @@ twoway! ("key" => key);
|
||||
|
||||
// Disabled because we can't handle comments on exprs yet
|
||||
twoway! ("multirsx" => multirsx);
|
||||
|
||||
// Disabled because we can't handle comments on exprs yet
|
||||
twoway! ("commentshard" => commentshard);
|
||||
@@ -0,0 +1,42 @@
|
||||
rsx! {
|
||||
// Comments
|
||||
div {
|
||||
// Comments
|
||||
class: "asdasd",
|
||||
|
||||
// Comments
|
||||
"hello world"
|
||||
|
||||
// Comments
|
||||
expr1,
|
||||
|
||||
// Comments
|
||||
expr2,
|
||||
|
||||
// Comments
|
||||
// Comments
|
||||
// Comments
|
||||
// Comments
|
||||
// Comments
|
||||
expr3,
|
||||
|
||||
div {
|
||||
// todo some work in here
|
||||
}
|
||||
|
||||
div {
|
||||
// todo some work in here
|
||||
// todo some work in here
|
||||
//
|
||||
// todo some work in here
|
||||
}
|
||||
|
||||
div {
|
||||
// todo some work in here
|
||||
class: "hello world",
|
||||
|
||||
// todo some work in here
|
||||
class: "hello world"
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dioxus-core-macro"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0"
|
||||
authors = ["Jonathan Kelley"]
|
||||
edition = "2021"
|
||||
description = "Core macro for Dioxus Virtual DOM"
|
||||
@@ -18,7 +18,7 @@ proc-macro = true
|
||||
proc-macro2 = { version = "1.0" }
|
||||
quote = "1.0"
|
||||
syn = { version = "1.0", features = ["full", "extra-traits"] }
|
||||
dioxus-rsx = { path = "../rsx", version = "0.0.1" }
|
||||
dioxus-rsx = { path = "../rsx", version = "0.0.2" }
|
||||
|
||||
# testing
|
||||
[dev-dependencies]
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dioxus-core"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0"
|
||||
authors = ["Jonathan Kelley"]
|
||||
edition = "2018"
|
||||
description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
|
||||
@@ -38,7 +38,7 @@ log = "0.4.17"
|
||||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "*", features = ["full"] }
|
||||
tokio = { version = "1", features = ["full"] }
|
||||
dioxus = { path = "../dioxus" }
|
||||
pretty_assertions = "1.3.0"
|
||||
|
||||
|
||||
@@ -153,11 +153,17 @@ impl VirtualDom {
|
||||
});
|
||||
|
||||
// Now that all the references are gone, we can safely drop our own references in our listeners.
|
||||
let mut listeners = scope.listeners.borrow_mut();
|
||||
let mut listeners = scope.attributes_to_drop.borrow_mut();
|
||||
listeners.drain(..).for_each(|listener| {
|
||||
let listener = unsafe { &*listener };
|
||||
if let AttributeValue::Listener(l) = &listener.value {
|
||||
_ = l.take();
|
||||
match &listener.value {
|
||||
AttributeValue::Listener(l) => {
|
||||
_ = l.take();
|
||||
}
|
||||
AttributeValue::Any(a) => {
|
||||
_ = a.take();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use crate::nodes::RenderReturn;
|
||||
use bumpalo::Bump;
|
||||
use std::cell::Cell;
|
||||
use std::cell::{Cell, UnsafeCell};
|
||||
|
||||
pub(crate) struct BumpFrame {
|
||||
pub bump: Bump,
|
||||
pub bump: UnsafeCell<Bump>,
|
||||
pub node: Cell<*const RenderReturn<'static>>,
|
||||
}
|
||||
|
||||
@@ -11,7 +11,7 @@ impl BumpFrame {
|
||||
pub(crate) fn new(capacity: usize) -> Self {
|
||||
let bump = Bump::with_capacity(capacity);
|
||||
Self {
|
||||
bump,
|
||||
bump: UnsafeCell::new(bump),
|
||||
node: Cell::new(std::ptr::null()),
|
||||
}
|
||||
}
|
||||
@@ -26,4 +26,13 @@ impl BumpFrame {
|
||||
|
||||
unsafe { std::mem::transmute(&*node) }
|
||||
}
|
||||
|
||||
pub(crate) fn bump(&self) -> &Bump {
|
||||
unsafe { &*self.bump.get() }
|
||||
}
|
||||
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
pub(crate) unsafe fn bump_mut(&self) -> &mut Bump {
|
||||
unsafe { &mut *self.bump.get() }
|
||||
}
|
||||
}
|
||||
+37
-41
@@ -1,5 +1,5 @@
|
||||
use crate::any_props::AnyProps;
|
||||
use crate::innerlude::{VComponent, VPlaceholder, VText};
|
||||
use crate::innerlude::{BorrowedAttributeValue, VComponent, VPlaceholder, VText};
|
||||
use crate::mutations::Mutation;
|
||||
use crate::mutations::Mutation::*;
|
||||
use crate::nodes::VNode;
|
||||
@@ -81,9 +81,7 @@ impl<'b> VirtualDom {
|
||||
|
||||
// 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.get().name) {
|
||||
self.register_template(node.template.get());
|
||||
}
|
||||
self.register_template(node.template.get());
|
||||
|
||||
// we know that this will generate at least one mutation per node
|
||||
self.mutations
|
||||
@@ -285,7 +283,7 @@ impl<'b> VirtualDom {
|
||||
}
|
||||
}
|
||||
|
||||
fn write_attribute(&mut self, attribute: &crate::Attribute, id: ElementId) {
|
||||
fn write_attribute(&mut self, attribute: &'b crate::Attribute<'b>, id: ElementId) {
|
||||
// Make sure we set the attribute's associated id
|
||||
attribute.mounted_element.set(id);
|
||||
|
||||
@@ -293,9 +291,17 @@ impl<'b> VirtualDom {
|
||||
let unbounded_name: &str = unsafe { std::mem::transmute(attribute.name) };
|
||||
|
||||
match &attribute.value {
|
||||
AttributeValue::Text(value) => {
|
||||
AttributeValue::Listener(_) => {
|
||||
self.mutations.push(NewEventListener {
|
||||
// all listeners start with "on"
|
||||
name: &unbounded_name[2..],
|
||||
id,
|
||||
})
|
||||
}
|
||||
_ => {
|
||||
// Safety: we promise not to re-alias this text later on after committing it to the mutation
|
||||
let unbounded_value: &str = unsafe { std::mem::transmute(*value) };
|
||||
let value: BorrowedAttributeValue<'b> = (&attribute.value).into();
|
||||
let unbounded_value = unsafe { std::mem::transmute(value) };
|
||||
|
||||
self.mutations.push(SetAttribute {
|
||||
name: unbounded_name,
|
||||
@@ -304,22 +310,6 @@ impl<'b> VirtualDom {
|
||||
id,
|
||||
})
|
||||
}
|
||||
AttributeValue::Bool(value) => self.mutations.push(SetBoolAttribute {
|
||||
name: unbounded_name,
|
||||
value: *value,
|
||||
id,
|
||||
}),
|
||||
AttributeValue::Listener(_) => {
|
||||
self.mutations.push(NewEventListener {
|
||||
// all listeners start with "on"
|
||||
name: &unbounded_name[2..],
|
||||
id,
|
||||
})
|
||||
}
|
||||
AttributeValue::Float(_) => todo!(),
|
||||
AttributeValue::Int(_) => todo!(),
|
||||
AttributeValue::Any(_) => todo!(),
|
||||
AttributeValue::None => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -397,30 +387,36 @@ impl<'b> VirtualDom {
|
||||
|
||||
/// 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
|
||||
// First, check if we've already seen this template
|
||||
if self
|
||||
.templates
|
||||
.get_mut(path)
|
||||
.and_then(|map| map.remove(&usize::MAX))
|
||||
.get(&path)
|
||||
.filter(|set| set.contains_key(&byte_index))
|
||||
.is_none()
|
||||
{
|
||||
// the byte index of the hot reloaded template could be different
|
||||
new_template.name = template.name;
|
||||
template = new_template;
|
||||
}
|
||||
// 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);
|
||||
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);
|
||||
// If it's all dynamic nodes, then we don't need to register it
|
||||
if !template.is_completely_dynamic() {
|
||||
self.mutations.templates.push(template);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-17
@@ -1,7 +1,7 @@
|
||||
use crate::{
|
||||
any_props::AnyProps,
|
||||
arena::ElementId,
|
||||
innerlude::{DirtyScope, VComponent, VPlaceholder, VText},
|
||||
innerlude::{BorrowedAttributeValue, DirtyScope, VComponent, VPlaceholder, VText},
|
||||
mutations::Mutation,
|
||||
nodes::RenderReturn,
|
||||
nodes::{DynamicNode, VNode},
|
||||
@@ -150,22 +150,16 @@ impl<'b> VirtualDom {
|
||||
};
|
||||
}
|
||||
|
||||
fn update_attribute(&mut self, right_attr: &Attribute, left_attr: &Attribute) {
|
||||
// todo: add more types of attribute values
|
||||
match right_attr.value {
|
||||
AttributeValue::Text(text) => {
|
||||
let name = unsafe { std::mem::transmute(left_attr.name) };
|
||||
let value = unsafe { std::mem::transmute(text) };
|
||||
self.mutations.push(Mutation::SetAttribute {
|
||||
id: left_attr.mounted_element.get(),
|
||||
ns: right_attr.namespace,
|
||||
name,
|
||||
value,
|
||||
});
|
||||
}
|
||||
// todo: more types of attribute values
|
||||
_ => todo!("other attribute types"),
|
||||
}
|
||||
fn update_attribute(&mut self, right_attr: &'b Attribute<'b>, left_attr: &'b Attribute) {
|
||||
let name = unsafe { std::mem::transmute(left_attr.name) };
|
||||
let value: BorrowedAttributeValue<'b> = (&right_attr.value).into();
|
||||
let value = unsafe { std::mem::transmute(value) };
|
||||
self.mutations.push(Mutation::SetAttribute {
|
||||
id: left_attr.mounted_element.get(),
|
||||
ns: right_attr.namespace,
|
||||
name,
|
||||
value,
|
||||
});
|
||||
}
|
||||
|
||||
fn diff_vcomponent(
|
||||
|
||||
@@ -23,7 +23,7 @@ use std::{
|
||||
pub struct Event<T: 'static + ?Sized> {
|
||||
/// The data associated with this event
|
||||
pub data: Rc<T>,
|
||||
pub(crate) propogates: Rc<Cell<bool>>,
|
||||
pub(crate) propagates: Rc<Cell<bool>>,
|
||||
}
|
||||
|
||||
impl<T> Event<T> {
|
||||
@@ -40,9 +40,9 @@ impl<T> Event<T> {
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
#[deprecated = "use stop_propogation instead"]
|
||||
#[deprecated = "use stop_propagation instead"]
|
||||
pub fn cancel_bubble(&self) {
|
||||
self.propogates.set(false);
|
||||
self.propagates.set(false);
|
||||
}
|
||||
|
||||
/// Prevent this event from continuing to bubble up the tree to parent elements.
|
||||
@@ -58,8 +58,8 @@ impl<T> Event<T> {
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub fn stop_propogation(&self) {
|
||||
self.propogates.set(false);
|
||||
pub fn stop_propagation(&self) {
|
||||
self.propagates.set(false);
|
||||
}
|
||||
|
||||
/// Get a reference to the inner data from this event
|
||||
@@ -84,7 +84,7 @@ impl<T> Event<T> {
|
||||
impl<T: ?Sized> Clone for Event<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
propogates: self.propogates.clone(),
|
||||
propagates: self.propagates.clone(),
|
||||
data: self.data.clone(),
|
||||
}
|
||||
}
|
||||
@@ -100,7 +100,7 @@ impl<T> std::ops::Deref for Event<T> {
|
||||
impl<T: std::fmt::Debug> std::fmt::Debug for Event<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("UiEvent")
|
||||
.field("bubble_state", &self.propogates)
|
||||
.field("bubble_state", &self.propagates)
|
||||
.field("data", &self.data)
|
||||
.finish()
|
||||
}
|
||||
|
||||
@@ -70,10 +70,10 @@ pub(crate) mod innerlude {
|
||||
}
|
||||
|
||||
pub use crate::innerlude::{
|
||||
fc_to_builder, Attribute, AttributeValue, CapturedError, Component, DynamicNode, Element,
|
||||
ElementId, Event, Fragment, IntoDynNode, LazyNodes, Mutation, Mutations, Properties,
|
||||
RenderReturn, Scope, ScopeId, ScopeState, Scoped, SuspenseContext, TaskId, Template,
|
||||
TemplateAttribute, TemplateNode, VComponent, VNode, VText, VirtualDom,
|
||||
fc_to_builder, AnyValue, Attribute, AttributeValue, BorrowedAttributeValue, CapturedError,
|
||||
Component, DynamicNode, Element, ElementId, Event, Fragment, IntoDynNode, LazyNodes, Mutation,
|
||||
Mutations, Properties, RenderReturn, Scope, ScopeId, ScopeState, Scoped, SuspenseContext,
|
||||
TaskId, Template, TemplateAttribute, TemplateNode, VComponent, VNode, VText, VirtualDom,
|
||||
};
|
||||
|
||||
/// The purpose of this module is to alleviate imports of many common types
|
||||
@@ -81,9 +81,9 @@ pub use crate::innerlude::{
|
||||
/// This includes types like [`Scope`], [`Element`], and [`Component`].
|
||||
pub mod prelude {
|
||||
pub use crate::innerlude::{
|
||||
fc_to_builder, Component, Element, Event, EventHandler, Fragment, LazyNodes, Properties,
|
||||
Scope, ScopeId, ScopeState, Scoped, TaskId, Template, TemplateAttribute, TemplateNode,
|
||||
Throw, VNode, VirtualDom,
|
||||
fc_to_builder, AnyValue, Component, Element, Event, EventHandler, Fragment,
|
||||
IntoAttributeValue, LazyNodes, Properties, Scope, ScopeId, ScopeState, Scoped, TaskId,
|
||||
Template, TemplateAttribute, TemplateNode, Throw, VNode, VirtualDom,
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
use rustc_hash::FxHashSet;
|
||||
|
||||
use crate::{arena::ElementId, ScopeId, Template};
|
||||
use crate::{arena::ElementId, innerlude::BorrowedAttributeValue, ScopeId, Template};
|
||||
|
||||
/// A container for all the relevant steps to modify the Real DOM
|
||||
///
|
||||
@@ -61,7 +61,7 @@ impl<'a> Mutations<'a> {
|
||||
derive(serde::Serialize, serde::Deserialize),
|
||||
serde(tag = "type")
|
||||
)]
|
||||
#[derive(Debug, PartialEq, Eq)]
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub enum Mutation<'a> {
|
||||
/// Add these m children to the target element
|
||||
AppendChildren {
|
||||
@@ -193,8 +193,9 @@ pub enum Mutation<'a> {
|
||||
SetAttribute {
|
||||
/// The name of the attribute to set.
|
||||
name: &'a str,
|
||||
|
||||
/// The value of the attribute.
|
||||
value: &'a str,
|
||||
value: BorrowedAttributeValue<'a>,
|
||||
|
||||
/// The ID of the node to set the attribute of.
|
||||
id: ElementId,
|
||||
@@ -204,18 +205,6 @@ pub enum Mutation<'a> {
|
||||
ns: Option<&'a str>,
|
||||
},
|
||||
|
||||
/// Set the value of a node's attribute.
|
||||
SetBoolAttribute {
|
||||
/// The name of the attribute to set.
|
||||
name: &'a str,
|
||||
|
||||
/// The value of the attribute.
|
||||
value: bool,
|
||||
|
||||
/// The ID of the node to set the attribute of.
|
||||
id: ElementId,
|
||||
},
|
||||
|
||||
/// Set the textcontent of a node.
|
||||
SetText {
|
||||
/// The textcontent of the node
|
||||
|
||||
+141
-15
@@ -6,7 +6,7 @@ use bumpalo::Bump;
|
||||
use std::{
|
||||
any::{Any, TypeId},
|
||||
cell::{Cell, RefCell, UnsafeCell},
|
||||
fmt::Arguments,
|
||||
fmt::{Arguments, Debug},
|
||||
future::Future,
|
||||
};
|
||||
|
||||
@@ -523,13 +523,108 @@ pub enum AttributeValue<'a> {
|
||||
Listener(RefCell<Option<ListenerCb<'a>>>),
|
||||
|
||||
/// An arbitrary value that implements PartialEq and is static
|
||||
Any(BumpBox<'a, dyn AnyValue>),
|
||||
Any(RefCell<Option<BumpBox<'a, dyn AnyValue>>>),
|
||||
|
||||
/// A "none" value, resulting in the removal of an attribute from the dom
|
||||
None,
|
||||
}
|
||||
|
||||
type ListenerCb<'a> = BumpBox<'a, dyn FnMut(Event<dyn Any>) + 'a>;
|
||||
pub type ListenerCb<'a> = BumpBox<'a, dyn FnMut(Event<dyn Any>) + 'a>;
|
||||
|
||||
/// Any of the built-in values that the Dioxus VirtualDom supports as dynamic attributes on elements that are borrowed
|
||||
///
|
||||
/// These varients are used to communicate what the value of an attribute is that needs to be updated
|
||||
#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))]
|
||||
#[cfg_attr(feature = "serialize", serde(untagged))]
|
||||
pub enum BorrowedAttributeValue<'a> {
|
||||
/// Text attribute
|
||||
Text(&'a str),
|
||||
|
||||
/// A float
|
||||
Float(f64),
|
||||
|
||||
/// Signed integer
|
||||
Int(i64),
|
||||
|
||||
/// Boolean
|
||||
Bool(bool),
|
||||
|
||||
/// An arbitrary value that implements PartialEq and is static
|
||||
#[cfg_attr(
|
||||
feature = "serialize",
|
||||
serde(
|
||||
deserialize_with = "deserialize_any_value",
|
||||
serialize_with = "serialize_any_value"
|
||||
)
|
||||
)]
|
||||
Any(std::cell::Ref<'a, dyn AnyValue>),
|
||||
|
||||
/// A "none" value, resulting in the removal of an attribute from the dom
|
||||
None,
|
||||
}
|
||||
|
||||
impl<'a> From<&'a AttributeValue<'a>> for BorrowedAttributeValue<'a> {
|
||||
fn from(value: &'a AttributeValue<'a>) -> Self {
|
||||
match value {
|
||||
AttributeValue::Text(value) => BorrowedAttributeValue::Text(value),
|
||||
AttributeValue::Float(value) => BorrowedAttributeValue::Float(*value),
|
||||
AttributeValue::Int(value) => BorrowedAttributeValue::Int(*value),
|
||||
AttributeValue::Bool(value) => BorrowedAttributeValue::Bool(*value),
|
||||
AttributeValue::Listener(_) => {
|
||||
panic!("A listener cannot be turned into a borrowed value")
|
||||
}
|
||||
AttributeValue::Any(value) => {
|
||||
let value = value.borrow();
|
||||
BorrowedAttributeValue::Any(std::cell::Ref::map(value, |value| {
|
||||
&**value.as_ref().unwrap()
|
||||
}))
|
||||
}
|
||||
AttributeValue::None => BorrowedAttributeValue::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Debug for BorrowedAttributeValue<'_> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Text(arg0) => f.debug_tuple("Text").field(arg0).finish(),
|
||||
Self::Float(arg0) => f.debug_tuple("Float").field(arg0).finish(),
|
||||
Self::Int(arg0) => f.debug_tuple("Int").field(arg0).finish(),
|
||||
Self::Bool(arg0) => f.debug_tuple("Bool").field(arg0).finish(),
|
||||
Self::Any(_) => f.debug_tuple("Any").field(&"...").finish(),
|
||||
Self::None => write!(f, "None"),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl PartialEq for BorrowedAttributeValue<'_> {
|
||||
fn eq(&self, other: &Self) -> bool {
|
||||
match (self, other) {
|
||||
(Self::Text(l0), Self::Text(r0)) => l0 == r0,
|
||||
(Self::Float(l0), Self::Float(r0)) => l0 == r0,
|
||||
(Self::Int(l0), Self::Int(r0)) => l0 == r0,
|
||||
(Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
|
||||
(Self::Any(l0), Self::Any(r0)) => l0.any_cmp(&**r0),
|
||||
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
fn serialize_any_value<S>(_: &std::cell::Ref<'_, dyn AnyValue>, _: S) -> Result<S::Ok, S::Error>
|
||||
where
|
||||
S: serde::Serializer,
|
||||
{
|
||||
panic!("Any cannot be serialized")
|
||||
}
|
||||
|
||||
#[cfg(feature = "serialize")]
|
||||
fn deserialize_any_value<'de, 'a, D>(_: D) -> Result<std::cell::Ref<'a, dyn AnyValue>, D::Error>
|
||||
where
|
||||
D: serde::Deserializer<'de>,
|
||||
{
|
||||
panic!("Any cannot be deserialized")
|
||||
}
|
||||
|
||||
impl<'a> std::fmt::Debug for AttributeValue<'a> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
@@ -553,29 +648,36 @@ impl<'a> PartialEq for AttributeValue<'a> {
|
||||
(Self::Int(l0), Self::Int(r0)) => l0 == r0,
|
||||
(Self::Bool(l0), Self::Bool(r0)) => l0 == r0,
|
||||
(Self::Listener(_), Self::Listener(_)) => true,
|
||||
(Self::Any(l0), Self::Any(r0)) => l0.any_cmp(r0.as_ref()),
|
||||
_ => core::mem::discriminant(self) == core::mem::discriminant(other),
|
||||
(Self::Any(l0), Self::Any(r0)) => {
|
||||
let l0 = l0.borrow();
|
||||
let r0 = r0.borrow();
|
||||
l0.as_ref().unwrap().any_cmp(&**r0.as_ref().unwrap())
|
||||
}
|
||||
_ => false,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
pub trait AnyValue {
|
||||
pub trait AnyValue: 'static {
|
||||
fn any_cmp(&self, other: &dyn AnyValue) -> bool;
|
||||
fn our_typeid(&self) -> TypeId;
|
||||
fn as_any(&self) -> &dyn Any;
|
||||
fn type_id(&self) -> TypeId {
|
||||
self.as_any().type_id()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: PartialEq + Any> AnyValue for T {
|
||||
impl<T: Any + PartialEq + 'static> AnyValue for T {
|
||||
fn any_cmp(&self, other: &dyn AnyValue) -> bool {
|
||||
if self.type_id() != other.our_typeid() {
|
||||
return false;
|
||||
if let Some(other) = other.as_any().downcast_ref() {
|
||||
self == other
|
||||
} else {
|
||||
false
|
||||
}
|
||||
|
||||
self == unsafe { &*(other as *const _ as *const T) }
|
||||
}
|
||||
|
||||
fn our_typeid(&self) -> TypeId {
|
||||
self.type_id()
|
||||
fn as_any(&self) -> &dyn Any {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
@@ -717,7 +819,6 @@ impl<'a, 'b> IntoTemplate<'a> for LazyNodes<'a, 'b> {
|
||||
}
|
||||
|
||||
// Note that we're using the E as a generic but this is never crafted anyways.
|
||||
#[doc(hidden)]
|
||||
pub struct FromNodeIterator;
|
||||
impl<'a, T, I> IntoDynNode<'a, FromNodeIterator> for T
|
||||
where
|
||||
@@ -742,26 +843,36 @@ pub trait IntoAttributeValue<'a> {
|
||||
fn into_value(self, bump: &'a Bump) -> AttributeValue<'a>;
|
||||
}
|
||||
|
||||
impl<'a> IntoAttributeValue<'a> for AttributeValue<'a> {
|
||||
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoAttributeValue<'a> for &'a str {
|
||||
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
||||
AttributeValue::Text(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoAttributeValue<'a> for f64 {
|
||||
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
||||
AttributeValue::Float(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoAttributeValue<'a> for i64 {
|
||||
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
||||
AttributeValue::Int(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoAttributeValue<'a> for bool {
|
||||
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
||||
AttributeValue::Bool(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
|
||||
fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> {
|
||||
use bumpalo::core_alloc::fmt::Write;
|
||||
@@ -770,3 +881,18 @@ impl<'a> IntoAttributeValue<'a> for Arguments<'_> {
|
||||
AttributeValue::Text(str_buf.into_bump_str())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> IntoAttributeValue<'a> for BumpBox<'a, dyn AnyValue> {
|
||||
fn into_value(self, _: &'a Bump) -> AttributeValue<'a> {
|
||||
AttributeValue::Any(RefCell::new(Some(self)))
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: IntoAttributeValue<'a>> IntoAttributeValue<'a> for Option<T> {
|
||||
fn into_value(self, bump: &'a Bump) -> AttributeValue<'a> {
|
||||
match self {
|
||||
Some(val) => val.into_value(bump),
|
||||
None => AttributeValue::None,
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -59,9 +59,9 @@ impl Scheduler {
|
||||
|
||||
/// Drop the future with the given TaskId
|
||||
///
|
||||
/// This does nto abort the task, so you'll want to wrap it in an aborthandle if that's important to you
|
||||
/// This does not abort the task, so you'll want to wrap it in an aborthandle if that's important to you
|
||||
pub fn remove(&self, id: TaskId) {
|
||||
self.tasks.borrow_mut().remove(id.0);
|
||||
self.tasks.borrow_mut().try_remove(id.0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -31,10 +31,11 @@ impl VirtualDom {
|
||||
// If the task completes...
|
||||
if task.task.borrow_mut().as_mut().poll(&mut cx).is_ready() {
|
||||
// Remove it from the scope so we dont try to double drop it when the scope dropes
|
||||
self.scopes[task.scope.0].spawned_tasks.remove(&id);
|
||||
let scope = &self.scopes[task.scope.0];
|
||||
scope.spawned_tasks.borrow_mut().remove(&id);
|
||||
|
||||
// Remove it from the scheduler
|
||||
tasks.remove(id.0);
|
||||
tasks.try_remove(id.0);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -63,10 +64,10 @@ impl VirtualDom {
|
||||
if let Poll::Ready(new_nodes) = as_pinned_mut.poll_unpin(&mut cx) {
|
||||
let fiber = self.acquire_suspense_boundary(leaf.scope_id);
|
||||
|
||||
let scope = &mut self.scopes[scope_id.0];
|
||||
let scope = &self.scopes[scope_id.0];
|
||||
let arena = scope.current_frame();
|
||||
|
||||
let ret = arena.bump.alloc(match new_nodes {
|
||||
let ret = arena.bump().alloc(match new_nodes {
|
||||
Some(new) => RenderReturn::Ready(new),
|
||||
None => RenderReturn::default(),
|
||||
});
|
||||
|
||||
@@ -7,7 +7,6 @@ use crate::{
|
||||
scopes::{ScopeId, ScopeState},
|
||||
virtual_dom::VirtualDom,
|
||||
};
|
||||
use bumpalo::Bump;
|
||||
use futures_util::FutureExt;
|
||||
use std::{
|
||||
mem,
|
||||
@@ -44,15 +43,14 @@ impl VirtualDom {
|
||||
hook_idx: Default::default(),
|
||||
shared_contexts: Default::default(),
|
||||
borrowed_props: Default::default(),
|
||||
listeners: Default::default(),
|
||||
attributes_to_drop: Default::default(),
|
||||
}))
|
||||
}
|
||||
|
||||
fn acquire_current_scope_raw(&mut self) -> Option<*mut ScopeState> {
|
||||
self.scope_stack
|
||||
.last()
|
||||
.copied()
|
||||
.and_then(|id| self.scopes.get_mut(id.0).map(|f| f.as_mut() as *mut _))
|
||||
fn acquire_current_scope_raw(&self) -> Option<*const ScopeState> {
|
||||
let id = self.scope_stack.last().copied()?;
|
||||
let scope = self.scopes.get(id.0)?;
|
||||
Some(scope.as_ref())
|
||||
}
|
||||
|
||||
pub(crate) fn run_scope(&mut self, scope_id: ScopeId) -> &RenderReturn {
|
||||
@@ -62,17 +60,10 @@ impl VirtualDom {
|
||||
self.ensure_drop_safety(scope_id);
|
||||
|
||||
let mut new_nodes = unsafe {
|
||||
let scope = self.scopes[scope_id.0].as_mut();
|
||||
self.scopes[scope_id.0].previous_frame().bump_mut().reset();
|
||||
|
||||
// if this frame hasn't been intialized yet, we can guess the size of the next frame to be more efficient
|
||||
if scope.previous_frame().bump.allocated_bytes() == 0 {
|
||||
scope.previous_frame_mut().bump =
|
||||
Bump::with_capacity(scope.current_frame().bump.allocated_bytes());
|
||||
} else {
|
||||
scope.previous_frame_mut().bump.reset();
|
||||
}
|
||||
let scope = &self.scopes[scope_id.0];
|
||||
|
||||
// Make sure to reset the hook counter so we give out hooks in the right order
|
||||
scope.hook_idx.set(0);
|
||||
|
||||
// safety: due to how we traverse the tree, we know that the scope is not currently aliased
|
||||
@@ -142,7 +133,7 @@ impl VirtualDom {
|
||||
let frame = scope.previous_frame();
|
||||
|
||||
// set the new head of the bump frame
|
||||
let allocated = &*frame.bump.alloc(new_nodes);
|
||||
let allocated = &*frame.bump().alloc(new_nodes);
|
||||
frame.node.set(allocated);
|
||||
|
||||
// And move the render generation forward by one
|
||||
|
||||
+26
-19
@@ -7,7 +7,7 @@ use crate::{
|
||||
innerlude::{ErrorBoundary, Scheduler, SchedulerMsg},
|
||||
lazynodes::LazyNodes,
|
||||
nodes::{ComponentReturn, IntoAttributeValue, IntoDynNode, RenderReturn},
|
||||
Attribute, AttributeValue, Element, Event, Properties, TaskId,
|
||||
AnyValue, Attribute, AttributeValue, Element, Event, Properties, TaskId,
|
||||
};
|
||||
use bumpalo::{boxed::Box as BumpBox, Bump};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
@@ -73,7 +73,7 @@ pub struct ScopeState {
|
||||
pub(crate) node_arena_1: BumpFrame,
|
||||
pub(crate) node_arena_2: BumpFrame,
|
||||
|
||||
pub(crate) parent: Option<*mut ScopeState>,
|
||||
pub(crate) parent: Option<*const ScopeState>,
|
||||
pub(crate) id: ScopeId,
|
||||
|
||||
pub(crate) height: u32,
|
||||
@@ -85,10 +85,10 @@ pub struct ScopeState {
|
||||
pub(crate) shared_contexts: RefCell<FxHashMap<TypeId, Box<dyn Any>>>,
|
||||
|
||||
pub(crate) tasks: Rc<Scheduler>,
|
||||
pub(crate) spawned_tasks: FxHashSet<TaskId>,
|
||||
pub(crate) spawned_tasks: RefCell<FxHashSet<TaskId>>,
|
||||
|
||||
pub(crate) borrowed_props: RefCell<Vec<*const VComponent<'static>>>,
|
||||
pub(crate) listeners: RefCell<Vec<*const Attribute<'static>>>,
|
||||
pub(crate) attributes_to_drop: RefCell<Vec<*const Attribute<'static>>>,
|
||||
|
||||
pub(crate) props: Option<Box<dyn AnyProps<'static>>>,
|
||||
pub(crate) placeholder: Cell<Option<ElementId>>,
|
||||
@@ -111,14 +111,6 @@ impl<'src> ScopeState {
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) fn previous_frame_mut(&mut self) -> &mut BumpFrame {
|
||||
match self.render_cnt.get() % 2 {
|
||||
1 => &mut self.node_arena_1,
|
||||
0 => &mut self.node_arena_2,
|
||||
_ => unreachable!(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Get the name of this component
|
||||
pub fn name(&self) -> &str {
|
||||
self.name
|
||||
@@ -139,7 +131,7 @@ impl<'src> ScopeState {
|
||||
/// If you need to allocate items that need to be dropped, use bumpalo's box.
|
||||
pub fn bump(&self) -> &Bump {
|
||||
// note that this is actually the previous frame since we use that as scratch space while the component is rendering
|
||||
&self.previous_frame().bump
|
||||
self.previous_frame().bump()
|
||||
}
|
||||
|
||||
/// Get a handle to the currently active head node arena for this Scope
|
||||
@@ -374,11 +366,15 @@ impl<'src> ScopeState {
|
||||
pub fn render(&'src self, rsx: LazyNodes<'src, '_>) -> Element<'src> {
|
||||
let element = rsx.call(self);
|
||||
|
||||
let mut listeners = self.listeners.borrow_mut();
|
||||
let mut listeners = self.attributes_to_drop.borrow_mut();
|
||||
for attr in element.dynamic_attrs {
|
||||
if let AttributeValue::Listener(_) = attr.value {
|
||||
let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
|
||||
listeners.push(unbounded);
|
||||
match attr.value {
|
||||
AttributeValue::Any(_) | AttributeValue::Listener(_) => {
|
||||
let unbounded = unsafe { std::mem::transmute(attr as *const Attribute) };
|
||||
listeners.push(unbounded);
|
||||
}
|
||||
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -499,7 +495,7 @@ impl<'src> ScopeState {
|
||||
BumpBox::from_raw(self.bump().alloc(move |event: Event<dyn Any>| {
|
||||
if let Ok(data) = event.data.downcast::<T>() {
|
||||
callback(Event {
|
||||
propogates: event.propogates,
|
||||
propagates: event.propagates,
|
||||
data,
|
||||
})
|
||||
}
|
||||
@@ -509,6 +505,17 @@ impl<'src> ScopeState {
|
||||
AttributeValue::Listener(RefCell::new(Some(boxed)))
|
||||
}
|
||||
|
||||
/// Create a new [`AttributeValue`] with a value that implements [`AnyValue`]
|
||||
pub fn any_value<T: AnyValue>(&'src self, value: T) -> AttributeValue<'src> {
|
||||
// safety: there's no other way to create a dynamicly-dispatched bump box other than alloc + from-raw
|
||||
// This is the suggested way to build a bumpbox
|
||||
//
|
||||
// In theory, we could just use regular boxes
|
||||
let boxed: BumpBox<'src, dyn AnyValue> =
|
||||
unsafe { BumpBox::from_raw(self.bump().alloc(value)) };
|
||||
AttributeValue::Any(RefCell::new(Some(boxed)))
|
||||
}
|
||||
|
||||
/// Inject an error into the nearest error boundary and quit rendering
|
||||
///
|
||||
/// The error doesn't need to implement Error or any specific traits since the boundary
|
||||
@@ -541,7 +548,7 @@ impl<'src> ScopeState {
|
||||
#[allow(clippy::mut_from_ref)]
|
||||
pub fn use_hook<State: 'static>(&self, initializer: impl FnOnce() -> State) -> &mut State {
|
||||
let cur_hook = self.hook_idx.get();
|
||||
let mut hook_list = self.hook_list.borrow_mut();
|
||||
let mut hook_list = self.hook_list.try_borrow_mut().expect("The hook list is already borrowed: This error is likely caused by trying to use a hook inside a hook which violates the rules of hooks.");
|
||||
|
||||
if cur_hook >= hook_list.len() {
|
||||
hook_list.push(self.hook_arena.alloc(initializer()));
|
||||
|
||||
@@ -347,7 +347,7 @@ impl VirtualDom {
|
||||
|
||||
// We will clone this later. The data itself is wrapped in RC to be used in callbacks if required
|
||||
let uievent = Event {
|
||||
propogates: Rc::new(Cell::new(bubbles)),
|
||||
propagates: Rc::new(Cell::new(bubbles)),
|
||||
data,
|
||||
};
|
||||
|
||||
@@ -388,7 +388,7 @@ impl VirtualDom {
|
||||
cb(uievent.clone());
|
||||
}
|
||||
|
||||
if !uievent.propogates.get() {
|
||||
if !uievent.propagates.get() {
|
||||
return;
|
||||
}
|
||||
}
|
||||
@@ -549,7 +549,7 @@ impl VirtualDom {
|
||||
loop {
|
||||
// first, unload any complete suspense trees
|
||||
for finished_fiber in self.finished_fibers.drain(..) {
|
||||
let scope = &mut self.scopes[finished_fiber.0];
|
||||
let scope = &self.scopes[finished_fiber.0];
|
||||
let context = scope.has_context::<Rc<SuspenseContext>>().unwrap();
|
||||
|
||||
self.mutations
|
||||
@@ -641,10 +641,7 @@ impl VirtualDom {
|
||||
|
||||
/// Swap the current mutations with a new
|
||||
fn finalize(&mut self) -> Mutations {
|
||||
// todo: make this a routine
|
||||
let mut out = Mutations::default();
|
||||
std::mem::swap(&mut self.mutations, &mut out);
|
||||
out
|
||||
std::mem::take(&mut self.mutations)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -2,8 +2,10 @@
|
||||
//!
|
||||
//! This tests to ensure we clean it up
|
||||
|
||||
use bumpalo::Bump;
|
||||
use dioxus::core::{ElementId, Mutation::*};
|
||||
use dioxus::prelude::*;
|
||||
use dioxus_core::{AttributeValue, BorrowedAttributeValue};
|
||||
|
||||
#[test]
|
||||
fn attrs_cycle() {
|
||||
@@ -22,6 +24,8 @@ fn attrs_cycle() {
|
||||
}
|
||||
});
|
||||
|
||||
let bump = Bump::new();
|
||||
|
||||
assert_eq!(
|
||||
dom.rebuild().santize().edits,
|
||||
[
|
||||
@@ -36,8 +40,18 @@ fn attrs_cycle() {
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
|
||||
AssignId { path: &[0,], id: ElementId(3,) },
|
||||
SetAttribute { name: "class", value: "1", id: ElementId(3,), ns: None },
|
||||
SetAttribute { name: "id", value: "1", id: ElementId(3,), ns: None },
|
||||
SetAttribute {
|
||||
name: "class",
|
||||
value: (&*bump.alloc("1".into_value(&bump))).into(),
|
||||
id: ElementId(3,),
|
||||
ns: None
|
||||
},
|
||||
SetAttribute {
|
||||
name: "id",
|
||||
value: (&*bump.alloc("1".into_value(&bump))).into(),
|
||||
id: ElementId(3,),
|
||||
ns: None
|
||||
},
|
||||
ReplaceWith { id: ElementId(1,), m: 1 },
|
||||
]
|
||||
);
|
||||
@@ -57,8 +71,18 @@ fn attrs_cycle() {
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2) },
|
||||
AssignId { path: &[0], id: ElementId(3) },
|
||||
SetAttribute { name: "class", value: "3", id: ElementId(3), ns: None },
|
||||
SetAttribute { name: "id", value: "3", id: ElementId(3), ns: None },
|
||||
SetAttribute {
|
||||
name: "class",
|
||||
value: BorrowedAttributeValue::Text("3"),
|
||||
id: ElementId(3),
|
||||
ns: None
|
||||
},
|
||||
SetAttribute {
|
||||
name: "id",
|
||||
value: BorrowedAttributeValue::Text("3"),
|
||||
id: ElementId(3),
|
||||
ns: None
|
||||
},
|
||||
ReplaceWith { id: ElementId(1), m: 1 }
|
||||
]
|
||||
);
|
||||
|
||||
@@ -1,15 +1,23 @@
|
||||
use bumpalo::Bump;
|
||||
use dioxus::core::{ElementId, Mutation::*};
|
||||
use dioxus::prelude::*;
|
||||
|
||||
#[test]
|
||||
fn bool_test() {
|
||||
let mut app = VirtualDom::new(|cx| cx.render(rsx!(div { hidden: false })));
|
||||
let bump = Bump::new();
|
||||
|
||||
assert_eq!(
|
||||
app.rebuild().santize().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||
SetBoolAttribute { name: "hidden", value: false, id: ElementId(1,) },
|
||||
SetAttribute {
|
||||
name: "hidden",
|
||||
value: (&*bump.alloc(false.into_value(&bump))).into(),
|
||||
id: ElementId(1,),
|
||||
ns: None
|
||||
},
|
||||
AppendChildren { m: 1, id: ElementId(0) },
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
@@ -18,7 +18,7 @@ fn test_borrowed_state() {
|
||||
ReplacePlaceholder { path: &[0,], m: 1 },
|
||||
AppendChildren { m: 1, id: ElementId(0) },
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
fn Parent(cx: Scope) -> Element {
|
||||
|
||||
@@ -20,7 +20,9 @@ fn app(cx: Scope) -> Element {
|
||||
fn bubbles_error() {
|
||||
let mut dom = VirtualDom::new(app);
|
||||
|
||||
let _edits = dom.rebuild().santize();
|
||||
{
|
||||
let _edits = dom.rebuild().santize();
|
||||
}
|
||||
|
||||
dom.mark_dirty(ScopeId(0));
|
||||
|
||||
|
||||
@@ -12,14 +12,16 @@ fn cycling_elements() {
|
||||
})
|
||||
});
|
||||
|
||||
let edits = dom.rebuild().santize();
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
|
||||
AppendChildren { m: 1, id: ElementId(0) },
|
||||
]
|
||||
);
|
||||
{
|
||||
let edits = dom.rebuild().santize();
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
|
||||
AppendChildren { m: 1, id: ElementId(0) },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
dom.mark_dirty(ScopeId(0));
|
||||
assert_eq!(
|
||||
|
||||
@@ -59,19 +59,21 @@ fn component_swap() {
|
||||
}
|
||||
|
||||
let mut dom = VirtualDom::new(app);
|
||||
let edits = dom.rebuild().santize();
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(3) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(4) },
|
||||
ReplacePlaceholder { path: &[1], m: 3 },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(5) },
|
||||
AppendChildren { m: 2, id: ElementId(0) }
|
||||
]
|
||||
);
|
||||
{
|
||||
let edits = dom.rebuild().santize();
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(3) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(4) },
|
||||
ReplacePlaceholder { path: &[1], m: 3 },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(5) },
|
||||
AppendChildren { m: 2, id: ElementId(0) }
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
dom.mark_dirty(ScopeId(0));
|
||||
assert_eq!(
|
||||
|
||||
@@ -20,22 +20,24 @@ fn keyed_diffing_out_of_order() {
|
||||
cx.render(rsx!(order.iter().map(|i| rsx!(div { key: "{i}" }))))
|
||||
});
|
||||
|
||||
assert_eq!(
|
||||
dom.rebuild().santize().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(3,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(4,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(6,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(7,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(8,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(10,) },
|
||||
AppendChildren { m: 10, id: ElementId(0) },
|
||||
]
|
||||
);
|
||||
{
|
||||
assert_eq!(
|
||||
dom.rebuild().santize().edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(3,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(4,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(6,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(7,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(8,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(10,) },
|
||||
AppendChildren { m: 10, id: ElementId(0) },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
dom.mark_dirty(ScopeId(0));
|
||||
assert_eq!(
|
||||
@@ -44,7 +46,7 @@ fn keyed_diffing_out_of_order() {
|
||||
PushRoot { id: ElementId(7,) },
|
||||
InsertBefore { id: ElementId(5,), m: 1 },
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/// Should result in moves only
|
||||
@@ -70,7 +72,7 @@ fn keyed_diffing_out_of_order_adds() {
|
||||
PushRoot { id: ElementId(4,) },
|
||||
InsertBefore { id: ElementId(1,), m: 2 },
|
||||
]
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
/// Should result in moves only
|
||||
|
||||
@@ -315,66 +315,76 @@ fn remove_many() {
|
||||
})
|
||||
});
|
||||
|
||||
let edits = dom.rebuild().santize();
|
||||
assert!(edits.templates.is_empty());
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
CreatePlaceholder { id: ElementId(1,) },
|
||||
AppendChildren { id: ElementId(0), m: 1 },
|
||||
]
|
||||
);
|
||||
{
|
||||
let edits = dom.rebuild().santize();
|
||||
assert!(edits.templates.is_empty());
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
CreatePlaceholder { id: ElementId(1,) },
|
||||
AppendChildren { id: ElementId(0), m: 1 },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
dom.mark_dirty(ScopeId(0));
|
||||
let edits = dom.render_immediate().santize();
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
|
||||
HydrateText { path: &[0,], value: "hello 0", id: ElementId(3,) },
|
||||
ReplaceWith { id: ElementId(1,), m: 1 },
|
||||
]
|
||||
);
|
||||
{
|
||||
dom.mark_dirty(ScopeId(0));
|
||||
let edits = dom.render_immediate().santize();
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
|
||||
HydrateText { path: &[0,], value: "hello 0", id: ElementId(3,) },
|
||||
ReplaceWith { id: ElementId(1,), m: 1 },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
dom.mark_dirty(ScopeId(0));
|
||||
let edits = dom.render_immediate().santize();
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
|
||||
HydrateText { path: &[0,], value: "hello 1", id: ElementId(4,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
|
||||
HydrateText { path: &[0,], value: "hello 2", id: ElementId(6,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(7,) },
|
||||
HydrateText { path: &[0,], value: "hello 3", id: ElementId(8,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
|
||||
HydrateText { path: &[0,], value: "hello 4", id: ElementId(10,) },
|
||||
InsertAfter { id: ElementId(2,), m: 4 },
|
||||
]
|
||||
);
|
||||
{
|
||||
dom.mark_dirty(ScopeId(0));
|
||||
let edits = dom.render_immediate().santize();
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1,) },
|
||||
HydrateText { path: &[0,], value: "hello 1", id: ElementId(4,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(5,) },
|
||||
HydrateText { path: &[0,], value: "hello 2", id: ElementId(6,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(7,) },
|
||||
HydrateText { path: &[0,], value: "hello 3", id: ElementId(8,) },
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(9,) },
|
||||
HydrateText { path: &[0,], value: "hello 4", id: ElementId(10,) },
|
||||
InsertAfter { id: ElementId(2,), m: 4 },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
dom.mark_dirty(ScopeId(0));
|
||||
let edits = dom.render_immediate().santize();
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
CreatePlaceholder { id: ElementId(11,) },
|
||||
Remove { id: ElementId(9,) },
|
||||
Remove { id: ElementId(7,) },
|
||||
Remove { id: ElementId(5,) },
|
||||
Remove { id: ElementId(1,) },
|
||||
ReplaceWith { id: ElementId(2,), m: 1 },
|
||||
]
|
||||
);
|
||||
{
|
||||
dom.mark_dirty(ScopeId(0));
|
||||
let edits = dom.render_immediate().santize();
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
CreatePlaceholder { id: ElementId(11,) },
|
||||
Remove { id: ElementId(9,) },
|
||||
Remove { id: ElementId(7,) },
|
||||
Remove { id: ElementId(5,) },
|
||||
Remove { id: ElementId(1,) },
|
||||
ReplaceWith { id: ElementId(2,), m: 1 },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
dom.mark_dirty(ScopeId(0));
|
||||
let edits = dom.render_immediate().santize();
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
|
||||
HydrateText { path: &[0,], value: "hello 0", id: ElementId(3,) },
|
||||
ReplaceWith { id: ElementId(11,), m: 1 },
|
||||
]
|
||||
)
|
||||
{
|
||||
dom.mark_dirty(ScopeId(0));
|
||||
let edits = dom.render_immediate().santize();
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(2,) },
|
||||
HydrateText { path: &[0,], value: "hello 0", id: ElementId(3,) },
|
||||
ReplaceWith { id: ElementId(11,), m: 1 },
|
||||
]
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
use bumpalo::Bump;
|
||||
use dioxus::core::{ElementId, Mutation};
|
||||
use dioxus::prelude::*;
|
||||
|
||||
@@ -26,17 +27,22 @@ fn basic_syntax_is_a_template(cx: Scope) -> Element {
|
||||
#[test]
|
||||
fn dual_stream() {
|
||||
let mut dom = VirtualDom::new(basic_syntax_is_a_template);
|
||||
let bump = Bump::new();
|
||||
let edits = dom.rebuild().santize();
|
||||
|
||||
use Mutation::*;
|
||||
assert_eq!(
|
||||
edits.edits,
|
||||
assert_eq!(edits.edits, {
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||
SetAttribute { name: "class", value: "123", id: ElementId(1), ns: None },
|
||||
SetAttribute {
|
||||
name: "class",
|
||||
value: (&*bump.alloc("123".into_value(&bump))).into(),
|
||||
id: ElementId(1),
|
||||
ns: None,
|
||||
},
|
||||
NewEventListener { name: "click", id: ElementId(1) },
|
||||
HydrateText { path: &[0, 0], value: "123", id: ElementId(2) },
|
||||
AppendChildren { id: ElementId(0), m: 1 }
|
||||
],
|
||||
);
|
||||
AppendChildren { id: ElementId(0), m: 1 },
|
||||
]
|
||||
});
|
||||
}
|
||||
@@ -5,40 +5,48 @@ use std::future::IntoFuture;
|
||||
use std::rc::Rc;
|
||||
use std::time::Duration;
|
||||
|
||||
#[tokio::test]
|
||||
async fn it_works() {
|
||||
let mut dom = VirtualDom::new(app);
|
||||
|
||||
let mutations = dom.rebuild().santize();
|
||||
|
||||
// We should at least get the top-level template in before pausing for the children
|
||||
// note: we dont test template edits anymore
|
||||
// assert_eq!(
|
||||
// mutations.templates,
|
||||
// [
|
||||
// CreateElement { name: "div" },
|
||||
// CreateStaticText { value: "Waiting for child..." },
|
||||
// CreateStaticPlaceholder,
|
||||
// AppendChildren { m: 2 },
|
||||
// SaveTemplate { name: "template", m: 1 }
|
||||
// ]
|
||||
// );
|
||||
|
||||
// And we should load it in and assign the placeholder properly
|
||||
assert_eq!(
|
||||
mutations.edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||
// hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly?
|
||||
// can we even?
|
||||
AssignId { path: &[1], id: ElementId(3) },
|
||||
AppendChildren { m: 1, id: ElementId(0) },
|
||||
]
|
||||
);
|
||||
|
||||
#[test]
|
||||
fn it_works() {
|
||||
// wait just a moment, not enough time for the boundary to resolve
|
||||
|
||||
dom.wait_for_work().await;
|
||||
tokio::runtime::Builder::new_current_thread()
|
||||
.enable_time()
|
||||
.build()
|
||||
.unwrap()
|
||||
.block_on(async {
|
||||
let mut dom = VirtualDom::new(app);
|
||||
|
||||
{
|
||||
let mutations = dom.rebuild().santize();
|
||||
|
||||
// We should at least get the top-level template in before pausing for the children
|
||||
// note: we dont test template edits anymore
|
||||
// assert_eq!(
|
||||
// mutations.templates,
|
||||
// [
|
||||
// CreateElement { name: "div" },
|
||||
// CreateStaticText { value: "Waiting for child..." },
|
||||
// CreateStaticPlaceholder,
|
||||
// AppendChildren { m: 2 },
|
||||
// SaveTemplate { name: "template", m: 1 }
|
||||
// ]
|
||||
// );
|
||||
|
||||
// And we should load it in and assign the placeholder properly
|
||||
assert_eq!(
|
||||
mutations.edits,
|
||||
[
|
||||
LoadTemplate { name: "template", index: 0, id: ElementId(1) },
|
||||
// hmmmmmmmmm.... with suspense how do we guarantee that IDs increase linearly?
|
||||
// can we even?
|
||||
AssignId { path: &[1], id: ElementId(3) },
|
||||
AppendChildren { m: 1, id: ElementId(0) },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
dom.wait_for_work().await;
|
||||
});
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dioxus-desktop"
|
||||
version = "0.2.3"
|
||||
version = "0.3.0"
|
||||
authors = ["Jonathan Kelley"]
|
||||
edition = "2018"
|
||||
description = "Dioxus VirtualDOM renderer for a remote webview instance"
|
||||
@@ -12,9 +12,9 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "../core", version = "^0.2.1", features = ["serialize"] }
|
||||
dioxus-html = { path = "../html", features = ["serialize"], version = "^0.2.1" }
|
||||
dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.1" }
|
||||
dioxus-core = { path = "../core", version = "^0.3.0", features = ["serialize"] }
|
||||
dioxus-html = { path = "../html", features = ["serialize"], version = "^0.3.0" }
|
||||
dioxus-interpreter-js = { path = "../interpreter", version = "^0.3.0" }
|
||||
|
||||
serde = "1.0.136"
|
||||
serde_json = "1.0.79"
|
||||
|
||||
@@ -1,14 +1,22 @@
|
||||
use std::cell::RefCell;
|
||||
use std::rc::Rc;
|
||||
use std::rc::Weak;
|
||||
|
||||
use crate::create_new_window;
|
||||
use crate::eval::EvalResult;
|
||||
use crate::events::IpcMessage;
|
||||
use crate::Config;
|
||||
use crate::WebviewHandler;
|
||||
use dioxus_core::ScopeState;
|
||||
use dioxus_core::VirtualDom;
|
||||
use serde_json::Value;
|
||||
use wry::application::event_loop::EventLoopProxy;
|
||||
use wry::application::event_loop::EventLoopWindowTarget;
|
||||
#[cfg(target_os = "ios")]
|
||||
use wry::application::platform::ios::WindowExtIOS;
|
||||
use wry::application::window::Fullscreen as WryFullscreen;
|
||||
use wry::application::window::Window;
|
||||
use wry::application::window::WindowId;
|
||||
use wry::webview::WebView;
|
||||
|
||||
pub type ProxyType = EventLoopProxy<UserWindowEvent>;
|
||||
@@ -20,6 +28,8 @@ pub fn use_window(cx: &ScopeState) -> &DesktopContext {
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub(crate) type WebviewQueue = Rc<RefCell<Vec<WebviewHandler>>>;
|
||||
|
||||
/// An imperative interface to the current window.
|
||||
///
|
||||
/// To get a handle to the current window, use the [`use_window`] hook.
|
||||
@@ -43,6 +53,10 @@ pub struct DesktopContext {
|
||||
/// The receiver for eval results since eval is async
|
||||
pub(super) eval: tokio::sync::broadcast::Sender<Value>,
|
||||
|
||||
pub(super) pending_windows: WebviewQueue,
|
||||
|
||||
pub(crate) event_loop: EventLoopWindowTarget<UserWindowEvent>,
|
||||
|
||||
#[cfg(target_os = "ios")]
|
||||
pub(crate) views: Rc<RefCell<Vec<*mut objc::runtime::Object>>>,
|
||||
}
|
||||
@@ -57,16 +71,56 @@ impl std::ops::Deref for DesktopContext {
|
||||
}
|
||||
|
||||
impl DesktopContext {
|
||||
pub(crate) fn new(webview: Rc<WebView>, proxy: ProxyType) -> Self {
|
||||
pub(crate) fn new(
|
||||
webview: Rc<WebView>,
|
||||
proxy: ProxyType,
|
||||
event_loop: EventLoopWindowTarget<UserWindowEvent>,
|
||||
webviews: WebviewQueue,
|
||||
) -> Self {
|
||||
Self {
|
||||
webview,
|
||||
proxy,
|
||||
event_loop,
|
||||
eval: tokio::sync::broadcast::channel(8).0,
|
||||
pending_windows: webviews,
|
||||
#[cfg(target_os = "ios")]
|
||||
views: Default::default(),
|
||||
}
|
||||
}
|
||||
|
||||
/// Create a new window using the props and window builder
|
||||
///
|
||||
/// Returns the webview handle for the new window.
|
||||
///
|
||||
/// You can use this to control other windows from the current window.
|
||||
///
|
||||
/// Be careful to not create a cycle of windows, or you might leak memory.
|
||||
pub fn new_window(&self, dom: VirtualDom, cfg: Config) -> Weak<WebView> {
|
||||
let window = create_new_window(
|
||||
cfg,
|
||||
&self.event_loop,
|
||||
&self.proxy,
|
||||
dom,
|
||||
&self.pending_windows,
|
||||
);
|
||||
|
||||
let id = window.webview.window().id();
|
||||
|
||||
self.proxy
|
||||
.send_event(UserWindowEvent(EventData::NewWindow, id))
|
||||
.unwrap();
|
||||
|
||||
self.proxy
|
||||
.send_event(UserWindowEvent(EventData::Poll, id))
|
||||
.unwrap();
|
||||
|
||||
let webview = window.webview.clone();
|
||||
|
||||
self.pending_windows.borrow_mut().push(window);
|
||||
|
||||
Rc::downgrade(&webview)
|
||||
}
|
||||
|
||||
/// trigger the drag-window event
|
||||
///
|
||||
/// Moves the window with the left mouse button until the button is released.
|
||||
@@ -79,7 +133,9 @@ impl DesktopContext {
|
||||
let window = self.webview.window();
|
||||
|
||||
// if the drag_window has any errors, we don't do anything
|
||||
window.fullscreen().is_none().then(|| window.drag_window());
|
||||
if window.fullscreen().is_none() {
|
||||
window.drag_window().unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
/// Toggle whether the window is maximized or not
|
||||
@@ -91,7 +147,16 @@ impl DesktopContext {
|
||||
|
||||
/// close window
|
||||
pub fn close(&self) {
|
||||
let _ = self.proxy.send_event(UserWindowEvent::CloseWindow);
|
||||
let _ = self
|
||||
.proxy
|
||||
.send_event(UserWindowEvent(EventData::CloseWindow, self.id()));
|
||||
}
|
||||
|
||||
/// close window
|
||||
pub fn close_window(&self, id: WindowId) {
|
||||
let _ = self
|
||||
.proxy
|
||||
.send_event(UserWindowEvent(EventData::CloseWindow, id));
|
||||
}
|
||||
|
||||
/// change window to fullscreen
|
||||
@@ -188,12 +253,17 @@ impl DesktopContext {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub enum UserWindowEvent {
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct UserWindowEvent(pub EventData, pub WindowId);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum EventData {
|
||||
Poll,
|
||||
|
||||
Ipc(IpcMessage),
|
||||
|
||||
NewWindow,
|
||||
|
||||
CloseWindow,
|
||||
}
|
||||
|
||||
|
||||
@@ -2,7 +2,7 @@
|
||||
|
||||
use serde::{Deserialize, Serialize};
|
||||
|
||||
#[derive(Deserialize, Serialize, Debug)]
|
||||
#[derive(Deserialize, Serialize, Debug, Clone)]
|
||||
pub struct IpcMessage {
|
||||
method: String,
|
||||
params: serde_json::Value,
|
||||
|
||||
+131
-73
@@ -16,8 +16,8 @@ mod webview;
|
||||
mod hot_reload;
|
||||
|
||||
pub use cfg::Config;
|
||||
use desktop_context::UserWindowEvent;
|
||||
pub use desktop_context::{use_window, DesktopContext};
|
||||
use desktop_context::{EventData, UserWindowEvent, WebviewQueue};
|
||||
use dioxus_core::*;
|
||||
use dioxus_html::HtmlEvent;
|
||||
pub use eval::{use_eval, EvalResult};
|
||||
@@ -26,6 +26,7 @@ use std::collections::HashMap;
|
||||
use std::rc::Rc;
|
||||
use std::task::Waker;
|
||||
pub use tao::dpi::{LogicalSize, PhysicalSize};
|
||||
use tao::event_loop::{EventLoopProxy, EventLoopWindowTarget};
|
||||
pub use tao::window::WindowBuilder;
|
||||
use tao::{
|
||||
event::{Event, StartCause, WindowEvent},
|
||||
@@ -33,6 +34,8 @@ use tao::{
|
||||
};
|
||||
pub use wry;
|
||||
pub use wry::application as tao;
|
||||
use wry::application::window::WindowId;
|
||||
use wry::webview::WebView;
|
||||
|
||||
/// Launch the WebView and run the event loop.
|
||||
///
|
||||
@@ -101,10 +104,8 @@ pub fn launch_cfg(root: Component, config_builder: Config) {
|
||||
/// })
|
||||
/// }
|
||||
/// ```
|
||||
pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, mut cfg: Config) {
|
||||
let mut dom = VirtualDom::new_with_props(root, props);
|
||||
|
||||
let event_loop = EventLoop::with_user_event();
|
||||
pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, cfg: Config) {
|
||||
let event_loop = EventLoop::<UserWindowEvent>::with_user_event();
|
||||
|
||||
let proxy = event_loop.create_proxy();
|
||||
|
||||
@@ -118,23 +119,35 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, mut cfg: Conf
|
||||
// We enter the runtime but we poll futures manually, circumventing the per-task runtime budget
|
||||
let _guard = rt.enter();
|
||||
|
||||
// We want to poll the virtualdom and the event loop at the same time, so the waker will be connected to both
|
||||
let waker = waker::tao_waker(&proxy);
|
||||
|
||||
// We only have one webview right now, but we'll have more later
|
||||
// Store them in a hashmap so we can remove them when they're closed
|
||||
let mut webviews = HashMap::new();
|
||||
let mut webviews = HashMap::<WindowId, WebviewHandler>::new();
|
||||
|
||||
event_loop.run(move |window_event, event_loop, control_flow| {
|
||||
let queue = WebviewQueue::default();
|
||||
|
||||
// By default, we'll create a new window when the app starts
|
||||
queue.borrow_mut().push(create_new_window(
|
||||
cfg,
|
||||
&event_loop,
|
||||
&proxy,
|
||||
VirtualDom::new_with_props(root, props),
|
||||
&queue,
|
||||
));
|
||||
|
||||
event_loop.run(move |window_event, _event_loop, control_flow| {
|
||||
*control_flow = ControlFlow::Wait;
|
||||
|
||||
match window_event {
|
||||
Event::UserEvent(UserWindowEvent::CloseWindow) => *control_flow = ControlFlow::Exit,
|
||||
|
||||
Event::WindowEvent {
|
||||
event, window_id, ..
|
||||
} => match event {
|
||||
WindowEvent::CloseRequested => *control_flow = ControlFlow::Exit,
|
||||
WindowEvent::CloseRequested => {
|
||||
webviews.remove(&window_id);
|
||||
|
||||
if webviews.is_empty() {
|
||||
*control_flow = ControlFlow::Exit
|
||||
}
|
||||
}
|
||||
WindowEvent::Destroyed { .. } => {
|
||||
webviews.remove(&window_id);
|
||||
|
||||
@@ -145,78 +158,125 @@ pub fn launch_with_props<P: 'static>(root: Component<P>, props: P, mut cfg: Conf
|
||||
_ => {}
|
||||
},
|
||||
|
||||
Event::NewEvents(StartCause::Init) => {
|
||||
let window = webview::build(&mut cfg, event_loop, proxy.clone());
|
||||
|
||||
dom.base_scope()
|
||||
.provide_context(DesktopContext::new(window.clone(), proxy.clone()));
|
||||
|
||||
webviews.insert(window.window().id(), window);
|
||||
|
||||
_ = proxy.send_event(UserWindowEvent::Poll);
|
||||
}
|
||||
|
||||
Event::UserEvent(UserWindowEvent::Poll) => {
|
||||
poll_vdom(&waker, &mut dom, &mut webviews);
|
||||
}
|
||||
|
||||
Event::UserEvent(UserWindowEvent::Ipc(msg)) if msg.method() == "user_event" => {
|
||||
let evt = match serde_json::from_value::<HtmlEvent>(msg.params()) {
|
||||
Ok(value) => value,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
dom.handle_event(&evt.name, evt.data.into_any(), evt.element, evt.bubbles);
|
||||
|
||||
send_edits(dom.render_immediate(), &mut webviews);
|
||||
}
|
||||
|
||||
Event::UserEvent(UserWindowEvent::Ipc(msg)) if msg.method() == "initialize" => {
|
||||
send_edits(dom.rebuild(), &mut webviews);
|
||||
}
|
||||
|
||||
// When the webview chirps back with the result of the eval, we send it to the active receiver
|
||||
//
|
||||
// This currently doesn't perform any targeting to the callsite, so if you eval multiple times at once,
|
||||
// you might the wrong result. This should be fixed
|
||||
Event::UserEvent(UserWindowEvent::Ipc(msg)) if msg.method() == "eval_result" => {
|
||||
dom.base_scope()
|
||||
.consume_context::<DesktopContext>()
|
||||
.unwrap()
|
||||
.eval
|
||||
.send(msg.params())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
Event::UserEvent(UserWindowEvent::Ipc(msg)) if msg.method() == "browser_open" => {
|
||||
if let Some(temp) = msg.params().as_object() {
|
||||
if temp.contains_key("href") {
|
||||
let open = webbrowser::open(temp["href"].as_str().unwrap());
|
||||
if let Err(e) = open {
|
||||
log::error!("Open Browser error: {:?}", e);
|
||||
}
|
||||
}
|
||||
Event::NewEvents(StartCause::Init)
|
||||
| Event::UserEvent(UserWindowEvent(EventData::NewWindow, _)) => {
|
||||
for handler in queue.borrow_mut().drain(..) {
|
||||
let id = handler.webview.window().id();
|
||||
webviews.insert(id, handler);
|
||||
_ = proxy.send_event(UserWindowEvent(EventData::Poll, id));
|
||||
}
|
||||
}
|
||||
|
||||
Event::UserEvent(event) => match event.0 {
|
||||
EventData::CloseWindow => {
|
||||
webviews.remove(&event.1);
|
||||
|
||||
if webviews.is_empty() {
|
||||
*control_flow = ControlFlow::Exit
|
||||
}
|
||||
}
|
||||
|
||||
EventData::Poll => {
|
||||
if let Some(view) = webviews.get_mut(&event.1) {
|
||||
poll_vdom(view);
|
||||
}
|
||||
}
|
||||
|
||||
EventData::Ipc(msg) if msg.method() == "user_event" => {
|
||||
let evt = match serde_json::from_value::<HtmlEvent>(msg.params()) {
|
||||
Ok(value) => value,
|
||||
Err(_) => return,
|
||||
};
|
||||
|
||||
let view = webviews.get_mut(&event.1).unwrap();
|
||||
|
||||
view.dom
|
||||
.handle_event(&evt.name, evt.data.into_any(), evt.element, evt.bubbles);
|
||||
|
||||
send_edits(view.dom.render_immediate(), &view.webview);
|
||||
}
|
||||
|
||||
EventData::Ipc(msg) if msg.method() == "initialize" => {
|
||||
let view = webviews.get_mut(&event.1).unwrap();
|
||||
send_edits(view.dom.rebuild(), &view.webview);
|
||||
}
|
||||
|
||||
// When the webview chirps back with the result of the eval, we send it to the active receiver
|
||||
//
|
||||
// This currently doesn't perform any targeting to the callsite, so if you eval multiple times at once,
|
||||
// you might the wrong result. This should be fixed
|
||||
EventData::Ipc(msg) if msg.method() == "eval_result" => {
|
||||
webviews[&event.1]
|
||||
.dom
|
||||
.base_scope()
|
||||
.consume_context::<DesktopContext>()
|
||||
.unwrap()
|
||||
.eval
|
||||
.send(msg.params())
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
EventData::Ipc(msg) if msg.method() == "browser_open" => {
|
||||
if let Some(temp) = msg.params().as_object() {
|
||||
if temp.contains_key("href") {
|
||||
let open = webbrowser::open(temp["href"].as_str().unwrap());
|
||||
if let Err(e) = open {
|
||||
log::error!("Open Browser error: {:?}", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_ => {}
|
||||
},
|
||||
_ => {}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type Webviews = HashMap<tao::window::WindowId, Rc<wry::webview::WebView>>;
|
||||
fn create_new_window(
|
||||
mut cfg: Config,
|
||||
event_loop: &EventLoopWindowTarget<UserWindowEvent>,
|
||||
proxy: &EventLoopProxy<UserWindowEvent>,
|
||||
dom: VirtualDom,
|
||||
queue: &WebviewQueue,
|
||||
) -> WebviewHandler {
|
||||
let webview = webview::build(&mut cfg, event_loop, proxy.clone());
|
||||
|
||||
dom.base_scope().provide_context(DesktopContext::new(
|
||||
webview.clone(),
|
||||
proxy.clone(),
|
||||
event_loop.clone(),
|
||||
queue.clone(),
|
||||
));
|
||||
|
||||
let id = webview.window().id();
|
||||
|
||||
// We want to poll the virtualdom and the event loop at the same time, so the waker will be connected to both
|
||||
WebviewHandler {
|
||||
webview,
|
||||
dom,
|
||||
waker: waker::tao_waker(proxy, id),
|
||||
}
|
||||
}
|
||||
|
||||
struct WebviewHandler {
|
||||
dom: VirtualDom,
|
||||
webview: Rc<wry::webview::WebView>,
|
||||
waker: Waker,
|
||||
}
|
||||
|
||||
/// Poll the virtualdom until it's pending
|
||||
///
|
||||
/// The waker we give it is connected to the event loop, so it will wake up the event loop when it's ready to be polled again
|
||||
///
|
||||
/// All IO is done on the tokio runtime we started earlier
|
||||
fn poll_vdom(waker: &Waker, dom: &mut VirtualDom, webviews: &mut Webviews) {
|
||||
let mut cx = std::task::Context::from_waker(waker);
|
||||
fn poll_vdom(view: &mut WebviewHandler) {
|
||||
let mut cx = std::task::Context::from_waker(&view.waker);
|
||||
|
||||
loop {
|
||||
{
|
||||
let fut = dom.wait_for_work();
|
||||
let fut = view.dom.wait_for_work();
|
||||
pin_mut!(fut);
|
||||
|
||||
match fut.poll_unpin(&mut cx) {
|
||||
@@ -225,16 +285,14 @@ fn poll_vdom(waker: &Waker, dom: &mut VirtualDom, webviews: &mut Webviews) {
|
||||
}
|
||||
}
|
||||
|
||||
send_edits(dom.render_immediate(), webviews);
|
||||
send_edits(view.dom.render_immediate(), &view.webview);
|
||||
}
|
||||
}
|
||||
|
||||
/// Send a list of mutations to the webview
|
||||
fn send_edits(edits: Mutations, webviews: &mut Webviews) {
|
||||
fn send_edits(edits: Mutations, webview: &WebView) {
|
||||
let serialized = serde_json::to_string(&edits).unwrap();
|
||||
|
||||
let (_id, view) = webviews.iter_mut().next().unwrap();
|
||||
|
||||
// todo: use SSE and binary data to send the edits with lower overhead
|
||||
_ = view.evaluate_script(&format!("window.interpreter.handleEdits({})", serialized));
|
||||
_ = webview.evaluate_script(&format!("window.interpreter.handleEdits({})", serialized));
|
||||
}
|
||||
@@ -25,73 +25,62 @@ fn module_loader(root_name: &str) -> String {
|
||||
|
||||
pub(super) fn desktop_handler(
|
||||
request: &Request<Vec<u8>>,
|
||||
asset_root: Option<PathBuf>,
|
||||
custom_head: Option<String>,
|
||||
custom_index: Option<String>,
|
||||
root_name: &str,
|
||||
) -> Result<Response<Vec<u8>>> {
|
||||
// Any content that uses the `dioxus://` scheme will be shuttled through this handler as a "special case".
|
||||
// For now, we only serve two pieces of content which get included as bytes into the final binary.
|
||||
let path = request.uri().to_string().replace("dioxus://", "");
|
||||
|
||||
// all assets should be called from index.html
|
||||
let trimmed = path.trim_start_matches("index.html/");
|
||||
|
||||
if trimmed.is_empty() {
|
||||
// If the request is for the root, we'll serve the index.html file.
|
||||
if request.uri().path() == "/" {
|
||||
// If a custom index is provided, just defer to that, expecting the user to know what they're doing.
|
||||
// we'll look for the closing </body> tag and insert our little module loader there.
|
||||
if let Some(custom_index) = custom_index {
|
||||
let rendered = custom_index
|
||||
let body = match custom_index {
|
||||
Some(custom_index) => custom_index
|
||||
.replace("</body>", &format!("{}</body>", module_loader(root_name)))
|
||||
.into_bytes();
|
||||
Response::builder()
|
||||
.header("Content-Type", "text/html")
|
||||
.body(rendered)
|
||||
.map_err(From::from)
|
||||
} else {
|
||||
// Otherwise, we'll serve the default index.html and apply a custom head if that's specified.
|
||||
let mut template = include_str!("./index.html").to_string();
|
||||
if let Some(custom_head) = custom_head {
|
||||
template = template.replace("<!-- CUSTOM HEAD -->", &custom_head);
|
||||
.into_bytes(),
|
||||
|
||||
None => {
|
||||
// Otherwise, we'll serve the default index.html and apply a custom head if that's specified.
|
||||
let mut template = include_str!("./index.html").to_string();
|
||||
|
||||
if let Some(custom_head) = custom_head {
|
||||
template = template.replace("<!-- CUSTOM HEAD -->", &custom_head);
|
||||
}
|
||||
|
||||
template
|
||||
.replace("<!-- MODULE LOADER -->", &module_loader(root_name))
|
||||
.into_bytes()
|
||||
}
|
||||
template = template.replace("<!-- MODULE LOADER -->", &module_loader(root_name));
|
||||
};
|
||||
|
||||
Response::builder()
|
||||
.header("Content-Type", "text/html")
|
||||
.body(template.into_bytes())
|
||||
.map_err(From::from)
|
||||
}
|
||||
} else if trimmed == "index.js" {
|
||||
Response::builder()
|
||||
.header("Content-Type", "text/javascript")
|
||||
.body(dioxus_interpreter_js::INTERPRETER_JS.as_bytes().to_vec())
|
||||
.map_err(From::from)
|
||||
} else {
|
||||
let asset_root = asset_root
|
||||
.unwrap_or_else(|| get_asset_root().unwrap_or_else(|| Path::new(".").to_path_buf()))
|
||||
.canonicalize()?;
|
||||
|
||||
let asset = asset_root.join(trimmed).canonicalize()?;
|
||||
|
||||
if !asset.starts_with(asset_root) {
|
||||
return Response::builder()
|
||||
.status(StatusCode::FORBIDDEN)
|
||||
.body(String::from("Forbidden").into_bytes())
|
||||
.map_err(From::from);
|
||||
}
|
||||
|
||||
if !asset.exists() {
|
||||
return Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(String::from("Not Found").into_bytes())
|
||||
.map_err(From::from);
|
||||
}
|
||||
|
||||
Response::builder()
|
||||
.header("Content-Type", get_mime_from_path(trimmed)?)
|
||||
.body(std::fs::read(asset)?)
|
||||
.map_err(From::from)
|
||||
return Response::builder()
|
||||
.header("Content-Type", "text/html")
|
||||
.body(body)
|
||||
.map_err(From::from);
|
||||
}
|
||||
|
||||
// Else, try to serve a file from the filesystem.
|
||||
let path = PathBuf::from(request.uri().path().trim_start_matches('/'));
|
||||
|
||||
// If the path is relative, we'll try to serve it from the assets directory.
|
||||
let mut asset = get_asset_root()
|
||||
.unwrap_or_else(|| Path::new(".").to_path_buf())
|
||||
.join(&path);
|
||||
|
||||
if !asset.exists() {
|
||||
asset = PathBuf::from("/").join(path);
|
||||
}
|
||||
|
||||
if asset.exists() {
|
||||
return Response::builder()
|
||||
.header("Content-Type", get_mime_from_path(&asset)?)
|
||||
.body(std::fs::read(asset)?)
|
||||
.map_err(From::from);
|
||||
}
|
||||
|
||||
Response::builder()
|
||||
.status(StatusCode::NOT_FOUND)
|
||||
.body(String::from("Not Found").into_bytes())
|
||||
.map_err(From::from)
|
||||
}
|
||||
|
||||
#[allow(unreachable_code)]
|
||||
@@ -128,7 +117,7 @@ fn get_asset_root() -> Option<PathBuf> {
|
||||
}
|
||||
|
||||
/// Get the mime type from a path-like string
|
||||
fn get_mime_from_path(trimmed: &str) -> Result<&str> {
|
||||
fn get_mime_from_path(trimmed: &Path) -> Result<&'static str> {
|
||||
if trimmed.ends_with(".svg") {
|
||||
return Ok("image/svg+xml");
|
||||
}
|
||||
@@ -143,9 +132,8 @@ fn get_mime_from_path(trimmed: &str) -> Result<&str> {
|
||||
}
|
||||
|
||||
/// Get the mime type from a URI using its extension
|
||||
fn get_mime_by_ext(trimmed: &str) -> &str {
|
||||
let suffix = trimmed.split('.').last();
|
||||
match suffix {
|
||||
fn get_mime_by_ext(trimmed: &Path) -> &'static str {
|
||||
match trimmed.extension().and_then(|e| e.to_str()) {
|
||||
Some("bin") => "application/octet-stream",
|
||||
Some("css") => "text/css",
|
||||
Some("csv") => "text/csv",
|
||||
|
||||
@@ -1,15 +1,18 @@
|
||||
use crate::desktop_context::UserWindowEvent;
|
||||
use crate::desktop_context::{EventData, UserWindowEvent};
|
||||
use futures_util::task::ArcWake;
|
||||
use std::sync::Arc;
|
||||
use wry::application::event_loop::EventLoopProxy;
|
||||
use wry::application::{event_loop::EventLoopProxy, window::WindowId};
|
||||
|
||||
/// Create a waker that will send a poll event to the event loop.
|
||||
///
|
||||
/// This lets the VirtualDom "come up for air" and process events while the main thread is blocked by the WebView.
|
||||
///
|
||||
/// All other IO lives in the Tokio runtime,
|
||||
pub fn tao_waker(proxy: &EventLoopProxy<UserWindowEvent>) -> std::task::Waker {
|
||||
struct DomHandle(EventLoopProxy<UserWindowEvent>);
|
||||
pub fn tao_waker(proxy: &EventLoopProxy<UserWindowEvent>, id: WindowId) -> std::task::Waker {
|
||||
struct DomHandle {
|
||||
proxy: EventLoopProxy<UserWindowEvent>,
|
||||
id: WindowId,
|
||||
}
|
||||
|
||||
// this should be implemented by most platforms, but ios is missing this until
|
||||
// https://github.com/tauri-apps/wry/issues/830 is resolved
|
||||
@@ -18,9 +21,14 @@ pub fn tao_waker(proxy: &EventLoopProxy<UserWindowEvent>) -> std::task::Waker {
|
||||
|
||||
impl ArcWake for DomHandle {
|
||||
fn wake_by_ref(arc_self: &Arc<Self>) {
|
||||
_ = arc_self.0.send_event(UserWindowEvent::Poll);
|
||||
_ = arc_self
|
||||
.proxy
|
||||
.send_event(UserWindowEvent(EventData::Poll, arc_self.id));
|
||||
}
|
||||
}
|
||||
|
||||
futures_util::task::waker(Arc::new(DomHandle(proxy.clone())))
|
||||
futures_util::task::waker(Arc::new(DomHandle {
|
||||
id,
|
||||
proxy: proxy.clone(),
|
||||
}))
|
||||
}
|
||||
@@ -1,5 +1,6 @@
|
||||
use std::rc::Rc;
|
||||
|
||||
use crate::desktop_context::EventData;
|
||||
use crate::protocol;
|
||||
use crate::{desktop_context::UserWindowEvent, Config};
|
||||
use tao::event_loop::{EventLoopProxy, EventLoopWindowTarget};
|
||||
@@ -17,7 +18,6 @@ pub fn build(
|
||||
let window = builder.build(event_loop).unwrap();
|
||||
let file_handler = cfg.file_drop_handler.take();
|
||||
let custom_head = cfg.custom_head.clone();
|
||||
let resource_dir = cfg.resource_dir.clone();
|
||||
let index_file = cfg.custom_index.clone();
|
||||
let root_name = cfg.root_name.clone();
|
||||
|
||||
@@ -38,20 +38,14 @@ pub fn build(
|
||||
.with_transparent(cfg.window.window.transparent)
|
||||
.with_url("dioxus://index.html/")
|
||||
.unwrap()
|
||||
.with_ipc_handler(move |_window: &Window, payload: String| {
|
||||
.with_ipc_handler(move |window: &Window, payload: String| {
|
||||
// defer the event to the main thread
|
||||
if let Ok(message) = serde_json::from_str(&payload) {
|
||||
_ = proxy.send_event(UserWindowEvent::Ipc(message));
|
||||
_ = proxy.send_event(UserWindowEvent(EventData::Ipc(message), window.id()));
|
||||
}
|
||||
})
|
||||
.with_custom_protocol(String::from("dioxus"), move |r| {
|
||||
protocol::desktop_handler(
|
||||
r,
|
||||
resource_dir.clone(),
|
||||
custom_head.clone(),
|
||||
index_file.clone(),
|
||||
&root_name,
|
||||
)
|
||||
protocol::desktop_handler(r, custom_head.clone(), index_file.clone(), &root_name)
|
||||
})
|
||||
.with_file_drop_handler(move |window, evet| {
|
||||
file_handler
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
[package]
|
||||
name = "dioxus"
|
||||
version = "0.2.4"
|
||||
authors = ["Jonathan Kelley"]
|
||||
version = "0.3.1"
|
||||
authors = ["Jonathan Kelley", "Dioxus Labs", "ealmloff"]
|
||||
edition = "2021"
|
||||
description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
|
||||
license = "MIT OR Apache-2.0"
|
||||
@@ -9,14 +9,14 @@ repository = "https://github.com/DioxusLabs/dioxus/"
|
||||
homepage = "https://dioxuslabs.com"
|
||||
documentation = "https://dioxuslabs.com"
|
||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
rust-version = "1.60.0"
|
||||
rust-version = "1.65.0"
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "../core", version = "^0.2.1" }
|
||||
dioxus-html = { path = "../html", version = "^0.2.1", optional = true }
|
||||
dioxus-core-macro = { path = "../core-macro", version = "^0.2.1", optional = true }
|
||||
dioxus-hooks = { path = "../hooks", version = "^0.2.1", optional = true }
|
||||
dioxus-rsx = { path = "../rsx", optional = true }
|
||||
dioxus-core = { path = "../core", version = "^0.3.0" }
|
||||
dioxus-html = { path = "../html", version = "^0.3.0", optional = true }
|
||||
dioxus-core-macro = { path = "../core-macro", version = "^0.3.0", optional = true }
|
||||
dioxus-hooks = { path = "../hooks", version = "^0.3.0", optional = true }
|
||||
dioxus-rsx = { path = "../rsx", version = "0.0.2", optional = true }
|
||||
|
||||
[features]
|
||||
default = ["macro", "hooks", "html"]
|
||||
|
||||
@@ -0,0 +1,325 @@
|
||||
<div align="center">
|
||||
<h1>🌗🚀 Dioxus</h1>
|
||||
<p>
|
||||
<strong>A concurrent, functional, virtual DOM for Rust</strong>
|
||||
</p>
|
||||
</div>
|
||||
|
||||
# Resources
|
||||
|
||||
This overview provides a brief introduction to Dioxus. For a more in-depth guide, make sure to check out:
|
||||
|
||||
- [Getting Started](https://dioxuslabs.com/guide/setup.html)
|
||||
- [Book](https://dioxuslabs.com/guide/)
|
||||
- [Reference](https://dioxuslabs.com/reference)
|
||||
- [Examples](https://github.com/DioxusLabs/example-projects)
|
||||
|
||||
# Overview and Goals
|
||||
|
||||
Dioxus makes it easy to quickly build complex user interfaces with Rust. Any Dioxus app can run in the web browser,
|
||||
as a desktop app, as a mobile app, or anywhere else provided you build the right renderer.
|
||||
|
||||
Dioxus is heavily inspired by React, supporting many of the same concepts:
|
||||
|
||||
- Hooks for state
|
||||
- VirtualDom & diffing
|
||||
- Concurrency, fibers, and asynchronous rendering
|
||||
- JSX-like templating syntax
|
||||
|
||||
If you know React, then you know Dioxus.
|
||||
|
||||
Dioxus is *substantially* more performant than many of the other Rust UI libraries (Yew/Percy) and is *significantly* more performant
|
||||
than React - roughly competitive with InfernoJS.
|
||||
|
||||
Remember: Dioxus is a library for declaring interactive user interfaces - it is not a dedicated renderer. Most 1st party renderers for Dioxus currently only support web technologies.
|
||||
|
||||
## Brief Overview
|
||||
|
||||
All Dioxus apps are built by composing functions that take in a `Scope` which is generic over some `Properties` and return an `Element`.
|
||||
A `Scope` holds relevant state data for the currently-rendered component.
|
||||
|
||||
To launch an app, we use the `launch` method for the specific renderer we want to use. In the launch function, we pass the app's `Component`.
|
||||
|
||||
```rust, ignore
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(app);
|
||||
}
|
||||
|
||||
fn app(cx: Scope) -> Element {
|
||||
cx.render(rsx!("hello world!"))
|
||||
}
|
||||
```
|
||||
|
||||
## Elements & your first component
|
||||
|
||||
To assemble UI trees with Dioxus, you need to use the `render` function on
|
||||
something called `LazyNodes`. To produce `LazyNodes`, you can use the `rsx!`
|
||||
macro or the NodeFactory API. For the most part, you want to use the `rsx!`
|
||||
macro.
|
||||
|
||||
Any element in `rsx!` can have attributes, listeners, and children. For
|
||||
consistency, we force all attributes and listeners to be listed *before*
|
||||
children.
|
||||
|
||||
```rust, ignore
|
||||
let value = "123";
|
||||
|
||||
rsx!(
|
||||
div {
|
||||
class: "my-class {value}", // <--- attribute
|
||||
onclick: move |_| log::info!("clicked!"), // <--- listener
|
||||
h1 { "hello world" }, // <--- child
|
||||
}
|
||||
)
|
||||
```
|
||||
|
||||
The `rsx!` macro accepts attributes in "struct form" and will parse the rest
|
||||
of the body as child elements and rust expressions. Any rust expression that
|
||||
implements `IntoIterator<Item = impl IntoVNode>` will be parsed as a child.
|
||||
|
||||
```rust, ignore
|
||||
rsx!(
|
||||
div {
|
||||
(0..10).map(|_| rsx!(span { "hello world" }))
|
||||
}
|
||||
)
|
||||
|
||||
```
|
||||
|
||||
Used within components, the `rsx!` macro must be rendered into an `Element` with
|
||||
the `render` function on Scope.
|
||||
|
||||
If we want to omit the boilerplate of `cx.render`, we can simply pass in
|
||||
`cx` as the first argument of rsx. This is sometimes useful when we need to
|
||||
render nodes in match statements.
|
||||
|
||||
```rust, ignore
|
||||
fn example(cx: Scope) -> Element {
|
||||
|
||||
// both of these are equivalent
|
||||
cx.render(rsx!("hello world"))
|
||||
|
||||
render!("hello world!")
|
||||
}
|
||||
```
|
||||
|
||||
Putting everything together, we can write a simple component that renders a list of
|
||||
elements:
|
||||
|
||||
```rust, ignore
|
||||
fn app(cx: Scope) -> Element {
|
||||
let name = "dave";
|
||||
cx.render(rsx!(
|
||||
h1 { "Hello, {name}!" }
|
||||
div {
|
||||
class: "my-class",
|
||||
id: "my-id",
|
||||
|
||||
(0..5).map(|i| rsx!(
|
||||
div { key: "{i}"
|
||||
"FizzBuzz: {i}"
|
||||
}
|
||||
))
|
||||
|
||||
}
|
||||
))
|
||||
}
|
||||
```
|
||||
|
||||
## Components
|
||||
|
||||
We can compose these function components to build a complex app. Each new
|
||||
component we design must take some Properties. For components with no explicit
|
||||
properties, we can use the `()` type or simply omit the type altogether.
|
||||
|
||||
In Dioxus, all properties are memoized by default!
|
||||
|
||||
```rust, ignore
|
||||
fn App(cx: Scope) -> Element {
|
||||
cx.render(rsx!(
|
||||
Header {
|
||||
title: "My App",
|
||||
color: "red",
|
||||
}
|
||||
))
|
||||
}
|
||||
```
|
||||
|
||||
Our `Header` component takes a `title` and a `color` property, which we
|
||||
declare on an explicit `HeaderProps` struct.
|
||||
|
||||
```rust, ignore
|
||||
// The `Props` derive macro lets us add additional functionality to how props are interpreted.
|
||||
#[derive(Props, PartialEq)]
|
||||
struct HeaderProps {
|
||||
title: String,
|
||||
color: String,
|
||||
}
|
||||
|
||||
fn Header(cx: Scope<HeaderProps>) -> Element {
|
||||
cx.render(rsx!(
|
||||
div {
|
||||
background_color: "{cx.props.color}"
|
||||
h1 { "{cx.props.title}" }
|
||||
}
|
||||
))
|
||||
}
|
||||
```
|
||||
|
||||
Components may use the `inline_props` macro to completely inline the props
|
||||
definition into the function arguments.
|
||||
|
||||
```rust, ignore
|
||||
#[inline_props]
|
||||
fn Header(cx: Scope, title: String, color: String) -> Element {
|
||||
cx.render(rsx!(
|
||||
div {
|
||||
background_color: "{color}"
|
||||
h1 { "{title}" }
|
||||
}
|
||||
))
|
||||
}
|
||||
```
|
||||
|
||||
Components may also borrow data from their parent component. We just need to
|
||||
attach some lifetimes to the props struct.
|
||||
> Note: we don't need to derive `PartialEq` for borrowed props since they cannot be memoized.
|
||||
|
||||
```rust, ignore
|
||||
#[derive(Props)]
|
||||
struct HeaderProps<'a> {
|
||||
title: &'a str,
|
||||
color: &'a str,
|
||||
}
|
||||
|
||||
fn Header<'a>(cx: Scope<'a, HeaderProps<'a>>) -> Element {
|
||||
cx.render(rsx!(
|
||||
div {
|
||||
background_color: "{cx.props.color}"
|
||||
h1 { "{cx.props.title}" }
|
||||
}
|
||||
))
|
||||
}
|
||||
```
|
||||
|
||||
Components that begin with an uppercase letter may be called with
|
||||
the traditional (for React) curly-brace syntax like so:
|
||||
|
||||
```rust, ignore
|
||||
rsx!(
|
||||
Header { title: "My App" }
|
||||
)
|
||||
```
|
||||
|
||||
Alternatively, if your components begin with a lowercase letter, you can use
|
||||
the function call syntax:
|
||||
|
||||
```rust, ignore
|
||||
rsx!(
|
||||
header( title: "My App" )
|
||||
)
|
||||
```
|
||||
|
||||
## Hooks
|
||||
|
||||
While components are reusable forms of UI elements, hooks are reusable forms
|
||||
of logic. Hooks provide us a way of retrieving state from the `Scope` and using
|
||||
it to render UI elements.
|
||||
|
||||
By convention, all hooks are functions that should start with `use_`. We can
|
||||
use hooks to define state and modify it from within listeners.
|
||||
|
||||
```rust, ignore
|
||||
fn app(cx: Scope) -> Element {
|
||||
let name = use_state(cx, || "world");
|
||||
|
||||
render!("hello {name}!")
|
||||
}
|
||||
```
|
||||
|
||||
Hooks are sensitive to how they are used. To use hooks, you must abide by the
|
||||
["rules of hooks" (borrowed from react)](https://reactjs.org/docs/hooks-rules.html):
|
||||
- Functions with "use_" should not be called in callbacks
|
||||
- Functions with "use_" should not be called out of order
|
||||
- Functions with "use_" should not be called in loops or conditionals
|
||||
|
||||
In a sense, hooks let us add a field of state to our component without declaring
|
||||
an explicit state struct. However, this means we need to "load" the struct in the right
|
||||
order. If that order is wrong, then the hook will pick the wrong state and panic.
|
||||
|
||||
Most hooks you'll write are simply composition of other hooks:
|
||||
|
||||
```rust, ignore
|
||||
fn use_username(cx: &ScopeState, id: Uuid) -> bool {
|
||||
let users = use_context::<Users>(cx);
|
||||
users.get(&id).map(|user| user.logged_in).ok_or(false)
|
||||
}
|
||||
```
|
||||
|
||||
To create entirely new foundational hooks, we can use the `use_hook` method on `ScopeState`.
|
||||
|
||||
```rust, ignore
|
||||
fn use_mut_string(cx: &ScopeState) -> &mut String {
|
||||
cx.use_hook(|_| "Hello".to_string())
|
||||
}
|
||||
```
|
||||
|
||||
If you want to extend Dioxus with some new functionality, you'll probably want to implement a new hook from scratch.
|
||||
|
||||
## Putting it all together
|
||||
|
||||
Using components, templates, and hooks, we can build a simple app.
|
||||
|
||||
```rust, ignore
|
||||
use dioxus::prelude::*;
|
||||
|
||||
fn main() {
|
||||
dioxus_desktop::launch(App);
|
||||
}
|
||||
|
||||
fn App(cx: Scope) -> Element {
|
||||
let count = use_state(cx, || 0);
|
||||
|
||||
cx.render(rsx!(
|
||||
div { "Count: {count}" }
|
||||
button { onclick: move |_| count.set(count + 1), "Increment" }
|
||||
button { onclick: move |_| count.set(count - 1), "Decrement" }
|
||||
))
|
||||
}
|
||||
```
|
||||
|
||||
## Features
|
||||
|
||||
This overview doesn't cover everything. Make sure to check out the tutorial and reference guide on the official
|
||||
website for more details.
|
||||
|
||||
Beyond this overview, Dioxus supports:
|
||||
- Server-side rendering
|
||||
- Concurrent rendering (with async support)
|
||||
- Web/Desktop/Mobile support
|
||||
- Pre-rendering and rehydration
|
||||
- Fragments, Portals, and Suspense
|
||||
- Inline-styles
|
||||
- Custom event handlers
|
||||
- Custom elements
|
||||
- Basic fine-grained reactivity (IE SolidJS/Svelte)
|
||||
- and more!
|
||||
|
||||
Good luck!
|
||||
|
||||
## Inspiration, Resources, Alternatives and Credits
|
||||
|
||||
Dioxus is inspired by:
|
||||
- React: for its hooks, concurrency, suspense
|
||||
- Dodrio: for its research in bump allocation, double buffering, and diffing architecture
|
||||
|
||||
Alternatives to Dioxus include:
|
||||
- Yew: supports function components and web, but no SSR, borrowed data, or bump allocation. Rather slow at times.
|
||||
- Percy: supports function components, web, ssr, but lacks state management
|
||||
- Sycamore: supports function components, web, ssr, but closer to SolidJS than React
|
||||
- MoonZoom/Seed: opinionated frameworks based on the Elm model (message, update) - no hooks
|
||||
|
||||
We've put a lot of work into making Dioxus ergonomic and *familiar*.
|
||||
Our target audience is TypeScript developers looking to switch to Rust for the web - so we need to be comparable to React.
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "fermi"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0"
|
||||
authors = ["Jonathan Kelley"]
|
||||
edition = "2018"
|
||||
description = "Global state management for Dioxus"
|
||||
@@ -13,7 +13,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "../core", version = "^0.2.1" }
|
||||
dioxus-core = { path = "../core", version = "^0.3.0" }
|
||||
im-rc = { version = "15.0.0", features = ["serde"] }
|
||||
log = "0.4.14"
|
||||
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dioxus-hooks"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0"
|
||||
authors = ["Jonathan Kelley"]
|
||||
edition = "2018"
|
||||
description = "Dioxus VirtualDOM renderer for a remote webview instance"
|
||||
@@ -12,11 +12,11 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "../../packages/core", version = "^0.2.1" }
|
||||
dioxus-core = { path = "../../packages/core", version = "^0.3.0" }
|
||||
futures-channel = "0.3.21"
|
||||
log = "0.4"
|
||||
|
||||
|
||||
[dev-dependencies]
|
||||
futures-util = { version = "0.3", default-features = false }
|
||||
dioxus-core = { path = "../../packages/core", version = "^0.2.1" }
|
||||
dioxus-core = { path = "../../packages/core", version = "^0.3.0" }
|
||||
@@ -170,7 +170,7 @@ impl<T> UseRef<T> {
|
||||
|
||||
/// Set the curernt value to `new_value`. This will mark the component as "dirty"
|
||||
///
|
||||
/// This change will propogate immediately, so any other contexts that are
|
||||
/// This change will propagate immediately, so any other contexts that are
|
||||
/// using this RefCell will also be affected. If called during an async context,
|
||||
/// the component will not be re-rendered until the next `.await` call.
|
||||
pub fn set(&self, new: T) {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dioxus-html"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0"
|
||||
authors = ["Jonathan Kelley"]
|
||||
edition = "2018"
|
||||
description = "HTML Element pack for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
|
||||
@@ -11,8 +11,8 @@ documentation = "https://docs.rs/dioxus"
|
||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "../core", version = "^0.2.1" }
|
||||
dioxus-rsx = { path = "../rsx", optional = true }
|
||||
dioxus-core = { path = "../core", version = "^0.3.0" }
|
||||
dioxus-rsx = { path = "../rsx", version = "0.0.2", optional = true }
|
||||
serde = { version = "1", features = ["derive"], optional = true }
|
||||
serde_repr = { version = "0.1", optional = true }
|
||||
wasm-bindgen = { version = "0.2.79", optional = true }
|
||||
@@ -42,10 +42,10 @@ features = [
|
||||
]
|
||||
|
||||
[dev-dependencies]
|
||||
serde_json = "*"
|
||||
serde_json = "1"
|
||||
|
||||
[features]
|
||||
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"]
|
||||
hot-reload-context = ["dioxus-rsx"]
|
||||
@@ -1222,7 +1222,7 @@ builder_constructors! {
|
||||
rows: usize DEFAULT,
|
||||
spellcheck: BoolOrDefault DEFAULT,
|
||||
wrap: Wrap DEFAULT,
|
||||
value: Strign volatile,
|
||||
value: String volatile,
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ use crate::events::{
|
||||
};
|
||||
use crate::geometry::{ClientPoint, Coordinates, ElementPoint, PagePoint, ScreenPoint};
|
||||
use crate::input_data::{decode_key_location, decode_mouse_button_set, MouseButton};
|
||||
use crate::DragData;
|
||||
use keyboard_types::{Code, Key, Modifiers};
|
||||
use std::convert::TryInto;
|
||||
use std::str::FromStr;
|
||||
@@ -40,6 +41,7 @@ uncheck_convert![
|
||||
CompositionEvent => CompositionData,
|
||||
KeyboardEvent => KeyboardData,
|
||||
MouseEvent => MouseData,
|
||||
MouseEvent => DragData,
|
||||
TouchEvent => TouchData,
|
||||
PointerEvent => PointerData,
|
||||
WheelEvent => WheelData,
|
||||
@@ -117,6 +119,14 @@ impl From<&MouseEvent> for MouseData {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&MouseEvent> for DragData {
|
||||
fn from(value: &MouseEvent) -> Self {
|
||||
Self {
|
||||
mouse: MouseData::from(value),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl From<&TouchEvent> for TouchData {
|
||||
fn from(e: &TouchEvent) -> Self {
|
||||
Self {
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dioxus-interpreter-js"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0"
|
||||
edition = "2018"
|
||||
authors = ["Jonathan Kelley"]
|
||||
description = "JS Intepreter for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
|
||||
|
||||
@@ -144,8 +144,13 @@ class Interpreter {
|
||||
this.nodes[root].textContent = text;
|
||||
}
|
||||
SetAttribute(id, field, value, ns) {
|
||||
const node = this.nodes[id];
|
||||
this.SetAttributeInner(node, field, value, ns);
|
||||
if (value === null) {
|
||||
this.RemoveAttribute(id, field, ns);
|
||||
}
|
||||
else {
|
||||
const node = this.nodes[id];
|
||||
this.SetAttributeInner(node, field, value, ns);
|
||||
}
|
||||
}
|
||||
SetAttributeInner(node, field, value, ns) {
|
||||
const name = field;
|
||||
@@ -221,7 +226,6 @@ class Interpreter {
|
||||
}
|
||||
|
||||
MakeTemplateNode(node) {
|
||||
console.log("making template node", node);
|
||||
switch (node.type) {
|
||||
case "Text":
|
||||
return document.createTextNode(node.text);
|
||||
@@ -334,9 +338,6 @@ class Interpreter {
|
||||
case "SetAttribute":
|
||||
this.SetAttribute(edit.id, edit.name, edit.value, edit.ns);
|
||||
break;
|
||||
case "SetBoolAttribute":
|
||||
this.SetAttribute(edit.id, edit.name, edit.value, edit.ns);
|
||||
break;
|
||||
case "RemoveAttribute":
|
||||
this.RemoveAttribute(edit.id, edit.name, edit.ns);
|
||||
break;
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dioxus-liveview"
|
||||
version = "0.1.0"
|
||||
version = "0.3.0"
|
||||
edition = "2021"
|
||||
repository = "https://github.com/DioxusLabs/dioxus/"
|
||||
homepage = "https://dioxuslabs.com"
|
||||
@@ -25,9 +25,9 @@ serde = { version = "1.0.151", features = ["derive"] }
|
||||
serde_json = "1.0.91"
|
||||
tokio-util = { version = "0.7.4", features = ["full"] }
|
||||
|
||||
dioxus-html = { path = "../html", features = ["serialize"], version = "^0.2.1" }
|
||||
dioxus-core = { path = "../core", features = ["serialize"], version = "^0.2.1" }
|
||||
dioxus-interpreter-js = { path = "../interpreter" }
|
||||
dioxus-html = { path = "../html", features = ["serialize"], version = "^0.3.0" }
|
||||
dioxus-core = { path = "../core", features = ["serialize"], version = "^0.3.0" }
|
||||
dioxus-interpreter-js = { path = "../interpreter", version = "0.3.0" }
|
||||
|
||||
# warp
|
||||
warp = { version = "0.3.3", optional = true }
|
||||
@@ -49,7 +49,7 @@ anyhow = "1.0.68"
|
||||
|
||||
[dev-dependencies]
|
||||
tokio = { version = "1.23.0", features = ["full"] }
|
||||
dioxus = { path = "../dioxus" }
|
||||
dioxus = { path = "../dioxus", version = "0.3.0" }
|
||||
warp = "0.3.3"
|
||||
axum = { version = "0.6.1", features = ["ws"] }
|
||||
salvo = { version = "0.37.7", features = ["affix", "ws"] }
|
||||
|
||||
@@ -12,7 +12,13 @@ class IPC {
|
||||
|
||||
let ws = new WebSocket(WS_ADDR);
|
||||
|
||||
function ping() {
|
||||
ws.send("__ping__");
|
||||
}
|
||||
|
||||
ws.onopen = () => {
|
||||
// we ping every 30 seconds to keep the websocket alive
|
||||
setInterval(ping, 30000);
|
||||
ws.send(serializeIpcMessage("initialize"));
|
||||
};
|
||||
|
||||
@@ -21,8 +27,11 @@ class IPC {
|
||||
};
|
||||
|
||||
ws.onmessage = (event) => {
|
||||
let edits = JSON.parse(event.data);
|
||||
window.interpreter.handleEdits(edits);
|
||||
// Ignore pongs
|
||||
if (event.data != "__pong__") {
|
||||
let edits = JSON.parse(event.data);
|
||||
window.interpreter.handleEdits(edits);
|
||||
}
|
||||
};
|
||||
|
||||
this.ws = ws;
|
||||
|
||||
@@ -127,9 +127,13 @@ where
|
||||
_ = vdom.wait_for_work() => {}
|
||||
|
||||
evt = ws.next() => {
|
||||
match evt {
|
||||
match evt.as_ref().map(|o| o.as_deref()) {
|
||||
// respond with a pong every ping to keep the websocket alive
|
||||
Some(Ok("__ping__")) => {
|
||||
ws.send("__pong__".to_string()).await?;
|
||||
}
|
||||
Some(Ok(evt)) => {
|
||||
if let Ok(IpcMessage { params }) = serde_json::from_str::<IpcMessage>(&evt) {
|
||||
if let Ok(IpcMessage { params }) = serde_json::from_str::<IpcMessage>(evt) {
|
||||
vdom.handle_event(¶ms.name, params.data.into_any(), params.element, params.bubbles);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dioxus-mobile"
|
||||
version = "0.2.0"
|
||||
version = "0.3.0"
|
||||
authors = ["Jonathan Kelley"]
|
||||
edition = "2018"
|
||||
description = "Mobile-compatible renderer for Dioxus"
|
||||
@@ -12,7 +12,7 @@ license = "MIT/Apache-2.0"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus-desktop = { path = "../desktop", version = "^0.2.3" }
|
||||
dioxus-desktop = { path = "../desktop", version = "^0.3.0" }
|
||||
|
||||
[lib]
|
||||
doctest = false
|
||||
|
||||
@@ -18,10 +18,9 @@ proc-macro = true
|
||||
syn = { version = "1.0.11", features = ["extra-traits"] }
|
||||
quote = "1.0"
|
||||
dioxus-native-core = { path = "../native-core", version = "^0.2.0" }
|
||||
|
||||
[dev-dependencies]
|
||||
dioxus = { path = "../dioxus" }
|
||||
|
||||
[dev-dependencies]
|
||||
smallvec = "1.6"
|
||||
rustc-hash = "1.1.0"
|
||||
anymap = "0.12.1"
|
||||
@@ -124,6 +124,17 @@ enum DependencyKind {
|
||||
/// # The state attribute
|
||||
/// The state macro declares a member that implements the State trait. This allows you to organize your state into multiple isolated components.
|
||||
/// Unlike the other attributes, the state attribute does not accept any arguments, because a nested state cannot depend on any other part of the state.
|
||||
///
|
||||
/// # Custom values
|
||||
///
|
||||
/// If your state has a custom value type you can specify it with the state attribute.
|
||||
///
|
||||
/// ```rust, ignore
|
||||
/// #[derive(State)]
|
||||
/// #[state(custom_value = MyCustomType)]
|
||||
/// struct MyStruct {
|
||||
/// // ...
|
||||
/// }
|
||||
#[proc_macro_derive(
|
||||
State,
|
||||
attributes(node_dep_state, child_dep_state, parent_dep_state, state)
|
||||
@@ -134,6 +145,33 @@ pub fn state_macro_derive(input: TokenStream) -> TokenStream {
|
||||
}
|
||||
|
||||
fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
|
||||
let custom_type = ast
|
||||
.attrs
|
||||
.iter()
|
||||
.find(|a| a.path.is_ident("state"))
|
||||
.and_then(|attr| {
|
||||
// parse custom_type = "MyType"
|
||||
let assignment = attr.parse_args::<syn::Expr>().unwrap();
|
||||
if let syn::Expr::Assign(assign) = assignment {
|
||||
let (left, right) = (&*assign.left, &*assign.right);
|
||||
if let syn::Expr::Path(e) = left {
|
||||
let path = &e.path;
|
||||
if let Some(ident) = path.get_ident() {
|
||||
if ident == "custom_value" {
|
||||
return match right {
|
||||
syn::Expr::Path(e) => {
|
||||
let path = &e.path;
|
||||
Some(quote! {#path})
|
||||
}
|
||||
_ => None,
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
None
|
||||
})
|
||||
.unwrap_or(quote! {()});
|
||||
let type_name = &ast.ident;
|
||||
let fields: Vec<_> = match &ast.data {
|
||||
syn::Data::Struct(data) => match &data.fields {
|
||||
@@ -162,12 +200,12 @@ fn impl_derive_macro(ast: &syn::DeriveInput) -> TokenStream {
|
||||
let impl_members = state_strct
|
||||
.state_members
|
||||
.iter()
|
||||
.map(|m| m.impl_pass(state_strct.ty));
|
||||
.map(|m| m.impl_pass(state_strct.ty, &custom_type));
|
||||
|
||||
let gen = quote! {
|
||||
#(#impl_members)*
|
||||
impl State for #type_name {
|
||||
const PASSES: &'static [dioxus_native_core::AnyPass<dioxus_native_core::node::Node<Self>>] = &[
|
||||
impl State<#custom_type> for #type_name {
|
||||
const PASSES: &'static [dioxus_native_core::AnyPass<dioxus_native_core::node::Node<Self, #custom_type>>] = &[
|
||||
#(#passes),*
|
||||
];
|
||||
const MASKS: &'static [dioxus_native_core::NodeMask] = &[#(#member_types::NODE_MASK),*];
|
||||
@@ -378,7 +416,11 @@ impl<'a> StateMember<'a> {
|
||||
}
|
||||
|
||||
/// generate code to call the resolve function for the state. This does not handle checking if resolving the state is necessary, or marking the states that depend on this state as dirty.
|
||||
fn impl_pass(&self, parent_type: &Ident) -> quote::__private::TokenStream {
|
||||
fn impl_pass(
|
||||
&self,
|
||||
parent_type: &Ident,
|
||||
custom_type: impl ToTokens,
|
||||
) -> quote::__private::TokenStream {
|
||||
let ident = &self.mem.ident;
|
||||
let get_ctx = if let Some(ctx_ty) = &self.ctx_ty {
|
||||
if ctx_ty == &parse_quote!(()) {
|
||||
@@ -393,13 +435,14 @@ impl<'a> StateMember<'a> {
|
||||
|
||||
let ty = &self.mem.ty;
|
||||
let unit_type = &self.mem.unit_type;
|
||||
let node_view = quote!(dioxus_native_core::node_ref::NodeView::new(unsafe{&*{&node.node_data as *const _}}, #ty::NODE_MASK));
|
||||
let node_view =
|
||||
quote!(dioxus_native_core::node_ref::NodeView::new(&node.node_data, #ty::NODE_MASK));
|
||||
let dep_idents = self.dep_mems.iter().map(|m| &m.ident);
|
||||
let impl_specific = match self.dep_kind {
|
||||
DependencyKind::Node => {
|
||||
quote! {
|
||||
impl dioxus_native_core::NodePass<dioxus_native_core::node::Node<#parent_type>> for #unit_type {
|
||||
fn pass(&self, node: &mut dioxus_native_core::node::Node<#parent_type>, ctx: &dioxus_native_core::SendAnyMap) -> bool {
|
||||
impl dioxus_native_core::NodePass<dioxus_native_core::node::Node<#parent_type, #custom_type>> for #unit_type {
|
||||
fn pass(&self, node: &mut dioxus_native_core::node::Node<#parent_type, #custom_type>, ctx: &dioxus_native_core::SendAnyMap) -> bool {
|
||||
node.state.#ident.reduce(#node_view, (#(&node.state.#dep_idents,)*), #get_ctx)
|
||||
}
|
||||
}
|
||||
@@ -436,11 +479,11 @@ impl<'a> StateMember<'a> {
|
||||
}
|
||||
};
|
||||
quote!(
|
||||
impl dioxus_native_core::UpwardPass<dioxus_native_core::node::Node<#parent_type>> for #unit_type{
|
||||
impl dioxus_native_core::UpwardPass<dioxus_native_core::node::Node<#parent_type, #custom_type>> for #unit_type{
|
||||
fn pass<'a>(
|
||||
&self,
|
||||
node: &mut dioxus_native_core::node::Node<#parent_type>,
|
||||
children: &mut dyn Iterator<Item = &'a mut dioxus_native_core::node::Node<#parent_type>>,
|
||||
node: &mut dioxus_native_core::node::Node<#parent_type, #custom_type>,
|
||||
children: &mut dyn Iterator<Item = &'a mut dioxus_native_core::node::Node<#parent_type, #custom_type>>,
|
||||
ctx: &dioxus_native_core::SendAnyMap,
|
||||
) -> dioxus_native_core::PassReturn {
|
||||
let update = node.state.#ident.reduce(#node_view, children.map(|c| (#(&c.state.#dep_idents,)*)), #get_ctx);
|
||||
@@ -480,8 +523,8 @@ impl<'a> StateMember<'a> {
|
||||
}
|
||||
};
|
||||
quote!(
|
||||
impl dioxus_native_core::DownwardPass<dioxus_native_core::node::Node<#parent_type>> for #unit_type {
|
||||
fn pass(&self, node: &mut dioxus_native_core::node::Node<#parent_type>, parent: Option<&mut dioxus_native_core::node::Node<#parent_type>>, ctx: &dioxus_native_core::SendAnyMap) -> dioxus_native_core::PassReturn{
|
||||
impl dioxus_native_core::DownwardPass<dioxus_native_core::node::Node<#parent_type, #custom_type>> for #unit_type {
|
||||
fn pass(&self, node: &mut dioxus_native_core::node::Node<#parent_type, #custom_type>, parent: Option<&mut dioxus_native_core::node::Node<#parent_type, #custom_type>>, ctx: &dioxus_native_core::SendAnyMap) -> dioxus_native_core::PassReturn{
|
||||
let update = node.state.#ident.reduce(#node_view, parent.as_ref().map(|p| (#(&p.state.#dep_idents,)*)), #get_ctx);
|
||||
#update
|
||||
}
|
||||
|
||||
@@ -43,7 +43,6 @@ impl ParentDepState for ParentDepCallCounter {
|
||||
const NODE_MASK: NodeMask = NodeMask::ALL;
|
||||
fn reduce(&mut self, _node: NodeView, _parent: Option<(&Self,)>, _ctx: &Self::Ctx) -> bool {
|
||||
self.0 += 1;
|
||||
println!("ParentDepCallCounter::reduce on {:?}\n{}", _node, self.0);
|
||||
true
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,21 +11,24 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "../core", version = "^0.2.1" }
|
||||
dioxus-html = { path = "../html", version = "^0.2.1" }
|
||||
dioxus-core-macro = { path = "../core-macro", version = "^0.2.1" }
|
||||
dioxus-core = { path = "../core", version = "^0.3.0" }
|
||||
dioxus-html = { path = "../html", version = "^0.3.0" }
|
||||
dioxus-core-macro = { path = "../core-macro", version = "^0.3.0" }
|
||||
|
||||
taffy = "0.2.1"
|
||||
smallvec = "1.6"
|
||||
rustc-hash = "1.1.0"
|
||||
anymap = "0.12.1"
|
||||
anymap = "1.0.0-beta.2"
|
||||
slab = "0.4"
|
||||
parking_lot = "0.12.1"
|
||||
crossbeam-deque = "0.8.2"
|
||||
dashmap = "5.4.0"
|
||||
|
||||
# for parsing attributes
|
||||
lightningcss = "1.0.0-alpha.39"
|
||||
|
||||
[dev-dependencies]
|
||||
rand = "0.8.5"
|
||||
dioxus = { path = "../dioxus", version = "^0.2.1" }
|
||||
dioxus = { path = "../dioxus", version = "^0.3.0" }
|
||||
dioxus-native-core-macro = { path = "../native-core-macro" }
|
||||
tokio = { version = "*", features = ["full"] }
|
||||
@@ -1,7 +1,7 @@
|
||||
/*
|
||||
- [ ] pub display: Display,
|
||||
- [x] pub position_type: PositionType, --> kinda, taffy doesnt support everything
|
||||
- [ ] pub direction: Direction,
|
||||
- [ ] pub display: Display, ----> taffy doesnt support all display types
|
||||
- [x] pub position_type: PositionType, --> taffy doesnt support everything
|
||||
- [x] pub direction: Direction,
|
||||
|
||||
- [x] pub flex_direction: FlexDirection,
|
||||
- [x] pub flex_wrap: FlexWrap,
|
||||
@@ -9,7 +9,7 @@
|
||||
- [x] pub flex_shrink: f32,
|
||||
- [x] pub flex_basis: Dimension,
|
||||
|
||||
- [x] pub overflow: Overflow, ---> kinda implemented... taffy doesnt have support for directional overflow
|
||||
- [x] pub overflow: Overflow, ---> taffy doesnt have support for directional overflow
|
||||
|
||||
- [x] pub align_items: AlignItems,
|
||||
- [x] pub align_self: AlignSelf,
|
||||
@@ -19,16 +19,27 @@
|
||||
- [x] pub padding: Rect<Dimension>,
|
||||
|
||||
- [x] pub justify_content: JustifyContent,
|
||||
- [ ] pub position: Rect<Dimension>,
|
||||
- [x] pub position: Rect<Dimension>,
|
||||
- [x] pub border: Rect<Dimension>,
|
||||
|
||||
- [ ] pub size: Size<Dimension>, ----> ??? seems to only be relevant for input?
|
||||
- [ ] pub size: Size<Dimension>, ----> seems to only be relevant for input?
|
||||
- [ ] pub min_size: Size<Dimension>,
|
||||
- [ ] pub max_size: Size<Dimension>,
|
||||
|
||||
- [ ] pub aspect_ratio: Number,
|
||||
- [ ] pub aspect_ratio: Number, ----> parsing is done, but taffy doesnt support it
|
||||
*/
|
||||
|
||||
use lightningcss::properties::{align, display, flex, position, size};
|
||||
use lightningcss::{
|
||||
properties::{align::GapValue, border::BorderSideWidth, Property, PropertyId},
|
||||
stylesheet::ParserOptions,
|
||||
traits::Parse,
|
||||
values::{
|
||||
length::{Length, LengthPercentageOrAuto, LengthValue},
|
||||
percentage::DimensionPercentage,
|
||||
ratio::Ratio,
|
||||
},
|
||||
};
|
||||
use taffy::{
|
||||
prelude::*,
|
||||
style::{FlexDirection, PositionType},
|
||||
@@ -36,229 +47,311 @@ use taffy::{
|
||||
|
||||
/// applies the entire html namespace defined in dioxus-html
|
||||
pub fn apply_layout_attributes(name: &str, value: &str, style: &mut Style) {
|
||||
match name {
|
||||
"align-content"
|
||||
| "align-items"
|
||||
| "align-self" => apply_align(name, value, style),
|
||||
|
||||
"animation"
|
||||
| "animation-delay"
|
||||
| "animation-direction"
|
||||
| "animation-duration"
|
||||
| "animation-fill-mode"
|
||||
| "animation-iteration-count"
|
||||
| "animation-name"
|
||||
| "animation-play-state"
|
||||
| "animation-timing-function" => apply_animation(name, value, style),
|
||||
|
||||
"backface-visibility" => {}
|
||||
|
||||
"border"
|
||||
| "border-bottom"
|
||||
| "border-bottom-color"
|
||||
| "border-bottom-left-radius"
|
||||
| "border-bottom-right-radius"
|
||||
| "border-bottom-style"
|
||||
| "border-bottom-width"
|
||||
| "border-collapse"
|
||||
| "border-color"
|
||||
| "border-image"
|
||||
| "border-image-outset"
|
||||
| "border-image-repeat"
|
||||
| "border-image-slice"
|
||||
| "border-image-source"
|
||||
| "border-image-width"
|
||||
| "border-left"
|
||||
| "border-left-color"
|
||||
| "border-left-style"
|
||||
| "border-left-width"
|
||||
| "border-radius"
|
||||
| "border-right"
|
||||
| "border-right-color"
|
||||
| "border-right-style"
|
||||
| "border-right-width"
|
||||
| "border-spacing"
|
||||
| "border-style"
|
||||
| "border-top"
|
||||
| "border-top-color"
|
||||
| "border-top-left-radius"
|
||||
| "border-top-right-radius"
|
||||
| "border-top-style"
|
||||
| "border-top-width"
|
||||
| "border-width" => apply_border(name, value, style),
|
||||
|
||||
"bottom" => {}
|
||||
"box-shadow" => {}
|
||||
"box-sizing" => {}
|
||||
"caption-side" => {}
|
||||
"clear" => {}
|
||||
"clip" => {}
|
||||
|
||||
"column-count"
|
||||
| "column-fill"
|
||||
| "column-gap"
|
||||
| "column-rule"
|
||||
| "column-rule-color"
|
||||
| "column-rule-style"
|
||||
| "column-rule-width"
|
||||
| "column-span"
|
||||
// add column-width
|
||||
| "column-width" => apply_column(name, value, style),
|
||||
|
||||
"columns" => {}
|
||||
|
||||
"content" => {}
|
||||
"counter-increment" => {}
|
||||
"counter-reset" => {}
|
||||
|
||||
"cursor" => {}
|
||||
"direction" => {}
|
||||
|
||||
"display" => apply_display(name, value, style),
|
||||
|
||||
"empty-cells" => {}
|
||||
|
||||
"flex"
|
||||
| "flex-basis"
|
||||
| "flex-direction"
|
||||
| "flex-flow"
|
||||
| "flex-grow"
|
||||
| "flex-shrink"
|
||||
| "flex-wrap" => apply_flex(name, value, style),
|
||||
|
||||
"float" => {},
|
||||
|
||||
"font-style"
|
||||
| "font-variant"
|
||||
| "font-weight"
|
||||
| "font-size"
|
||||
| "line-height"
|
||||
| "font-family" => apply_font(name, value, style),
|
||||
|
||||
"height" => {
|
||||
if let Some(v) = parse_value(value){
|
||||
style.size.height = v;
|
||||
if let Ok(property) =
|
||||
Property::parse_string(PropertyId::from(name), value, ParserOptions::default())
|
||||
{
|
||||
match property {
|
||||
Property::Display(display) => match display {
|
||||
display::Display::Keyword(display::DisplayKeyword::None) => {
|
||||
style.display = Display::None
|
||||
}
|
||||
display::Display::Pair(pair) => {
|
||||
if let display::DisplayInside::Flex(_) = pair.inside {
|
||||
style.display = Display::Flex
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
},
|
||||
Property::Position(position) => {
|
||||
style.position_type = match position {
|
||||
position::Position::Relative => PositionType::Relative,
|
||||
position::Position::Absolute => PositionType::Absolute,
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
Property::Top(top) => style.position.top = convert_length_percentage_or_auto(top),
|
||||
Property::Bottom(bottom) => {
|
||||
style.position.bottom = convert_length_percentage_or_auto(bottom)
|
||||
}
|
||||
Property::Left(left) => style.position.left = convert_length_percentage_or_auto(left),
|
||||
Property::Right(right) => {
|
||||
style.position.right = convert_length_percentage_or_auto(right)
|
||||
}
|
||||
Property::Inset(inset) => {
|
||||
style.position.top = convert_length_percentage_or_auto(inset.top);
|
||||
style.position.bottom = convert_length_percentage_or_auto(inset.bottom);
|
||||
style.position.left = convert_length_percentage_or_auto(inset.left);
|
||||
style.position.right = convert_length_percentage_or_auto(inset.right);
|
||||
}
|
||||
Property::BorderTopWidth(width) => {
|
||||
style.border.top = convert_border_side_width(width);
|
||||
}
|
||||
Property::BorderBottomWidth(width) => {
|
||||
style.border.bottom = convert_border_side_width(width);
|
||||
}
|
||||
Property::BorderLeftWidth(width) => {
|
||||
style.border.left = convert_border_side_width(width);
|
||||
}
|
||||
Property::BorderRightWidth(width) => {
|
||||
style.border.right = convert_border_side_width(width);
|
||||
}
|
||||
Property::BorderWidth(width) => {
|
||||
style.border.top = convert_border_side_width(width.top);
|
||||
style.border.bottom = convert_border_side_width(width.bottom);
|
||||
style.border.left = convert_border_side_width(width.left);
|
||||
style.border.right = convert_border_side_width(width.right);
|
||||
}
|
||||
Property::Border(border) => {
|
||||
let width = convert_border_side_width(border.width);
|
||||
style.border.top = width;
|
||||
style.border.bottom = width;
|
||||
style.border.left = width;
|
||||
style.border.right = width;
|
||||
}
|
||||
Property::BorderTop(top) => {
|
||||
style.border.top = convert_border_side_width(top.width);
|
||||
}
|
||||
Property::BorderBottom(bottom) => {
|
||||
style.border.bottom = convert_border_side_width(bottom.width);
|
||||
}
|
||||
Property::BorderLeft(left) => {
|
||||
style.border.left = convert_border_side_width(left.width);
|
||||
}
|
||||
Property::BorderRight(right) => {
|
||||
style.border.right = convert_border_side_width(right.width);
|
||||
}
|
||||
Property::FlexDirection(flex_direction, _) => {
|
||||
use FlexDirection::*;
|
||||
style.flex_direction = match flex_direction {
|
||||
flex::FlexDirection::Row => Row,
|
||||
flex::FlexDirection::RowReverse => RowReverse,
|
||||
flex::FlexDirection::Column => Column,
|
||||
flex::FlexDirection::ColumnReverse => ColumnReverse,
|
||||
}
|
||||
}
|
||||
Property::FlexWrap(wrap, _) => {
|
||||
use FlexWrap::*;
|
||||
style.flex_wrap = match wrap {
|
||||
flex::FlexWrap::NoWrap => NoWrap,
|
||||
flex::FlexWrap::Wrap => Wrap,
|
||||
flex::FlexWrap::WrapReverse => WrapReverse,
|
||||
}
|
||||
}
|
||||
Property::FlexGrow(grow, _) => {
|
||||
style.flex_grow = grow;
|
||||
}
|
||||
Property::FlexShrink(shrink, _) => {
|
||||
style.flex_shrink = shrink;
|
||||
}
|
||||
Property::FlexBasis(basis, _) => {
|
||||
style.flex_basis = convert_length_percentage_or_auto(basis);
|
||||
}
|
||||
Property::Flex(flex, _) => {
|
||||
style.flex_grow = flex.grow;
|
||||
style.flex_shrink = flex.shrink;
|
||||
style.flex_basis = convert_length_percentage_or_auto(flex.basis);
|
||||
}
|
||||
Property::AlignContent(align, _) => {
|
||||
use AlignContent::*;
|
||||
style.align_content = match align {
|
||||
align::AlignContent::ContentDistribution(distribution) => match distribution {
|
||||
align::ContentDistribution::SpaceBetween => SpaceBetween,
|
||||
align::ContentDistribution::SpaceAround => SpaceAround,
|
||||
align::ContentDistribution::SpaceEvenly => SpaceEvenly,
|
||||
align::ContentDistribution::Stretch => Stretch,
|
||||
},
|
||||
align::AlignContent::ContentPosition {
|
||||
value: position, ..
|
||||
} => match position {
|
||||
align::ContentPosition::Center => Center,
|
||||
align::ContentPosition::Start | align::ContentPosition::FlexStart => {
|
||||
FlexStart
|
||||
}
|
||||
align::ContentPosition::End | align::ContentPosition::FlexEnd => FlexEnd,
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
}
|
||||
Property::JustifyContent(justify, _) => {
|
||||
use JustifyContent::*;
|
||||
style.justify_content = match justify {
|
||||
align::JustifyContent::ContentDistribution(distribution) => {
|
||||
match distribution {
|
||||
align::ContentDistribution::SpaceBetween => SpaceBetween,
|
||||
align::ContentDistribution::SpaceAround => SpaceAround,
|
||||
align::ContentDistribution::SpaceEvenly => SpaceEvenly,
|
||||
_ => return,
|
||||
}
|
||||
}
|
||||
align::JustifyContent::ContentPosition {
|
||||
value: position, ..
|
||||
} => match position {
|
||||
align::ContentPosition::Center => Center,
|
||||
// start ignores -reverse flex-direction but there is no way to specify that in Taffy
|
||||
align::ContentPosition::Start | align::ContentPosition::FlexStart => {
|
||||
FlexStart
|
||||
}
|
||||
// end ignores -reverse flex-direction but there is no way to specify that in Taffy
|
||||
align::ContentPosition::End | align::ContentPosition::FlexEnd => FlexEnd,
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
}
|
||||
Property::AlignSelf(align, _) => {
|
||||
use AlignSelf::*;
|
||||
style.align_self = match align {
|
||||
align::AlignSelf::Auto => Auto,
|
||||
align::AlignSelf::Stretch => Stretch,
|
||||
align::AlignSelf::BaselinePosition(_) => Baseline,
|
||||
align::AlignSelf::SelfPosition {
|
||||
value: position, ..
|
||||
} => match position {
|
||||
align::SelfPosition::Center => Center,
|
||||
align::SelfPosition::Start
|
||||
| align::SelfPosition::SelfStart
|
||||
| align::SelfPosition::FlexStart => FlexStart,
|
||||
align::SelfPosition::End
|
||||
| align::SelfPosition::SelfEnd
|
||||
| align::SelfPosition::FlexEnd => FlexEnd,
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
}
|
||||
Property::AlignItems(align, _) => {
|
||||
use AlignItems::*;
|
||||
style.align_items = match align {
|
||||
align::AlignItems::BaselinePosition(_) => Baseline,
|
||||
align::AlignItems::Stretch => Stretch,
|
||||
align::AlignItems::SelfPosition {
|
||||
value: position, ..
|
||||
} => match position {
|
||||
align::SelfPosition::Center => Center,
|
||||
align::SelfPosition::FlexStart => FlexStart,
|
||||
align::SelfPosition::FlexEnd => FlexEnd,
|
||||
_ => return,
|
||||
},
|
||||
_ => return,
|
||||
};
|
||||
}
|
||||
Property::RowGap(row_gap) => {
|
||||
style.gap.width = convert_gap_value(row_gap);
|
||||
}
|
||||
Property::ColumnGap(column_gap) => {
|
||||
style.gap.height = convert_gap_value(column_gap);
|
||||
}
|
||||
Property::Gap(gap) => {
|
||||
style.gap = Size {
|
||||
width: convert_gap_value(gap.row),
|
||||
height: convert_gap_value(gap.column),
|
||||
};
|
||||
}
|
||||
Property::MarginTop(margin) => {
|
||||
style.margin.top = convert_length_percentage_or_auto(margin);
|
||||
}
|
||||
Property::MarginBottom(margin) => {
|
||||
style.margin.bottom = convert_length_percentage_or_auto(margin);
|
||||
}
|
||||
Property::MarginLeft(margin) => {
|
||||
style.margin.left = convert_length_percentage_or_auto(margin);
|
||||
}
|
||||
Property::MarginRight(margin) => {
|
||||
style.margin.right = convert_length_percentage_or_auto(margin);
|
||||
}
|
||||
Property::Margin(margin) => {
|
||||
style.margin = Rect {
|
||||
top: convert_length_percentage_or_auto(margin.top),
|
||||
bottom: convert_length_percentage_or_auto(margin.bottom),
|
||||
left: convert_length_percentage_or_auto(margin.left),
|
||||
right: convert_length_percentage_or_auto(margin.right),
|
||||
};
|
||||
}
|
||||
Property::PaddingTop(padding) => {
|
||||
style.padding.top = convert_length_percentage_or_auto(padding);
|
||||
}
|
||||
Property::PaddingBottom(padding) => {
|
||||
style.padding.bottom = convert_length_percentage_or_auto(padding);
|
||||
}
|
||||
Property::PaddingLeft(padding) => {
|
||||
style.padding.left = convert_length_percentage_or_auto(padding);
|
||||
}
|
||||
Property::PaddingRight(padding) => {
|
||||
style.padding.right = convert_length_percentage_or_auto(padding);
|
||||
}
|
||||
Property::Padding(padding) => {
|
||||
style.padding = Rect {
|
||||
top: convert_length_percentage_or_auto(padding.top),
|
||||
bottom: convert_length_percentage_or_auto(padding.bottom),
|
||||
left: convert_length_percentage_or_auto(padding.left),
|
||||
right: convert_length_percentage_or_auto(padding.right),
|
||||
};
|
||||
}
|
||||
Property::Width(width) => {
|
||||
style.size.width = convert_size(width);
|
||||
}
|
||||
Property::Height(height) => {
|
||||
style.size.height = convert_size(height);
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
// currently not implemented in lightningcss
|
||||
if name == "aspect-ratio" {
|
||||
if let Ok(ratio) = Ratio::parse_string(value) {
|
||||
style.aspect_ratio = Some(ratio.0 / ratio.1);
|
||||
}
|
||||
}
|
||||
"justify-content" => {
|
||||
use JustifyContent::*;
|
||||
style.justify_content = match value {
|
||||
"flex-start" => FlexStart,
|
||||
"flex-end" => FlexEnd,
|
||||
"center" => Center,
|
||||
"space-between" => SpaceBetween,
|
||||
"space-around" => SpaceAround,
|
||||
"space-evenly" => SpaceEvenly,
|
||||
_ => FlexStart,
|
||||
};
|
||||
}
|
||||
"left" => {}
|
||||
"letter-spacing" => {}
|
||||
|
||||
"list-style"
|
||||
| "list-style-image"
|
||||
| "list-style-position"
|
||||
| "list-style-type" => {}
|
||||
|
||||
"margin"
|
||||
| "margin-bottom"
|
||||
| "margin-left"
|
||||
| "margin-right"
|
||||
| "margin-top" => apply_margin(name, value, style),
|
||||
|
||||
"max-height" => {}
|
||||
"max-width" => {}
|
||||
"min-height" => {}
|
||||
"min-width" => {}
|
||||
|
||||
"opacity" => {}
|
||||
"order" => {}
|
||||
"outline" => {}
|
||||
|
||||
"outline-color"
|
||||
| "outline-offset"
|
||||
| "outline-style"
|
||||
| "outline-width" => {}
|
||||
|
||||
"overflow"
|
||||
| "overflow-x"
|
||||
| "overflow-y" => apply_overflow(name, value, style),
|
||||
|
||||
"padding"
|
||||
| "padding-bottom"
|
||||
| "padding-left"
|
||||
| "padding-right"
|
||||
| "padding-top" => apply_padding(name, value, style),
|
||||
|
||||
"page-break-after"
|
||||
| "page-break-before"
|
||||
| "page-break-inside" => {}
|
||||
|
||||
"perspective"
|
||||
| "perspective-origin" => {}
|
||||
|
||||
"position" => {
|
||||
match value {
|
||||
"static" => {}
|
||||
"relative" => style.position_type = PositionType::Relative,
|
||||
"fixed" => {}
|
||||
"absolute" => style.position_type = PositionType::Absolute,
|
||||
"sticky" => {}
|
||||
_ => {}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
"pointer-events" => {}
|
||||
|
||||
"quotes" => {}
|
||||
"resize" => {}
|
||||
"right" => {}
|
||||
"tab-size" => {}
|
||||
"table-layout" => {}
|
||||
|
||||
"top" => {}
|
||||
|
||||
"transform"
|
||||
| "transform-origin"
|
||||
| "transform-style" => apply_transform(name, value, style),
|
||||
|
||||
"transition"
|
||||
| "transition-delay"
|
||||
| "transition-duration"
|
||||
| "transition-property"
|
||||
| "transition-timing-function" => apply_transition(name, value, style),
|
||||
|
||||
"vertical-align" => {}
|
||||
"visibility" => {}
|
||||
"white-space" => {}
|
||||
"width" => {
|
||||
if let Some(v) = parse_value(value){
|
||||
style.size.width = v;
|
||||
}
|
||||
}
|
||||
"word-break" => {}
|
||||
"word-spacing" => {}
|
||||
"word-wrap" => {}
|
||||
"z-index" => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_font(name: &str, value: &str, style: &mut Style) {
|
||||
match name {
|
||||
"font-style" => {}
|
||||
"font-variant" => {}
|
||||
"font-weight" => {}
|
||||
"font-size" => {}
|
||||
"line-height" => {
|
||||
style.size = Size {
|
||||
width: style.size.width,
|
||||
height: parse_value(value).unwrap_or(Dimension::Points(12.0)),
|
||||
}
|
||||
fn convert_length_value(length_value: LengthValue) -> Dimension {
|
||||
match length_value {
|
||||
LengthValue::Px(value) => Dimension::Points(value),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_dimension_percentage(
|
||||
dimension_percentage: DimensionPercentage<LengthValue>,
|
||||
) -> Dimension {
|
||||
match dimension_percentage {
|
||||
DimensionPercentage::Dimension(value) => convert_length_value(value),
|
||||
DimensionPercentage::Percentage(percentage) => Dimension::Percent(percentage.0),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_length_percentage_or_auto(
|
||||
length_percentage_or_auto: LengthPercentageOrAuto,
|
||||
) -> Dimension {
|
||||
match length_percentage_or_auto {
|
||||
LengthPercentageOrAuto::Auto => Dimension::Auto,
|
||||
LengthPercentageOrAuto::LengthPercentage(percentage) => {
|
||||
convert_dimension_percentage(percentage)
|
||||
}
|
||||
"font-family" => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_border_side_width(border_side_width: BorderSideWidth) -> Dimension {
|
||||
match border_side_width {
|
||||
BorderSideWidth::Length(Length::Value(value)) => convert_length_value(value),
|
||||
BorderSideWidth::Thick => Dimension::Points(5.0),
|
||||
BorderSideWidth::Medium => Dimension::Points(3.0),
|
||||
BorderSideWidth::Thin => Dimension::Points(1.0),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_gap_value(gap_value: GapValue) -> Dimension {
|
||||
match gap_value {
|
||||
GapValue::LengthPercentage(dim) => convert_dimension_percentage(dim),
|
||||
GapValue::Normal => Dimension::Auto,
|
||||
}
|
||||
}
|
||||
|
||||
fn convert_size(size: size::Size) -> Dimension {
|
||||
match size {
|
||||
size::Size::Auto => Dimension::Auto,
|
||||
size::Size::LengthPercentage(length) => convert_dimension_percentage(length),
|
||||
_ => todo!(),
|
||||
}
|
||||
}
|
||||
|
||||
@@ -280,312 +373,3 @@ pub fn parse_value(value: &str) -> Option<Dimension> {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_overflow(_name: &str, _value: &str, _style: &mut Style) {
|
||||
// todo: add overflow support to taffy
|
||||
}
|
||||
|
||||
fn apply_display(_name: &str, value: &str, style: &mut Style) {
|
||||
style.display = match value {
|
||||
"flex" => Display::Flex,
|
||||
"block" => Display::None,
|
||||
_ => Display::Flex,
|
||||
}
|
||||
|
||||
// TODO: there are way more variants
|
||||
// taffy needs to be updated to handle them
|
||||
//
|
||||
// "block" => Display::Block,
|
||||
// "inline" => Display::Inline,
|
||||
// "inline-block" => Display::InlineBlock,
|
||||
// "inline-table" => Display::InlineTable,
|
||||
// "list-item" => Display::ListItem,
|
||||
// "run-in" => Display::RunIn,
|
||||
// "table" => Display::Table,
|
||||
// "table-caption" => Display::TableCaption,
|
||||
// "table-cell" => Display::TableCell,
|
||||
// "table-column" => Display::TableColumn,
|
||||
// "table-column-group" => Display::TableColumnGroup,
|
||||
// "table-footer-group" => Display::TableFooterGroup,
|
||||
// "table-header-group" => Display::TableHeaderGroup,
|
||||
// "table-row" => Display::TableRow,
|
||||
// "table-row-group" => Display::TableRowGroup,
|
||||
// "none" => Display::None,
|
||||
// _ => Display::Inline,
|
||||
}
|
||||
|
||||
fn apply_border(name: &str, value: &str, style: &mut Style) {
|
||||
match name {
|
||||
"border" => {}
|
||||
"border-bottom" => {}
|
||||
"border-bottom-color" => {}
|
||||
"border-bottom-left-radius" => {}
|
||||
"border-bottom-right-radius" => {}
|
||||
"border-bottom-style" => {
|
||||
if style.border.bottom == Dimension::default() {
|
||||
let v = Dimension::Points(1.0);
|
||||
style.border.bottom = v;
|
||||
}
|
||||
}
|
||||
"border-bottom-width" => {
|
||||
if let Some(v) = parse_value(value) {
|
||||
style.border.bottom = v;
|
||||
}
|
||||
}
|
||||
"border-collapse" => {}
|
||||
"border-color" => {}
|
||||
"border-image" => {}
|
||||
"border-image-outset" => {}
|
||||
"border-image-repeat" => {}
|
||||
"border-image-slice" => {}
|
||||
"border-image-source" => {}
|
||||
"border-image-width" => {}
|
||||
"border-left" => {}
|
||||
"border-left-color" => {}
|
||||
"border-left-style" => {
|
||||
if style.border.left == Dimension::default() {
|
||||
let v = Dimension::Points(1.0);
|
||||
style.border.left = v;
|
||||
}
|
||||
}
|
||||
"border-left-width" => {
|
||||
if let Some(v) = parse_value(value) {
|
||||
style.border.left = v;
|
||||
}
|
||||
}
|
||||
"border-radius" => {}
|
||||
"border-right" => {}
|
||||
"border-right-color" => {}
|
||||
"border-right-style" => {
|
||||
let v = Dimension::Points(1.0);
|
||||
style.border.right = v;
|
||||
}
|
||||
"border-right-width" => {
|
||||
if let Some(v) = parse_value(value) {
|
||||
style.border.right = v;
|
||||
}
|
||||
}
|
||||
"border-spacing" => {}
|
||||
"border-style" => {
|
||||
if style.border.top == Dimension::default() {
|
||||
let v = Dimension::Points(1.0);
|
||||
style.border.top = v;
|
||||
}
|
||||
if style.border.bottom == Dimension::default() {
|
||||
let v = Dimension::Points(1.0);
|
||||
style.border.bottom = v;
|
||||
}
|
||||
if style.border.left == Dimension::default() {
|
||||
let v = Dimension::Points(1.0);
|
||||
style.border.left = v;
|
||||
}
|
||||
if style.border.right == Dimension::default() {
|
||||
let v = Dimension::Points(1.0);
|
||||
style.border.right = v;
|
||||
}
|
||||
}
|
||||
"border-top" => {}
|
||||
"border-top-color" => {}
|
||||
"border-top-left-radius" => {}
|
||||
"border-top-right-radius" => {}
|
||||
"border-top-style" => {
|
||||
if style.border.top == Dimension::default() {
|
||||
let v = Dimension::Points(1.0);
|
||||
style.border.top = v;
|
||||
}
|
||||
}
|
||||
"border-top-width" => {
|
||||
if let Some(v) = parse_value(value) {
|
||||
style.border.top = v;
|
||||
}
|
||||
}
|
||||
"border-width" => {
|
||||
let values: Vec<_> = value.split(' ').collect();
|
||||
if values.len() == 1 {
|
||||
if let Some(dim) = parse_value(values[0]) {
|
||||
style.border = Rect {
|
||||
right: dim,
|
||||
left: dim,
|
||||
top: dim,
|
||||
bottom: dim,
|
||||
};
|
||||
}
|
||||
} else {
|
||||
let border_widths = [
|
||||
&mut style.border.top,
|
||||
&mut style.border.bottom,
|
||||
&mut style.border.left,
|
||||
&mut style.border.right,
|
||||
];
|
||||
for (v, width) in values.into_iter().zip(border_widths) {
|
||||
if let Some(w) = parse_value(v) {
|
||||
*width = w;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_animation(name: &str, _value: &str, _style: &mut Style) {
|
||||
match name {
|
||||
"animation" => {}
|
||||
"animation-delay" => {}
|
||||
"animation-direction =>{}" => {}
|
||||
"animation-duration" => {}
|
||||
"animation-fill-mode" => {}
|
||||
"animation-itera =>{}tion-count" => {}
|
||||
"animation-name" => {}
|
||||
"animation-play-state" => {}
|
||||
"animation-timing-function" => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_column(name: &str, _value: &str, _style: &mut Style) {
|
||||
match name {
|
||||
"column-count" => {}
|
||||
"column-fill" => {}
|
||||
"column-gap" => {}
|
||||
"column-rule" => {}
|
||||
"column-rule-color" => {}
|
||||
"column-rule-style" => {}
|
||||
"column-rule-width" => {}
|
||||
"column-span" => {}
|
||||
"column-width" => {}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_flex(name: &str, value: &str, style: &mut Style) {
|
||||
// - [x] pub flex_direction: FlexDirection,
|
||||
// - [x] pub flex_wrap: FlexWrap,
|
||||
// - [x] pub flex_grow: f32,
|
||||
// - [x] pub flex_shrink: f32,
|
||||
// - [x] pub flex_basis: Dimension,
|
||||
|
||||
match name {
|
||||
"flex" => {}
|
||||
"flex-direction" => {
|
||||
use FlexDirection::*;
|
||||
style.flex_direction = match value {
|
||||
"row" => Row,
|
||||
"row-reverse" => RowReverse,
|
||||
"column" => Column,
|
||||
"column-reverse" => ColumnReverse,
|
||||
_ => Row,
|
||||
};
|
||||
}
|
||||
"flex-basis" => {
|
||||
if let Some(v) = parse_value(value) {
|
||||
style.flex_basis = v;
|
||||
}
|
||||
}
|
||||
"flex-flow" => {}
|
||||
"flex-grow" => {
|
||||
if let Ok(val) = value.parse::<f32>() {
|
||||
style.flex_grow = val;
|
||||
}
|
||||
}
|
||||
"flex-shrink" => {
|
||||
if let Ok(px) = value.parse::<f32>() {
|
||||
style.flex_shrink = px;
|
||||
}
|
||||
}
|
||||
"flex-wrap" => {
|
||||
use FlexWrap::*;
|
||||
style.flex_wrap = match value {
|
||||
"nowrap" => NoWrap,
|
||||
"wrap" => Wrap,
|
||||
"wrap-reverse" => WrapReverse,
|
||||
_ => NoWrap,
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_padding(name: &str, value: &str, style: &mut Style) {
|
||||
if let Some(v) = parse_value(value) {
|
||||
match name {
|
||||
"padding" => {
|
||||
style.padding.top = v;
|
||||
style.padding.bottom = v;
|
||||
style.padding.left = v;
|
||||
style.padding.right = v;
|
||||
}
|
||||
"padding-bottom" => style.padding.bottom = v,
|
||||
"padding-left" => style.padding.left = v,
|
||||
"padding-right" => style.padding.right = v,
|
||||
"padding-top" => style.padding.top = v,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_transform(_name: &str, _value: &str, _style: &mut Style) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn apply_transition(_name: &str, _value: &str, _style: &mut Style) {
|
||||
todo!()
|
||||
}
|
||||
|
||||
fn apply_align(name: &str, value: &str, style: &mut Style) {
|
||||
match name {
|
||||
"align-items" => {
|
||||
use AlignItems::*;
|
||||
style.align_items = match value {
|
||||
"flex-start" => FlexStart,
|
||||
"flex-end" => FlexEnd,
|
||||
"center" => Center,
|
||||
"baseline" => Baseline,
|
||||
"stretch" => Stretch,
|
||||
_ => FlexStart,
|
||||
};
|
||||
}
|
||||
"align-content" => {
|
||||
use AlignContent::*;
|
||||
style.align_content = match value {
|
||||
"flex-start" => FlexStart,
|
||||
"flex-end" => FlexEnd,
|
||||
"center" => Center,
|
||||
"space-between" => SpaceBetween,
|
||||
"space-around" => SpaceAround,
|
||||
_ => FlexStart,
|
||||
};
|
||||
}
|
||||
"align-self" => {
|
||||
use AlignSelf::*;
|
||||
style.align_self = match value {
|
||||
"auto" => Auto,
|
||||
"flex-start" => FlexStart,
|
||||
"flex-end" => FlexEnd,
|
||||
"center" => Center,
|
||||
"baseline" => Baseline,
|
||||
"stretch" => Stretch,
|
||||
_ => Auto,
|
||||
};
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn apply_margin(name: &str, value: &str, style: &mut Style) {
|
||||
if let Some(dim) = parse_value(value) {
|
||||
match name {
|
||||
"margin" => {
|
||||
style.margin.top = dim;
|
||||
style.margin.bottom = dim;
|
||||
style.margin.left = dim;
|
||||
style.margin.right = dim;
|
||||
}
|
||||
"margin-top" => style.margin.top = dim,
|
||||
"margin-bottom" => style.margin.bottom = dim,
|
||||
"margin-left" => style.margin.left = dim,
|
||||
"margin-right" => style.margin.right = dim,
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,3 +1,4 @@
|
||||
use std::any::Any;
|
||||
use std::hash::BuildHasherDefault;
|
||||
|
||||
pub use node_ref::NodeMask;
|
||||
@@ -20,7 +21,7 @@ pub mod utils;
|
||||
pub type RealNodeId = NodeId;
|
||||
pub type FxDashMap<K, V> = dashmap::DashMap<K, V, BuildHasherDefault<FxHasher>>;
|
||||
pub type FxDashSet<K> = dashmap::DashSet<K, BuildHasherDefault<FxHasher>>;
|
||||
pub type SendAnyMap = anymap::Map<dyn anymap::any::Any + Send + Sync + 'static>;
|
||||
pub type SendAnyMap = anymap::Map<dyn Any + Send + Sync + 'static>;
|
||||
|
||||
/// Used in derived state macros
|
||||
#[derive(Eq, PartialEq)]
|
||||
|
||||
@@ -1,43 +1,44 @@
|
||||
use crate::{state::State, tree::NodeId};
|
||||
use dioxus_core::ElementId;
|
||||
use dioxus_core::{AnyValue, BorrowedAttributeValue, ElementId};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use std::fmt::Debug;
|
||||
|
||||
/// The node is stored client side and stores only basic data about the node.
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct Node<S: State> {
|
||||
pub struct Node<S: State<V>, V: FromAnyValue + 'static = ()> {
|
||||
/// The transformed state of the node.
|
||||
pub state: S,
|
||||
/// The raw data for the node
|
||||
pub node_data: NodeData,
|
||||
pub node_data: NodeData<V>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct NodeData {
|
||||
pub struct NodeData<V: FromAnyValue = ()> {
|
||||
/// The id of the node
|
||||
pub node_id: NodeId,
|
||||
/// The id of the node in the vdom.
|
||||
pub element_id: Option<ElementId>,
|
||||
/// Additional inforation specific to the node type
|
||||
pub node_type: NodeType,
|
||||
pub node_type: NodeType<V>,
|
||||
}
|
||||
|
||||
/// A type of node with data specific to the node type. The types are a subset of the [VNode] types.
|
||||
#[derive(Debug, Clone)]
|
||||
pub enum NodeType {
|
||||
pub enum NodeType<V: FromAnyValue = ()> {
|
||||
Text {
|
||||
text: String,
|
||||
},
|
||||
Element {
|
||||
tag: String,
|
||||
namespace: Option<String>,
|
||||
attributes: FxHashMap<OwnedAttributeDiscription, OwnedAttributeValue>,
|
||||
attributes: FxHashMap<OwnedAttributeDiscription, OwnedAttributeValue<V>>,
|
||||
listeners: FxHashSet<String>,
|
||||
},
|
||||
Placeholder,
|
||||
}
|
||||
|
||||
impl<S: State> Node<S> {
|
||||
pub(crate) fn new(node_type: NodeType) -> Self {
|
||||
impl<S: State<V>, V: FromAnyValue> Node<S, V> {
|
||||
pub(crate) fn new(node_type: NodeType<V>) -> Self {
|
||||
Node {
|
||||
state: S::default(),
|
||||
node_data: NodeData {
|
||||
@@ -64,24 +65,57 @@ pub struct OwnedAttributeDiscription {
|
||||
/// An attribute on a DOM node, such as `id="my-thing"` or
|
||||
/// `href="https://example.com"`.
|
||||
#[derive(Clone, Copy, Debug)]
|
||||
pub struct OwnedAttributeView<'a> {
|
||||
pub struct OwnedAttributeView<'a, V: FromAnyValue = ()> {
|
||||
/// The discription of the attribute.
|
||||
pub attribute: &'a OwnedAttributeDiscription,
|
||||
|
||||
/// The value of the attribute.
|
||||
pub value: &'a OwnedAttributeValue,
|
||||
pub value: &'a OwnedAttributeValue<V>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
pub enum OwnedAttributeValue {
|
||||
#[derive(Clone)]
|
||||
pub enum OwnedAttributeValue<V: FromAnyValue = ()> {
|
||||
Text(String),
|
||||
Float(f32),
|
||||
Int(i32),
|
||||
Float(f64),
|
||||
Int(i64),
|
||||
Bool(bool),
|
||||
None,
|
||||
Custom(V),
|
||||
}
|
||||
|
||||
impl OwnedAttributeValue {
|
||||
pub trait FromAnyValue: Clone {
|
||||
fn from_any_value(value: &dyn AnyValue) -> Self;
|
||||
}
|
||||
|
||||
impl FromAnyValue for () {
|
||||
fn from_any_value(_: &dyn AnyValue) -> Self {}
|
||||
}
|
||||
|
||||
impl<V: FromAnyValue> Debug for OwnedAttributeValue<V> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
match self {
|
||||
Self::Text(arg0) => f.debug_tuple("Text").field(arg0).finish(),
|
||||
Self::Float(arg0) => f.debug_tuple("Float").field(arg0).finish(),
|
||||
Self::Int(arg0) => f.debug_tuple("Int").field(arg0).finish(),
|
||||
Self::Bool(arg0) => f.debug_tuple("Bool").field(arg0).finish(),
|
||||
Self::Custom(_) => f.debug_tuple("Any").finish(),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: FromAnyValue> From<BorrowedAttributeValue<'_>> for OwnedAttributeValue<V> {
|
||||
fn from(value: BorrowedAttributeValue<'_>) -> Self {
|
||||
match value {
|
||||
BorrowedAttributeValue::Text(text) => Self::Text(text.to_string()),
|
||||
BorrowedAttributeValue::Float(float) => Self::Float(float),
|
||||
BorrowedAttributeValue::Int(int) => Self::Int(int),
|
||||
BorrowedAttributeValue::Bool(bool) => Self::Bool(bool),
|
||||
BorrowedAttributeValue::Any(any) => Self::Custom(V::from_any_value(&*any)),
|
||||
BorrowedAttributeValue::None => panic!("None attribute values result in removing the attribute, not converting it to a None value.")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<V: FromAnyValue> OwnedAttributeValue<V> {
|
||||
pub fn as_text(&self) -> Option<&str> {
|
||||
match self {
|
||||
OwnedAttributeValue::Text(text) => Some(text),
|
||||
@@ -89,14 +123,14 @@ impl OwnedAttributeValue {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_float(&self) -> Option<f32> {
|
||||
pub fn as_float(&self) -> Option<f64> {
|
||||
match self {
|
||||
OwnedAttributeValue::Float(float) => Some(*float),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_int(&self) -> Option<i32> {
|
||||
pub fn as_int(&self) -> Option<i64> {
|
||||
match self {
|
||||
OwnedAttributeValue::Int(int) => Some(*int),
|
||||
_ => None,
|
||||
@@ -110,9 +144,9 @@ impl OwnedAttributeValue {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn as_none(&self) -> Option<()> {
|
||||
pub fn as_custom(&self) -> Option<&V> {
|
||||
match self {
|
||||
OwnedAttributeValue::None => Some(()),
|
||||
OwnedAttributeValue::Custom(custom) => Some(custom),
|
||||
_ => None,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,21 +1,21 @@
|
||||
use dioxus_core::ElementId;
|
||||
|
||||
use crate::{
|
||||
node::{NodeData, NodeType, OwnedAttributeView},
|
||||
node::{FromAnyValue, NodeData, NodeType, OwnedAttributeView},
|
||||
state::union_ordered_iter,
|
||||
RealNodeId,
|
||||
};
|
||||
|
||||
/// A view into a [VNode] with limited access.
|
||||
#[derive(Debug)]
|
||||
pub struct NodeView<'a> {
|
||||
inner: &'a NodeData,
|
||||
pub struct NodeView<'a, V: FromAnyValue = ()> {
|
||||
inner: &'a NodeData<V>,
|
||||
mask: NodeMask,
|
||||
}
|
||||
|
||||
impl<'a> NodeView<'a> {
|
||||
impl<'a, V: FromAnyValue> NodeView<'a, V> {
|
||||
/// Create a new NodeView from a VNode, and mask.
|
||||
pub fn new(node: &'a NodeData, view: NodeMask) -> Self {
|
||||
pub fn new(node: &'a NodeData<V>, view: NodeMask) -> Self {
|
||||
Self {
|
||||
inner: node,
|
||||
mask: view,
|
||||
@@ -55,7 +55,9 @@ impl<'a> NodeView<'a> {
|
||||
}
|
||||
|
||||
/// Get any attributes that are enabled in the mask
|
||||
pub fn attributes<'b>(&'b self) -> Option<impl Iterator<Item = OwnedAttributeView<'a>> + 'b> {
|
||||
pub fn attributes<'b>(
|
||||
&'b self,
|
||||
) -> Option<impl Iterator<Item = OwnedAttributeView<'a, V>> + 'b> {
|
||||
match &self.inner.node_type {
|
||||
NodeType::Element { attributes, .. } => Some(
|
||||
attributes
|
||||
|
||||
+178
-173
@@ -1,112 +1,100 @@
|
||||
use crate::tree::{NodeId, TreeView};
|
||||
use crate::{FxDashMap, FxDashSet, SendAnyMap};
|
||||
use crate::{FxDashSet, SendAnyMap};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use std::collections::BTreeMap;
|
||||
use std::ops::{BitAnd, BitAndAssign, BitOr, BitOrAssign};
|
||||
use std::sync::atomic::{AtomicU64, Ordering};
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Debug, Clone, PartialEq, Eq, Default)]
|
||||
pub struct DirtyNodes {
|
||||
map: BTreeMap<u16, FxHashSet<NodeId>>,
|
||||
#[derive(Default)]
|
||||
struct DirtyNodes {
|
||||
passes_dirty: Vec<u64>,
|
||||
}
|
||||
|
||||
impl DirtyNodes {
|
||||
pub fn insert(&mut self, depth: u16, node_id: NodeId) {
|
||||
self.map
|
||||
.entry(depth)
|
||||
.or_insert_with(FxHashSet::default)
|
||||
.insert(node_id);
|
||||
}
|
||||
|
||||
fn pop_front(&mut self) -> Option<NodeId> {
|
||||
let (&depth, values) = self.map.iter_mut().next()?;
|
||||
let key = *values.iter().next()?;
|
||||
let node_id = values.take(&key)?;
|
||||
if values.is_empty() {
|
||||
self.map.remove(&depth);
|
||||
fn add_node(&mut self, node_id: NodeId) {
|
||||
let node_id = node_id.0;
|
||||
let index = node_id / 64;
|
||||
let bit = node_id % 64;
|
||||
let encoded = 1 << bit;
|
||||
if let Some(passes) = self.passes_dirty.get_mut(index) {
|
||||
*passes |= encoded;
|
||||
} else {
|
||||
self.passes_dirty.resize(index + 1, 0);
|
||||
self.passes_dirty[index] |= encoded;
|
||||
}
|
||||
Some(node_id)
|
||||
}
|
||||
|
||||
fn pop_back(&mut self) -> Option<NodeId> {
|
||||
let (&depth, values) = self.map.iter_mut().rev().next()?;
|
||||
let key = *values.iter().next()?;
|
||||
let node_id = values.take(&key)?;
|
||||
if values.is_empty() {
|
||||
self.map.remove(&depth);
|
||||
}
|
||||
Some(node_id)
|
||||
fn is_empty(&self) -> bool {
|
||||
self.passes_dirty.iter().all(|dirty| *dirty == 0)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn dirty_nodes() {
|
||||
let mut dirty_nodes = DirtyNodes::default();
|
||||
|
||||
dirty_nodes.insert(1, NodeId(1));
|
||||
dirty_nodes.insert(0, NodeId(0));
|
||||
dirty_nodes.insert(2, NodeId(3));
|
||||
dirty_nodes.insert(1, NodeId(2));
|
||||
|
||||
assert_eq!(dirty_nodes.pop_front(), Some(NodeId(0)));
|
||||
assert!(matches!(dirty_nodes.pop_front(), Some(NodeId(1 | 2))));
|
||||
assert!(matches!(dirty_nodes.pop_front(), Some(NodeId(1 | 2))));
|
||||
assert_eq!(dirty_nodes.pop_front(), Some(NodeId(3)));
|
||||
fn pop(&mut self) -> Option<NodeId> {
|
||||
let index = self.passes_dirty.iter().position(|dirty| *dirty != 0)?;
|
||||
let passes = self.passes_dirty[index];
|
||||
let node_id = passes.trailing_zeros();
|
||||
let encoded = 1 << node_id;
|
||||
self.passes_dirty[index] &= !encoded;
|
||||
Some(NodeId((index * 64) + node_id as usize))
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct DirtyNodeStates {
|
||||
dirty: FxDashMap<NodeId, Vec<AtomicU64>>,
|
||||
dirty: BTreeMap<u16, FxHashMap<PassId, DirtyNodes>>,
|
||||
}
|
||||
|
||||
impl DirtyNodeStates {
|
||||
pub fn new(starting_nodes: FxHashMap<NodeId, FxHashSet<PassId>>) -> Self {
|
||||
let this = Self::default();
|
||||
for (node, nodes) in starting_nodes {
|
||||
for pass_id in nodes {
|
||||
this.insert(pass_id, node);
|
||||
}
|
||||
}
|
||||
this
|
||||
}
|
||||
|
||||
pub fn insert(&self, pass_id: PassId, node_id: NodeId) {
|
||||
let pass_id = pass_id.0;
|
||||
let index = pass_id / 64;
|
||||
let bit = pass_id % 64;
|
||||
let encoded = 1 << bit;
|
||||
if let Some(dirty) = self.dirty.get(&node_id) {
|
||||
if let Some(atomic) = dirty.get(index as usize) {
|
||||
atomic.fetch_or(encoded, Ordering::Relaxed);
|
||||
pub fn insert(&mut self, pass_id: PassId, node_id: NodeId, height: u16) {
|
||||
if let Some(dirty) = self.dirty.get_mut(&height) {
|
||||
if let Some(entry) = dirty.get_mut(&pass_id) {
|
||||
entry.add_node(node_id);
|
||||
} else {
|
||||
drop(dirty);
|
||||
let mut write = self.dirty.get_mut(&node_id).unwrap();
|
||||
write.resize_with(index as usize + 1, || AtomicU64::new(0));
|
||||
write[index as usize].fetch_or(encoded, Ordering::Relaxed);
|
||||
let mut entry = DirtyNodes::default();
|
||||
entry.add_node(node_id);
|
||||
dirty.insert(pass_id, entry);
|
||||
}
|
||||
} else {
|
||||
let mut v = Vec::with_capacity(index as usize + 1);
|
||||
v.resize_with(index as usize + 1, || AtomicU64::new(0));
|
||||
v[index as usize].fetch_or(encoded, Ordering::Relaxed);
|
||||
self.dirty.insert(node_id, v);
|
||||
let mut entry = DirtyNodes::default();
|
||||
entry.add_node(node_id);
|
||||
let mut hm = FxHashMap::default();
|
||||
hm.insert(pass_id, entry);
|
||||
self.dirty.insert(height, hm);
|
||||
}
|
||||
}
|
||||
|
||||
fn all_dirty<T>(&self, pass_id: PassId, dirty_nodes: &mut DirtyNodes, tree: &impl TreeView<T>) {
|
||||
let pass_id = pass_id.0;
|
||||
let index = pass_id / 64;
|
||||
let bit = pass_id % 64;
|
||||
let encoded = 1 << bit;
|
||||
for entry in self.dirty.iter() {
|
||||
let node_id = entry.key();
|
||||
let dirty = entry.value();
|
||||
if let Some(atomic) = dirty.get(index as usize) {
|
||||
if atomic.load(Ordering::Relaxed) & encoded != 0 {
|
||||
dirty_nodes.insert(tree.height(*node_id).unwrap(), *node_id);
|
||||
}
|
||||
}
|
||||
fn pop_front(&mut self, pass_id: PassId) -> Option<(u16, NodeId)> {
|
||||
let (&height, values) = self
|
||||
.dirty
|
||||
.iter_mut()
|
||||
.find(|(_, values)| values.contains_key(&pass_id))?;
|
||||
let dirty = values.get_mut(&pass_id)?;
|
||||
let node_id = dirty.pop()?;
|
||||
if dirty.is_empty() {
|
||||
values.remove(&pass_id);
|
||||
}
|
||||
if values.is_empty() {
|
||||
self.dirty.remove(&height);
|
||||
}
|
||||
|
||||
Some((height, node_id))
|
||||
}
|
||||
|
||||
fn pop_back(&mut self, pass_id: PassId) -> Option<(u16, NodeId)> {
|
||||
let (&height, values) = self
|
||||
.dirty
|
||||
.iter_mut()
|
||||
.rev()
|
||||
.find(|(_, values)| values.contains_key(&pass_id))?;
|
||||
let dirty = values.get_mut(&pass_id)?;
|
||||
let node_id = dirty.pop()?;
|
||||
if dirty.is_empty() {
|
||||
values.remove(&pass_id);
|
||||
}
|
||||
if values.is_empty() {
|
||||
self.dirty.remove(&height);
|
||||
}
|
||||
|
||||
Some((height, node_id))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -174,12 +162,12 @@ pub trait UpwardPass<T>: Pass {
|
||||
fn resolve_upward_pass<T, P: UpwardPass<T> + ?Sized>(
|
||||
tree: &mut impl TreeView<T>,
|
||||
pass: &P,
|
||||
mut dirty: DirtyNodes,
|
||||
dirty_states: &DirtyNodeStates,
|
||||
dirty_states: &mut DirtyNodeStates,
|
||||
nodes_updated: &FxDashSet<NodeId>,
|
||||
ctx: &SendAnyMap,
|
||||
) {
|
||||
while let Some(id) = dirty.pop_back() {
|
||||
let pass_id = pass.pass_id();
|
||||
while let Some((height, id)) = dirty_states.pop_back(pass_id) {
|
||||
let (node, mut children) = tree.parent_child_mut(id).unwrap();
|
||||
let result = pass.pass(node, &mut children, ctx);
|
||||
drop(children);
|
||||
@@ -188,12 +176,11 @@ fn resolve_upward_pass<T, P: UpwardPass<T> + ?Sized>(
|
||||
if let Some(id) = tree.parent_id(id) {
|
||||
if result.mark_dirty {
|
||||
for dependant in pass.dependants() {
|
||||
dirty_states.insert(*dependant, id);
|
||||
dirty_states.insert(*dependant, id, height - 1);
|
||||
}
|
||||
}
|
||||
if result.progress {
|
||||
let height = tree.height(id).unwrap();
|
||||
dirty.insert(height, id);
|
||||
if result.progress && height > 0 {
|
||||
dirty_states.insert(pass_id, id, height - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -207,12 +194,12 @@ pub trait DownwardPass<T>: Pass {
|
||||
fn resolve_downward_pass<T, P: DownwardPass<T> + ?Sized>(
|
||||
tree: &mut impl TreeView<T>,
|
||||
pass: &P,
|
||||
mut dirty: DirtyNodes,
|
||||
dirty_states: &DirtyNodeStates,
|
||||
dirty_states: &mut DirtyNodeStates,
|
||||
nodes_updated: &FxDashSet<NodeId>,
|
||||
ctx: &SendAnyMap,
|
||||
) {
|
||||
while let Some(id) = dirty.pop_front() {
|
||||
let pass_id = pass.pass_id();
|
||||
while let Some((height, id)) = dirty_states.pop_front(pass_id) {
|
||||
let (node, parent) = tree.node_parent_mut(id).unwrap();
|
||||
let result = pass.pass(node, parent, ctx);
|
||||
if result.mark_dirty {
|
||||
@@ -222,12 +209,11 @@ fn resolve_downward_pass<T, P: DownwardPass<T> + ?Sized>(
|
||||
for id in tree.children_ids(id).unwrap() {
|
||||
if result.mark_dirty {
|
||||
for dependant in pass.dependants() {
|
||||
dirty_states.insert(*dependant, *id);
|
||||
dirty_states.insert(*dependant, *id, height + 1);
|
||||
}
|
||||
}
|
||||
if result.progress {
|
||||
let height = tree.height(*id).unwrap();
|
||||
dirty.insert(height, *id);
|
||||
dirty_states.insert(pass_id, *id, height + 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -241,17 +227,17 @@ pub trait NodePass<T>: Pass {
|
||||
fn resolve_node_pass<T, P: NodePass<T> + ?Sized>(
|
||||
tree: &mut impl TreeView<T>,
|
||||
pass: &P,
|
||||
mut dirty: DirtyNodes,
|
||||
dirty_states: &DirtyNodeStates,
|
||||
dirty_states: &mut DirtyNodeStates,
|
||||
nodes_updated: &FxDashSet<NodeId>,
|
||||
ctx: &SendAnyMap,
|
||||
) {
|
||||
while let Some(id) = dirty.pop_back() {
|
||||
let pass_id = pass.pass_id();
|
||||
while let Some((height, id)) = dirty_states.pop_back(pass_id) {
|
||||
let node = tree.get_mut(id).unwrap();
|
||||
if pass.pass(node, ctx) {
|
||||
nodes_updated.insert(id);
|
||||
for dependant in pass.dependants() {
|
||||
dirty_states.insert(*dependant, id);
|
||||
dirty_states.insert(*dependant, id, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -280,90 +266,109 @@ impl<T> AnyPass<T> {
|
||||
}
|
||||
}
|
||||
|
||||
fn mask(&self) -> MemberMask {
|
||||
match self {
|
||||
Self::Upward(pass) => pass.mask(),
|
||||
Self::Downward(pass) => pass.mask(),
|
||||
Self::Node(pass) => pass.mask(),
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve(
|
||||
&self,
|
||||
tree: &mut impl TreeView<T>,
|
||||
dirty: DirtyNodes,
|
||||
dirty_states: &DirtyNodeStates,
|
||||
dirty_states: &mut DirtyNodeStates,
|
||||
nodes_updated: &FxDashSet<NodeId>,
|
||||
ctx: &SendAnyMap,
|
||||
) {
|
||||
match self {
|
||||
Self::Downward(pass) => {
|
||||
resolve_downward_pass(tree, *pass, dirty, dirty_states, nodes_updated, ctx)
|
||||
resolve_downward_pass(tree, *pass, dirty_states, nodes_updated, ctx)
|
||||
}
|
||||
Self::Upward(pass) => {
|
||||
resolve_upward_pass(tree, *pass, dirty, dirty_states, nodes_updated, ctx)
|
||||
}
|
||||
Self::Node(pass) => {
|
||||
resolve_node_pass(tree, *pass, dirty, dirty_states, nodes_updated, ctx)
|
||||
resolve_upward_pass(tree, *pass, dirty_states, nodes_updated, ctx)
|
||||
}
|
||||
Self::Node(pass) => resolve_node_pass(tree, *pass, dirty_states, nodes_updated, ctx),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
struct RawPointer<T>(*mut T);
|
||||
unsafe impl<T> Send for RawPointer<T> {}
|
||||
unsafe impl<T> Sync for RawPointer<T> {}
|
||||
pub fn resolve_passes<T, Tr: TreeView<T> + Sync + Send>(
|
||||
tree: &mut Tr,
|
||||
dirty_nodes: DirtyNodeStates,
|
||||
passes: Vec<&AnyPass<T>>,
|
||||
ctx: SendAnyMap,
|
||||
) -> FxDashSet<NodeId> {
|
||||
resolve_passes_single_threaded(tree, dirty_nodes, passes, ctx)
|
||||
// TODO: multithreadeding has some safety issues currently that need to be resolved before it can be used
|
||||
// let dirty_states = Arc::new(dirty_nodes);
|
||||
// let mut resolved_passes: FxHashSet<PassId> = FxHashSet::default();
|
||||
// let mut resolving = Vec::new();
|
||||
// let nodes_updated = Arc::new(FxDashSet::default());
|
||||
// let ctx = Arc::new(ctx);
|
||||
// while !passes.is_empty() {
|
||||
// let mut currently_borrowed = MemberMask::default();
|
||||
// std::thread::scope(|s| {
|
||||
// let mut i = 0;
|
||||
// while i < passes.len() {
|
||||
// let pass = &passes[i];
|
||||
// let pass_id = pass.pass_id();
|
||||
// let pass_mask = pass.mask();
|
||||
// if pass
|
||||
// .dependancies()
|
||||
// .iter()
|
||||
// .all(|d| resolved_passes.contains(d) || *d == pass_id)
|
||||
// && !pass_mask.overlaps(currently_borrowed)
|
||||
// {
|
||||
// let pass = passes.remove(i);
|
||||
// resolving.push(pass_id);
|
||||
// currently_borrowed |= pass_mask;
|
||||
// let dirty_states = dirty_states.clone();
|
||||
// let nodes_updated = nodes_updated.clone();
|
||||
// let ctx = ctx.clone();
|
||||
// let mut dirty = DirtyNodes::default();
|
||||
// // dirty_states.all_dirty(pass_id, &mut dirty, tree);
|
||||
// // this is safe because the member_mask acts as a per-member mutex and we have verified that the pass does not overlap with any other pass
|
||||
// let tree_mut_unbounded = unsafe { &mut *(tree as *mut Tr) };
|
||||
// s.spawn(move || {
|
||||
// pass.resolve(
|
||||
// tree_mut_unbounded,
|
||||
// dirty,
|
||||
// &dirty_states,
|
||||
// &nodes_updated,
|
||||
// &ctx,
|
||||
// );
|
||||
// });
|
||||
// } else {
|
||||
// i += 1;
|
||||
// }
|
||||
// }
|
||||
// // all passes are resolved at the end of the scope
|
||||
// });
|
||||
// resolved_passes.extend(resolving.iter().copied());
|
||||
// resolving.clear()
|
||||
// }
|
||||
// std::sync::Arc::try_unwrap(nodes_updated).unwrap()
|
||||
}
|
||||
|
||||
pub fn resolve_passes<T, Tr: TreeView<T>>(
|
||||
pub fn resolve_passes_single_threaded<T, Tr: TreeView<T>>(
|
||||
tree: &mut Tr,
|
||||
dirty_nodes: DirtyNodeStates,
|
||||
mut passes: Vec<&AnyPass<T>>,
|
||||
ctx: SendAnyMap,
|
||||
) -> FxDashSet<NodeId> {
|
||||
let dirty_states = Arc::new(dirty_nodes);
|
||||
let mut dirty_states = dirty_nodes;
|
||||
let mut resolved_passes: FxHashSet<PassId> = FxHashSet::default();
|
||||
let mut resolving = Vec::new();
|
||||
let nodes_updated = Arc::new(FxDashSet::default());
|
||||
let ctx = Arc::new(ctx);
|
||||
while !passes.is_empty() {
|
||||
let mut currently_borrowed = MemberMask::default();
|
||||
std::thread::scope(|s| {
|
||||
let mut i = 0;
|
||||
while i < passes.len() {
|
||||
let pass = &passes[i];
|
||||
let pass_id = pass.pass_id();
|
||||
let pass_mask = pass.mask();
|
||||
if pass
|
||||
.dependancies()
|
||||
.iter()
|
||||
.all(|d| resolved_passes.contains(d) || *d == pass_id)
|
||||
&& !pass_mask.overlaps(currently_borrowed)
|
||||
{
|
||||
let pass = passes.remove(i);
|
||||
resolving.push(pass_id);
|
||||
currently_borrowed |= pass_mask;
|
||||
let tree_mut = tree as *mut _;
|
||||
let raw_ptr = RawPointer(tree_mut);
|
||||
let dirty_states = dirty_states.clone();
|
||||
let nodes_updated = nodes_updated.clone();
|
||||
let ctx = ctx.clone();
|
||||
s.spawn(move || unsafe {
|
||||
// let tree_mut: &mut Tr = &mut *raw_ptr.0;
|
||||
let raw = raw_ptr;
|
||||
// this is safe because the member_mask acts as a per-member mutex and we have verified that the pass does not overlap with any other pass
|
||||
let tree_mut: &mut Tr = &mut *raw.0;
|
||||
let mut dirty = DirtyNodes::default();
|
||||
dirty_states.all_dirty(pass_id, &mut dirty, tree_mut);
|
||||
pass.resolve(tree_mut, dirty, &dirty_states, &nodes_updated, &ctx);
|
||||
});
|
||||
} else {
|
||||
i += 1;
|
||||
}
|
||||
for (i, pass) in passes.iter().enumerate() {
|
||||
let pass_id = pass.pass_id();
|
||||
if pass
|
||||
.dependancies()
|
||||
.iter()
|
||||
.all(|d| resolved_passes.contains(d) || *d == pass_id)
|
||||
{
|
||||
let pass = passes.remove(i);
|
||||
let nodes_updated = nodes_updated.clone();
|
||||
let ctx = ctx.clone();
|
||||
pass.resolve(tree, &mut dirty_states, &nodes_updated, &ctx);
|
||||
resolved_passes.insert(pass_id);
|
||||
break;
|
||||
}
|
||||
// all passes are resolved at the end of the scope
|
||||
});
|
||||
resolved_passes.extend(resolving.iter().copied());
|
||||
resolving.clear()
|
||||
}
|
||||
}
|
||||
std::sync::Arc::try_unwrap(nodes_updated).unwrap()
|
||||
}
|
||||
@@ -401,8 +406,8 @@ fn node_pass() {
|
||||
|
||||
let add_pass = AnyPass::Node(&AddPass);
|
||||
let passes = vec![&add_pass];
|
||||
let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
|
||||
dirty_nodes.insert(PassId(0), tree.root());
|
||||
let mut dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
|
||||
dirty_nodes.insert(PassId(0), tree.root(), 0);
|
||||
resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new());
|
||||
|
||||
assert_eq!(tree.get(tree.root()).unwrap(), &1);
|
||||
@@ -468,8 +473,8 @@ fn dependant_node_pass() {
|
||||
let add_pass = AnyPass::Node(&AddPass);
|
||||
let subtract_pass = AnyPass::Node(&SubtractPass);
|
||||
let passes = vec![&add_pass, &subtract_pass];
|
||||
let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
|
||||
dirty_nodes.insert(PassId(1), tree.root());
|
||||
let mut dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
|
||||
dirty_nodes.insert(PassId(1), tree.root(), 0);
|
||||
resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new());
|
||||
|
||||
assert_eq!(*tree.get(tree.root()).unwrap(), 0);
|
||||
@@ -535,9 +540,9 @@ fn independant_node_pass() {
|
||||
let add_pass1 = AnyPass::Node(&AddPass1);
|
||||
let add_pass2 = AnyPass::Node(&AddPass2);
|
||||
let passes = vec![&add_pass1, &add_pass2];
|
||||
let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
|
||||
dirty_nodes.insert(PassId(0), tree.root());
|
||||
dirty_nodes.insert(PassId(1), tree.root());
|
||||
let mut dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
|
||||
dirty_nodes.insert(PassId(0), tree.root(), 0);
|
||||
dirty_nodes.insert(PassId(1), tree.root(), 0);
|
||||
resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new());
|
||||
|
||||
assert_eq!(tree.get(tree.root()).unwrap(), &(1, 1));
|
||||
@@ -590,8 +595,8 @@ fn down_pass() {
|
||||
|
||||
let add_pass = AnyPass::Downward(&AddPass);
|
||||
let passes = vec![&add_pass];
|
||||
let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
|
||||
dirty_nodes.insert(PassId(0), tree.root());
|
||||
let mut dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
|
||||
dirty_nodes.insert(PassId(0), tree.root(), 0);
|
||||
resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new());
|
||||
|
||||
assert_eq!(tree.get(tree.root()).unwrap(), &1);
|
||||
@@ -685,8 +690,8 @@ fn dependant_down_pass() {
|
||||
let add_pass = AnyPass::Downward(&AddPass);
|
||||
let subtract_pass = AnyPass::Downward(&SubtractPass);
|
||||
let passes = vec![&add_pass, &subtract_pass];
|
||||
let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
|
||||
dirty_nodes.insert(PassId(1), tree.root());
|
||||
let mut dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
|
||||
dirty_nodes.insert(PassId(1), tree.root(), 0);
|
||||
resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new());
|
||||
|
||||
// Tree before:
|
||||
@@ -775,9 +780,9 @@ fn up_pass() {
|
||||
|
||||
let add_pass = AnyPass::Upward(&AddPass);
|
||||
let passes = vec![&add_pass];
|
||||
let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
|
||||
dirty_nodes.insert(PassId(0), grandchild1);
|
||||
dirty_nodes.insert(PassId(0), grandchild2);
|
||||
let mut dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
|
||||
dirty_nodes.insert(PassId(0), grandchild1, tree.height(grandchild1).unwrap());
|
||||
dirty_nodes.insert(PassId(0), grandchild2, tree.height(grandchild2).unwrap());
|
||||
resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new());
|
||||
|
||||
assert_eq!(tree.get(tree.root()).unwrap(), &2);
|
||||
@@ -875,9 +880,9 @@ fn dependant_up_pass() {
|
||||
let add_pass = AnyPass::Upward(&AddPass);
|
||||
let subtract_pass = AnyPass::Upward(&SubtractPass);
|
||||
let passes = vec![&add_pass, &subtract_pass];
|
||||
let dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
|
||||
dirty_nodes.insert(PassId(1), grandchild1);
|
||||
dirty_nodes.insert(PassId(1), grandchild2);
|
||||
let mut dirty_nodes: DirtyNodeStates = DirtyNodeStates::default();
|
||||
dirty_nodes.insert(PassId(1), grandchild1, tree.height(grandchild1).unwrap());
|
||||
dirty_nodes.insert(PassId(1), grandchild2, tree.height(grandchild2).unwrap());
|
||||
resolve_passes(&mut tree, dirty_nodes, passes, SendAnyMap::new());
|
||||
|
||||
// Tree before:
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
use dioxus_core::{ElementId, Mutations, TemplateNode};
|
||||
use dioxus_core::{BorrowedAttributeValue, ElementId, Mutations, TemplateNode};
|
||||
use rustc_hash::{FxHashMap, FxHashSet};
|
||||
use std::fmt::Debug;
|
||||
use std::ops::{Deref, DerefMut, Index, IndexMut};
|
||||
|
||||
use crate::node::{Node, NodeType, OwnedAttributeDiscription, OwnedAttributeValue};
|
||||
use crate::node::{FromAnyValue, Node, NodeType, OwnedAttributeDiscription, OwnedAttributeValue};
|
||||
use crate::node_ref::{AttributeMask, NodeMask};
|
||||
use crate::passes::DirtyNodeStates;
|
||||
use crate::state::State;
|
||||
@@ -25,9 +25,12 @@ fn mark_dirty(
|
||||
/// A Dom that can sync with the VirtualDom mutations intended for use in lazy renderers.
|
||||
/// The render state passes from parent to children and or accumulates state from children to parents.
|
||||
/// To get started implement [crate::state::ParentDepState], [crate::state::NodeDepState], or [crate::state::ChildDepState] and call [RealDom::apply_mutations] to update the dom and [RealDom::update_state] to update the state of the nodes.
|
||||
///
|
||||
/// # Custom values
|
||||
/// To allow custom values to be passed into attributes implement FromAnyValue on a type that can represent your custom value and specify the V generic to be that type. If you have many different custom values, it can be useful to use a enum type to represent the varients.
|
||||
#[derive(Debug)]
|
||||
pub struct RealDom<S: State> {
|
||||
pub tree: Tree<Node<S>>,
|
||||
pub struct RealDom<S: State<V>, V: FromAnyValue + 'static = ()> {
|
||||
pub tree: Tree<Node<S, V>>,
|
||||
/// a map from element id to real node id
|
||||
node_id_mapping: Vec<Option<RealNodeId>>,
|
||||
nodes_listening: FxHashMap<String, FxHashSet<RealNodeId>>,
|
||||
@@ -36,14 +39,14 @@ pub struct RealDom<S: State> {
|
||||
root_initialized: bool,
|
||||
}
|
||||
|
||||
impl<S: State> Default for RealDom<S> {
|
||||
impl<S: State<V>, V: FromAnyValue> Default for RealDom<S, V> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: State> RealDom<S> {
|
||||
pub fn new() -> RealDom<S> {
|
||||
impl<S: State<V>, V: FromAnyValue> RealDom<S, V> {
|
||||
pub fn new() -> RealDom<S, V> {
|
||||
let mut root = Node::new(NodeType::Element {
|
||||
tag: "Root".to_string(),
|
||||
namespace: Some("Root".to_string()),
|
||||
@@ -76,10 +79,6 @@ impl<S: State> RealDom<S> {
|
||||
if self.node_id_mapping.len() <= element_id.0 {
|
||||
self.node_id_mapping.resize(element_id.0 + 1, None);
|
||||
}
|
||||
if let Some(Some(old_id)) = self.node_id_mapping.get(element_id.0) {
|
||||
// free the memory associated with the old node
|
||||
self.tree.remove(*old_id);
|
||||
}
|
||||
self.node_id_mapping[element_id.0] = Some(node_id);
|
||||
}
|
||||
|
||||
@@ -91,7 +90,7 @@ impl<S: State> RealDom<S> {
|
||||
current
|
||||
}
|
||||
|
||||
fn create_node(&mut self, node: Node<S>) -> RealNodeId {
|
||||
fn create_node(&mut self, node: Node<S, V>) -> RealNodeId {
|
||||
let node_id = self.tree.create_node(node);
|
||||
let node = self.tree.get_mut(node_id).unwrap();
|
||||
node.node_data.node_id = node_id;
|
||||
@@ -228,7 +227,7 @@ impl<S: State> RealDom<S> {
|
||||
self.tree.insert_before(old_node_id, new);
|
||||
mark_dirty(new, NodeMask::ALL, &mut nodes_updated);
|
||||
}
|
||||
self.tree.remove(old_node_id);
|
||||
self.remove(old_node_id, &mut nodes_updated);
|
||||
}
|
||||
ReplacePlaceholder { path, m } => {
|
||||
let new_nodes = self.stack.split_off(self.stack.len() - m);
|
||||
@@ -237,7 +236,7 @@ impl<S: State> RealDom<S> {
|
||||
self.tree.insert_before(old_node_id, new);
|
||||
mark_dirty(new, NodeMask::ALL, &mut nodes_updated);
|
||||
}
|
||||
self.tree.remove(old_node_id);
|
||||
self.remove(old_node_id, &mut nodes_updated);
|
||||
}
|
||||
InsertAfter { id, m } => {
|
||||
let new_nodes = self.stack.split_off(self.stack.len() - m);
|
||||
@@ -264,38 +263,32 @@ impl<S: State> RealDom<S> {
|
||||
let node_id = self.element_to_node_id(id);
|
||||
let node = self.tree.get_mut(node_id).unwrap();
|
||||
if let NodeType::Element { attributes, .. } = &mut node.node_data.node_type {
|
||||
attributes.insert(
|
||||
OwnedAttributeDiscription {
|
||||
if let BorrowedAttributeValue::None = &value {
|
||||
attributes.remove(&OwnedAttributeDiscription {
|
||||
name: name.to_string(),
|
||||
namespace: ns.map(|s| s.to_string()),
|
||||
volatile: false,
|
||||
},
|
||||
crate::node::OwnedAttributeValue::Text(value.to_string()),
|
||||
);
|
||||
mark_dirty(
|
||||
node_id,
|
||||
NodeMask::new_with_attrs(AttributeMask::single(name)),
|
||||
&mut nodes_updated,
|
||||
);
|
||||
}
|
||||
}
|
||||
SetBoolAttribute { name, value, id } => {
|
||||
let node_id = self.element_to_node_id(id);
|
||||
let node = self.tree.get_mut(node_id).unwrap();
|
||||
if let NodeType::Element { attributes, .. } = &mut node.node_data.node_type {
|
||||
attributes.insert(
|
||||
OwnedAttributeDiscription {
|
||||
name: name.to_string(),
|
||||
namespace: None,
|
||||
volatile: false,
|
||||
},
|
||||
crate::node::OwnedAttributeValue::Bool(value),
|
||||
);
|
||||
mark_dirty(
|
||||
node_id,
|
||||
NodeMask::new_with_attrs(AttributeMask::single(name)),
|
||||
&mut nodes_updated,
|
||||
);
|
||||
});
|
||||
mark_dirty(
|
||||
node_id,
|
||||
NodeMask::new_with_attrs(AttributeMask::single(name)),
|
||||
&mut nodes_updated,
|
||||
);
|
||||
} else {
|
||||
attributes.insert(
|
||||
OwnedAttributeDiscription {
|
||||
name: name.to_string(),
|
||||
namespace: ns.map(|s| s.to_string()),
|
||||
volatile: false,
|
||||
},
|
||||
OwnedAttributeValue::from(value),
|
||||
);
|
||||
mark_dirty(
|
||||
node_id,
|
||||
NodeMask::new_with_attrs(AttributeMask::single(name)),
|
||||
&mut nodes_updated,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
SetText { value, id } => {
|
||||
@@ -333,7 +326,7 @@ impl<S: State> RealDom<S> {
|
||||
}
|
||||
Remove { id } => {
|
||||
let node_id = self.element_to_node_id(id);
|
||||
self.tree.remove(node_id);
|
||||
self.remove(node_id, &mut nodes_updated);
|
||||
}
|
||||
PushRoot { id } => {
|
||||
let node_id = self.element_to_node_id(id);
|
||||
@@ -342,13 +335,13 @@ impl<S: State> RealDom<S> {
|
||||
}
|
||||
}
|
||||
|
||||
let dirty_nodes = DirtyNodeStates::default();
|
||||
let mut dirty_nodes = DirtyNodeStates::default();
|
||||
for (&n, mask) in &nodes_updated {
|
||||
// remove any nodes that were created and then removed in the same mutations from the dirty nodes list
|
||||
if self.tree.contains(n) {
|
||||
if let Some(height) = self.tree.height(n) {
|
||||
for (m, p) in S::MASKS.iter().zip(S::PASSES.iter()) {
|
||||
if mask.overlaps(m) {
|
||||
dirty_nodes.insert(p.pass_id(), n);
|
||||
dirty_nodes.insert(p.pass_id(), n, height);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -358,17 +351,17 @@ impl<S: State> RealDom<S> {
|
||||
}
|
||||
|
||||
/// Update the state of the dom, after appling some mutations. This will keep the nodes in the dom up to date with their VNode counterparts.
|
||||
pub fn update_state(
|
||||
pub fn update_state_single_threaded(
|
||||
&mut self,
|
||||
nodes_updated: DirtyNodeStates,
|
||||
ctx: SendAnyMap,
|
||||
) -> FxDashSet<RealNodeId> {
|
||||
S::update(nodes_updated, &mut self.tree, ctx)
|
||||
S::update_single_threaded(nodes_updated, &mut self.tree, ctx)
|
||||
}
|
||||
|
||||
/// Find all nodes that are listening for an event, sorted by there height in the dom progressing starting at the bottom and progressing up.
|
||||
/// This can be useful to avoid creating duplicate events.
|
||||
pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&Node<S>> {
|
||||
pub fn get_listening_sorted(&self, event: &'static str) -> Vec<&Node<S, V>> {
|
||||
if let Some(nodes) = self.nodes_listening.get(event) {
|
||||
let mut listening: Vec<_> = nodes.iter().map(|id| &self[*id]).collect();
|
||||
listening.sort_by(|n1, n2| {
|
||||
@@ -413,45 +406,77 @@ impl<S: State> RealDom<S> {
|
||||
}
|
||||
new_id
|
||||
}
|
||||
|
||||
fn remove(&mut self, node_id: NodeId, nodes_updated: &mut FxHashMap<RealNodeId, NodeMask>) {
|
||||
let node = self.tree.get(node_id).unwrap();
|
||||
if let NodeType::Element { listeners, .. } = &node.node_data.node_type {
|
||||
for name in listeners.iter() {
|
||||
self.nodes_listening.get_mut(name).unwrap().remove(&node_id);
|
||||
}
|
||||
}
|
||||
if let Some(children) = self.tree.children_ids(node_id) {
|
||||
let children = children.to_vec();
|
||||
for child in children {
|
||||
self.remove(child, nodes_updated);
|
||||
}
|
||||
}
|
||||
self.tree.remove(node_id);
|
||||
mark_dirty(node_id, NodeMask::ALL, nodes_updated);
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: State> Deref for RealDom<S> {
|
||||
type Target = Tree<Node<S>>;
|
||||
impl<S: State<V> + Sync, V: FromAnyValue> RealDom<S, V>
|
||||
where
|
||||
Tree<Node<S, V>>: Sync + Send,
|
||||
{
|
||||
/// Update the state of the dom, after appling some mutations. This will keep the nodes in the dom up to date with their VNode counterparts.
|
||||
/// This will resolve the state in parallel
|
||||
pub fn update_state(
|
||||
&mut self,
|
||||
nodes_updated: DirtyNodeStates,
|
||||
ctx: SendAnyMap,
|
||||
) -> FxDashSet<RealNodeId> {
|
||||
S::update(nodes_updated, &mut self.tree, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: State<V>, V: FromAnyValue> Deref for RealDom<S, V> {
|
||||
type Target = Tree<Node<S, V>>;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
&self.tree
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: State> DerefMut for RealDom<S> {
|
||||
impl<S: State<V>, V: FromAnyValue> DerefMut for RealDom<S, V> {
|
||||
fn deref_mut(&mut self) -> &mut Self::Target {
|
||||
&mut self.tree
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: State> Index<ElementId> for RealDom<S> {
|
||||
type Output = Node<S>;
|
||||
impl<S: State<V>, V: FromAnyValue> Index<ElementId> for RealDom<S, V> {
|
||||
type Output = Node<S, V>;
|
||||
|
||||
fn index(&self, id: ElementId) -> &Self::Output {
|
||||
self.tree.get(self.element_to_node_id(id)).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: State> Index<RealNodeId> for RealDom<S> {
|
||||
type Output = Node<S>;
|
||||
impl<S: State<V>, V: FromAnyValue> Index<RealNodeId> for RealDom<S, V> {
|
||||
type Output = Node<S, V>;
|
||||
|
||||
fn index(&self, idx: RealNodeId) -> &Self::Output {
|
||||
self.tree.get(idx).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: State> IndexMut<ElementId> for RealDom<S> {
|
||||
impl<S: State<V>, V: FromAnyValue> IndexMut<ElementId> for RealDom<S, V> {
|
||||
fn index_mut(&mut self, id: ElementId) -> &mut Self::Output {
|
||||
self.tree.get_mut(self.element_to_node_id(id)).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: State> IndexMut<RealNodeId> for RealDom<S> {
|
||||
impl<S: State<V>, V: FromAnyValue> IndexMut<RealNodeId> for RealDom<S, V> {
|
||||
fn index_mut(&mut self, idx: RealNodeId) -> &mut Self::Output {
|
||||
self.tree.get_mut(idx).unwrap()
|
||||
}
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
use std::cmp::Ordering;
|
||||
|
||||
use crate::node::Node;
|
||||
use crate::node::{FromAnyValue, Node};
|
||||
use crate::node_ref::{NodeMask, NodeView};
|
||||
use crate::passes::{resolve_passes, AnyPass, DirtyNodeStates};
|
||||
use crate::passes::{resolve_passes, resolve_passes_single_threaded, AnyPass, DirtyNodeStates};
|
||||
use crate::tree::TreeView;
|
||||
use crate::{FxDashSet, RealNodeId, SendAnyMap};
|
||||
|
||||
@@ -76,7 +76,7 @@ pub(crate) fn union_ordered_iter<'a>(
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait ChildDepState {
|
||||
pub trait ChildDepState<V: FromAnyValue = ()> {
|
||||
/// The context is passed to the [ChildDepState::reduce] when it is resolved.
|
||||
type Ctx;
|
||||
/// A state from each child node that this node depends on. Typically this is Self, but it could be any state that is within the state tree.
|
||||
@@ -87,7 +87,7 @@ pub trait ChildDepState {
|
||||
/// Resolve the state current node's state from the state of the children, the state of the node, and some external context.
|
||||
fn reduce<'a>(
|
||||
&mut self,
|
||||
node: NodeView,
|
||||
node: NodeView<'a, V>,
|
||||
children: impl Iterator<Item = <Self::DepState as ElementBorrowable>::ElementBorrowed<'a>>,
|
||||
ctx: &Self::Ctx,
|
||||
) -> bool
|
||||
@@ -138,7 +138,7 @@ pub trait ChildDepState {
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait ParentDepState {
|
||||
pub trait ParentDepState<V: FromAnyValue = ()> {
|
||||
/// The context is passed to the [ParentDepState::reduce] when it is resolved.
|
||||
type Ctx;
|
||||
/// A state from from the parent node that this node depends on. Typically this is Self, but it could be any state that is within the state tree.
|
||||
@@ -149,7 +149,7 @@ pub trait ParentDepState {
|
||||
/// Resolve the state current node's state from the state of the parent node, the state of the node, and some external context.
|
||||
fn reduce<'a>(
|
||||
&mut self,
|
||||
node: NodeView,
|
||||
node: NodeView<'a, V>,
|
||||
parent: Option<<Self::DepState as ElementBorrowable>::ElementBorrowed<'a>>,
|
||||
ctx: &Self::Ctx,
|
||||
) -> bool;
|
||||
@@ -193,7 +193,7 @@ pub trait ParentDepState {
|
||||
/// }
|
||||
/// }
|
||||
/// ```
|
||||
pub trait NodeDepState {
|
||||
pub trait NodeDepState<V: FromAnyValue = ()> {
|
||||
/// Depstate must be a tuple containing any number of borrowed elements that are either [ChildDepState], [ParentDepState] or [NodeDepState].
|
||||
type DepState: ElementBorrowable;
|
||||
/// The state passed to [NodeDepState::reduce] when it is resolved.
|
||||
@@ -203,21 +203,21 @@ pub trait NodeDepState {
|
||||
/// Resolve the state current node's state from the state of the sibling states, the state of the node, and some external context.
|
||||
fn reduce<'a>(
|
||||
&mut self,
|
||||
node: NodeView,
|
||||
node: NodeView<'a, V>,
|
||||
node_state: <Self::DepState as ElementBorrowable>::ElementBorrowed<'a>,
|
||||
ctx: &Self::Ctx,
|
||||
) -> bool;
|
||||
}
|
||||
|
||||
/// Do not implement this trait. It is only meant to be derived and used through [crate::real_dom::RealDom].
|
||||
pub trait State: Default + Clone + 'static {
|
||||
pub trait State<V: FromAnyValue + 'static>: Default + Clone + 'static {
|
||||
#[doc(hidden)]
|
||||
const PASSES: &'static [AnyPass<Node<Self>>];
|
||||
const PASSES: &'static [AnyPass<Node<Self, V>>];
|
||||
#[doc(hidden)]
|
||||
const MASKS: &'static [NodeMask];
|
||||
|
||||
#[doc(hidden)]
|
||||
fn update<T: TreeView<Node<Self>>>(
|
||||
fn update<T: TreeView<Node<Self, V>> + Sync + Send>(
|
||||
dirty: DirtyNodeStates,
|
||||
tree: &mut T,
|
||||
ctx: SendAnyMap,
|
||||
@@ -225,12 +225,22 @@ pub trait State: Default + Clone + 'static {
|
||||
let passes = Self::PASSES.iter().collect();
|
||||
resolve_passes(tree, dirty, passes, ctx)
|
||||
}
|
||||
|
||||
#[doc(hidden)]
|
||||
fn update_single_threaded<T: TreeView<Node<Self, V>>>(
|
||||
dirty: DirtyNodeStates,
|
||||
tree: &mut T,
|
||||
ctx: SendAnyMap,
|
||||
) -> FxDashSet<RealNodeId> {
|
||||
let passes = Self::PASSES.iter().collect();
|
||||
resolve_passes_single_threaded(tree, dirty, passes, ctx)
|
||||
}
|
||||
}
|
||||
|
||||
impl ChildDepState for () {
|
||||
type Ctx = ();
|
||||
type DepState = ();
|
||||
fn reduce<'a>(&mut self, _: NodeView, _: impl Iterator<Item = ()>, _: &Self::Ctx) -> bool
|
||||
fn reduce<'a>(&mut self, _: NodeView<'a>, _: impl Iterator<Item = ()>, _: &Self::Ctx) -> bool
|
||||
where
|
||||
Self::DepState: 'a,
|
||||
{
|
||||
@@ -241,7 +251,7 @@ impl ChildDepState for () {
|
||||
impl ParentDepState for () {
|
||||
type Ctx = ();
|
||||
type DepState = ();
|
||||
fn reduce<'a>(&mut self, _: NodeView, _: Option<()>, _: &Self::Ctx) -> bool {
|
||||
fn reduce<'a>(&mut self, _: NodeView<'a>, _: Option<()>, _: &Self::Ctx) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
@@ -249,7 +259,7 @@ impl ParentDepState for () {
|
||||
impl NodeDepState for () {
|
||||
type DepState = ();
|
||||
type Ctx = ();
|
||||
fn reduce(&mut self, _: NodeView, _sibling: (), _: &Self::Ctx) -> bool {
|
||||
fn reduce<'a>(&mut self, _: NodeView<'a>, _sibling: (), _: &Self::Ctx) -> bool {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
+181
-371
@@ -1,11 +1,5 @@
|
||||
use core::panic;
|
||||
use parking_lot::lock_api::RawMutex as _;
|
||||
use parking_lot::{RawMutex, RwLock};
|
||||
use slab::Slab;
|
||||
use std::cell::UnsafeCell;
|
||||
use std::collections::VecDeque;
|
||||
use std::marker::PhantomData;
|
||||
use std::sync::Arc;
|
||||
|
||||
#[derive(Hash, PartialEq, Eq, Clone, Copy, Debug, PartialOrd, Ord)]
|
||||
pub struct NodeId(pub usize);
|
||||
@@ -93,17 +87,7 @@ pub trait TreeView<T>: Sized {
|
||||
|
||||
fn children_mut(&mut self, id: NodeId) -> Option<Self::IteratorMut<'_>>;
|
||||
|
||||
fn parent_child_mut(&mut self, id: NodeId) -> Option<(&mut T, Self::IteratorMut<'_>)> {
|
||||
let mut_ptr: *mut Self = self;
|
||||
unsafe {
|
||||
// Safety: No node has itself as a child.
|
||||
(*mut_ptr).get_mut(id).and_then(|parent| {
|
||||
(*mut_ptr)
|
||||
.children_mut(id)
|
||||
.map(|children| (parent, children))
|
||||
})
|
||||
}
|
||||
}
|
||||
fn parent_child_mut(&mut self, id: NodeId) -> Option<(&mut T, Self::IteratorMut<'_>)>;
|
||||
|
||||
fn children_ids(&self, id: NodeId) -> Option<&[NodeId]>;
|
||||
|
||||
@@ -111,28 +95,12 @@ pub trait TreeView<T>: Sized {
|
||||
|
||||
fn parent_mut(&mut self, id: NodeId) -> Option<&mut T>;
|
||||
|
||||
fn node_parent_mut(&mut self, id: NodeId) -> Option<(&mut T, Option<&mut T>)> {
|
||||
let mut_ptr: *mut Self = self;
|
||||
unsafe {
|
||||
// Safety: No node has itself as a parent.
|
||||
(*mut_ptr)
|
||||
.get_mut(id)
|
||||
.map(|node| (node, (*mut_ptr).parent_mut(id)))
|
||||
}
|
||||
}
|
||||
fn node_parent_mut(&mut self, id: NodeId) -> Option<(&mut T, Option<&mut T>)>;
|
||||
|
||||
fn parent_id(&self, id: NodeId) -> Option<NodeId>;
|
||||
|
||||
fn height(&self, id: NodeId) -> Option<u16>;
|
||||
|
||||
fn map<T2, F: Fn(&T) -> &T2, FMut: Fn(&mut T) -> &mut T2>(
|
||||
&mut self,
|
||||
map: F,
|
||||
map_mut: FMut,
|
||||
) -> TreeMap<T, T2, Self, F, FMut> {
|
||||
TreeMap::new(self, map, map_mut)
|
||||
}
|
||||
|
||||
fn size(&self) -> usize;
|
||||
|
||||
fn traverse_depth_first(&self, mut f: impl FnMut(&T)) {
|
||||
@@ -208,58 +176,39 @@ pub trait TreeLike<T>: TreeView<T> {
|
||||
fn insert_after(&mut self, id: NodeId, new: NodeId);
|
||||
}
|
||||
|
||||
pub struct ChildNodeIterator<'a, T, Tr: TreeView<T>> {
|
||||
tree: &'a Tr,
|
||||
children_ids: &'a [NodeId],
|
||||
pub struct ChildNodeIterator<'a, T> {
|
||||
nodes: &'a Slab<Node<T>>,
|
||||
children_ids: Vec<NodeId>,
|
||||
index: usize,
|
||||
node_type: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'a, T: 'a, Tr: TreeView<T>> Iterator for ChildNodeIterator<'a, T, Tr> {
|
||||
impl<'a, T: 'a> Iterator for ChildNodeIterator<'a, T> {
|
||||
type Item = &'a T;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
self.children_ids.get(self.index).map(|id| {
|
||||
self.index += 1;
|
||||
self.tree.get_unchecked(*id)
|
||||
&self.nodes.get(id.0).unwrap().value
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
pub struct ChildNodeIteratorMut<'a, T, Tr: TreeView<T> + 'a> {
|
||||
tree: *mut Tr,
|
||||
children_ids: &'a [NodeId],
|
||||
index: usize,
|
||||
node_type: PhantomData<T>,
|
||||
pub struct ChildNodeIteratorMut<'a, T> {
|
||||
nodes: Vec<&'a mut Node<T>>,
|
||||
}
|
||||
|
||||
unsafe impl<'a, T, Tr: TreeView<T> + 'a> Sync for ChildNodeIteratorMut<'a, T, Tr> {}
|
||||
|
||||
impl<'a, T, Tr: TreeView<T>> ChildNodeIteratorMut<'a, T, Tr> {
|
||||
fn tree_mut(&mut self) -> &'a mut Tr {
|
||||
unsafe { &mut *self.tree }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'a, Tr: TreeView<T>> Iterator for ChildNodeIteratorMut<'a, T, Tr> {
|
||||
impl<'a, T: 'a> Iterator for ChildNodeIteratorMut<'a, T> {
|
||||
type Item = &'a mut T;
|
||||
|
||||
fn next(&mut self) -> Option<Self::Item> {
|
||||
let owned = self.children_ids.get(self.index).copied();
|
||||
match owned {
|
||||
Some(id) => {
|
||||
self.index += 1;
|
||||
|
||||
Some(self.tree_mut().get_mut_unchecked(id))
|
||||
}
|
||||
None => None,
|
||||
}
|
||||
self.nodes.pop().map(|node| &mut node.value)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TreeView<T> for Tree<T> {
|
||||
type Iterator<'a> = ChildNodeIterator<'a, T, Tree<T>> where T: 'a;
|
||||
type IteratorMut<'a> = ChildNodeIteratorMut<'a, T, Tree<T>> where T: 'a;
|
||||
type Iterator<'a> = ChildNodeIterator<'a, T> where T: 'a;
|
||||
type IteratorMut<'a> = ChildNodeIteratorMut<'a, T> where T: 'a;
|
||||
|
||||
fn root(&self) -> NodeId {
|
||||
self.root
|
||||
@@ -275,24 +224,26 @@ impl<T> TreeView<T> for Tree<T> {
|
||||
|
||||
fn children(&self, id: NodeId) -> Option<Self::Iterator<'_>> {
|
||||
self.children_ids(id).map(|children_ids| ChildNodeIterator {
|
||||
tree: self,
|
||||
children_ids,
|
||||
nodes: &self.nodes,
|
||||
children_ids: children_ids.to_vec(),
|
||||
index: 0,
|
||||
node_type: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
fn children_mut(&mut self, id: NodeId) -> Option<Self::IteratorMut<'_>> {
|
||||
let raw_ptr = self as *mut Self;
|
||||
unsafe {
|
||||
// Safety: No node will appear as a child twice
|
||||
self.children_ids(id)
|
||||
.map(|children_ids| ChildNodeIteratorMut {
|
||||
tree: &mut *raw_ptr,
|
||||
children_ids,
|
||||
index: 0,
|
||||
node_type: PhantomData,
|
||||
})
|
||||
// Safety: No node has itself as a parent.
|
||||
if let Some(children_ids) = self.children_ids(id) {
|
||||
let children_ids = children_ids.to_vec();
|
||||
Some(ChildNodeIteratorMut {
|
||||
nodes: unsafe {
|
||||
self.nodes
|
||||
.get_many_mut_unchecked(children_ids.into_iter().rev().map(|id| id.0))
|
||||
.unwrap()
|
||||
},
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -340,6 +291,39 @@ impl<T> TreeView<T> for Tree<T> {
|
||||
fn size(&self) -> usize {
|
||||
self.nodes.len()
|
||||
}
|
||||
|
||||
fn node_parent_mut(&mut self, id: NodeId) -> Option<(&mut T, Option<&mut T>)> {
|
||||
if let Some(parent_id) = self.parent_id(id) {
|
||||
self.nodes
|
||||
.get2_mut(id.0, parent_id.0)
|
||||
.map(|(node, parent)| (&mut node.value, Some(&mut parent.value)))
|
||||
} else {
|
||||
self.nodes.get_mut(id.0).map(|node| (&mut node.value, None))
|
||||
}
|
||||
}
|
||||
|
||||
fn parent_child_mut(&mut self, id: NodeId) -> Option<(&mut T, Self::IteratorMut<'_>)> {
|
||||
// Safety: No node will appear as a child twice
|
||||
if let Some(children_ids) = self.children_ids(id) {
|
||||
debug_assert!(!children_ids.iter().any(|child_id| *child_id == id));
|
||||
let mut borrowed = unsafe {
|
||||
let as_vec = children_ids.to_vec();
|
||||
self.nodes
|
||||
.get_many_mut_unchecked(
|
||||
as_vec
|
||||
.into_iter()
|
||||
.rev()
|
||||
.map(|id| id.0)
|
||||
.chain(std::iter::once(id.0)),
|
||||
)
|
||||
.unwrap()
|
||||
};
|
||||
let node = &mut borrowed.pop().unwrap().value;
|
||||
Some((node, ChildNodeIteratorMut { nodes: borrowed }))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TreeLike<T> for Tree<T> {
|
||||
@@ -435,248 +419,6 @@ impl<T> TreeLike<T> for Tree<T> {
|
||||
self.set_height(new, height);
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TreeMap<'a, T1, T2, Tr, F, FMut>
|
||||
where
|
||||
Tr: TreeView<T1>,
|
||||
F: Fn(&T1) -> &T2,
|
||||
FMut: Fn(&mut T1) -> &mut T2,
|
||||
{
|
||||
tree: &'a mut Tr,
|
||||
map: F,
|
||||
map_mut: FMut,
|
||||
in_node_type: PhantomData<T1>,
|
||||
out_node_type: PhantomData<T2>,
|
||||
}
|
||||
|
||||
impl<'a, T1, T2, Tr, F, FMut> TreeMap<'a, T1, T2, Tr, F, FMut>
|
||||
where
|
||||
Tr: TreeView<T1>,
|
||||
F: Fn(&T1) -> &T2,
|
||||
FMut: Fn(&mut T1) -> &mut T2,
|
||||
{
|
||||
pub fn new(tree: &'a mut Tr, map: F, map_mut: FMut) -> Self {
|
||||
TreeMap {
|
||||
tree,
|
||||
map,
|
||||
map_mut,
|
||||
in_node_type: PhantomData,
|
||||
out_node_type: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T1, T2, Tr, F, FMut> TreeView<T2> for TreeMap<'a, T1, T2, Tr, F, FMut>
|
||||
where
|
||||
Tr: TreeView<T1>,
|
||||
F: Fn(&T1) -> &T2,
|
||||
FMut: Fn(&mut T1) -> &mut T2,
|
||||
{
|
||||
type Iterator<'b> = ChildNodeIterator<'b, T2, TreeMap<'a, T1, T2, Tr, F, FMut>>
|
||||
where
|
||||
T2: 'b,
|
||||
Self:'b;
|
||||
type IteratorMut<'b> = ChildNodeIteratorMut<'b, T2, TreeMap<'a, T1, T2, Tr, F, FMut>>
|
||||
where
|
||||
T2: 'b,
|
||||
Self:'b;
|
||||
|
||||
fn root(&self) -> NodeId {
|
||||
self.tree.root()
|
||||
}
|
||||
|
||||
fn get(&self, id: NodeId) -> Option<&T2> {
|
||||
self.tree.get(id).map(|node| (self.map)(node))
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, id: NodeId) -> Option<&mut T2> {
|
||||
self.tree.get_mut(id).map(|node| (self.map_mut)(node))
|
||||
}
|
||||
|
||||
fn children(&self, id: NodeId) -> Option<Self::Iterator<'_>> {
|
||||
self.children_ids(id).map(|children_ids| ChildNodeIterator {
|
||||
tree: self,
|
||||
children_ids,
|
||||
index: 0,
|
||||
node_type: PhantomData,
|
||||
})
|
||||
}
|
||||
|
||||
fn children_mut(&mut self, id: NodeId) -> Option<Self::IteratorMut<'_>> {
|
||||
let raw_ptr = self as *mut Self;
|
||||
unsafe {
|
||||
// Safety: No node can be a child twice.
|
||||
self.children_ids(id)
|
||||
.map(|children_ids| ChildNodeIteratorMut {
|
||||
tree: &mut *raw_ptr,
|
||||
children_ids,
|
||||
index: 0,
|
||||
node_type: PhantomData,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
fn children_ids(&self, id: NodeId) -> Option<&[NodeId]> {
|
||||
self.tree.children_ids(id)
|
||||
}
|
||||
|
||||
fn parent(&self, id: NodeId) -> Option<&T2> {
|
||||
self.tree.parent(id).map(|node| (self.map)(node))
|
||||
}
|
||||
|
||||
fn parent_mut(&mut self, id: NodeId) -> Option<&mut T2> {
|
||||
self.tree.parent_mut(id).map(|node| (self.map_mut)(node))
|
||||
}
|
||||
|
||||
fn parent_id(&self, id: NodeId) -> Option<NodeId> {
|
||||
self.tree.parent_id(id)
|
||||
}
|
||||
|
||||
fn height(&self, id: NodeId) -> Option<u16> {
|
||||
self.tree.height(id)
|
||||
}
|
||||
|
||||
fn get_unchecked(&self, id: NodeId) -> &T2 {
|
||||
(self.map)(self.tree.get_unchecked(id))
|
||||
}
|
||||
|
||||
fn get_mut_unchecked(&mut self, id: NodeId) -> &mut T2 {
|
||||
(self.map_mut)(self.tree.get_mut_unchecked(id))
|
||||
}
|
||||
|
||||
fn size(&self) -> usize {
|
||||
self.tree.size()
|
||||
}
|
||||
}
|
||||
|
||||
/// A view into a tree that can be shared between multiple threads. Nodes are locked invividually.
|
||||
pub struct SharedView<'a, T, Tr: TreeView<T>> {
|
||||
tree: Arc<UnsafeCell<&'a mut Tr>>,
|
||||
node_locks: Arc<RwLock<Vec<RawMutex>>>,
|
||||
node_type: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<'a, T, Tr: TreeView<T>> SharedView<'a, T, Tr> {
|
||||
/// Checks if a node is currently locked. Returns None if the node does not exist.
|
||||
pub fn check_lock(&self, id: NodeId) -> Option<bool> {
|
||||
let locks = self.node_locks.read();
|
||||
locks.get(id.0).map(|lock| lock.is_locked())
|
||||
}
|
||||
}
|
||||
|
||||
unsafe impl<'a, T, Tr: TreeView<T>> Send for SharedView<'a, T, Tr> {}
|
||||
unsafe impl<'a, T, Tr: TreeView<T>> Sync for SharedView<'a, T, Tr> {}
|
||||
impl<'a, T, Tr: TreeView<T>> Clone for SharedView<'a, T, Tr> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
tree: self.tree.clone(),
|
||||
node_locks: self.node_locks.clone(),
|
||||
node_type: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T, Tr: TreeView<T>> SharedView<'a, T, Tr> {
|
||||
pub fn new(tree: &'a mut Tr) -> Self {
|
||||
let tree = Arc::new(UnsafeCell::new(tree));
|
||||
let mut node_locks = Vec::new();
|
||||
for _ in 0..unsafe { (*tree.get()).size() } {
|
||||
node_locks.push(RawMutex::INIT);
|
||||
}
|
||||
Self {
|
||||
tree,
|
||||
node_locks: Arc::new(RwLock::new(node_locks)),
|
||||
node_type: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
fn lock_node(&self, node: NodeId) {
|
||||
let read = self.node_locks.read();
|
||||
let lock = read.get(node.0);
|
||||
match lock {
|
||||
Some(lock) => lock.lock(),
|
||||
None => {
|
||||
drop(read);
|
||||
let mut write = self.node_locks.write();
|
||||
write.resize_with(node.0 + 1, || RawMutex::INIT);
|
||||
unsafe { write.get_unchecked(node.0).lock() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn unlock_node(&self, node: NodeId) {
|
||||
let read = self.node_locks.read();
|
||||
let lock = read.get(node.0);
|
||||
match lock {
|
||||
Some(lock) => unsafe { lock.unlock() },
|
||||
None => {
|
||||
panic!("unlocking node that was not locked")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn with_node<R>(&self, node_id: NodeId, f: impl FnOnce(&'a mut Tr) -> R) -> R {
|
||||
self.lock_node(node_id);
|
||||
let tree = unsafe { &mut *self.tree.get() };
|
||||
let r = f(tree);
|
||||
self.unlock_node(node_id);
|
||||
r
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T, Tr: TreeView<T>> TreeView<T> for SharedView<'a, T, Tr> {
|
||||
type Iterator<'b> = Tr::Iterator<'b> where T: 'b, Self: 'b;
|
||||
|
||||
type IteratorMut<'b>=Tr::IteratorMut<'b>
|
||||
where
|
||||
T: 'b,
|
||||
Self: 'b;
|
||||
|
||||
fn root(&self) -> NodeId {
|
||||
unsafe { (*self.tree.get()).root() }
|
||||
}
|
||||
|
||||
fn get(&self, id: NodeId) -> Option<&T> {
|
||||
self.with_node(id, |t| t.get(id))
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, id: NodeId) -> Option<&mut T> {
|
||||
self.with_node(id, |t| t.get_mut(id))
|
||||
}
|
||||
|
||||
fn children(&self, id: NodeId) -> Option<Self::Iterator<'_>> {
|
||||
self.with_node(id, |t| t.children(id))
|
||||
}
|
||||
|
||||
fn children_mut(&mut self, id: NodeId) -> Option<Self::IteratorMut<'_>> {
|
||||
self.with_node(id, |t| t.children_mut(id))
|
||||
}
|
||||
|
||||
fn children_ids(&self, id: NodeId) -> Option<&[NodeId]> {
|
||||
self.with_node(id, |t| t.children_ids(id))
|
||||
}
|
||||
|
||||
fn parent(&self, id: NodeId) -> Option<&T> {
|
||||
self.with_node(id, |t| t.get(id))
|
||||
}
|
||||
|
||||
fn parent_mut(&mut self, id: NodeId) -> Option<&mut T> {
|
||||
self.with_node(id, |t| t.parent_mut(id))
|
||||
}
|
||||
|
||||
fn parent_id(&self, id: NodeId) -> Option<NodeId> {
|
||||
self.with_node(id, |t| t.parent_id(id))
|
||||
}
|
||||
|
||||
fn height(&self, id: NodeId) -> Option<u16> {
|
||||
unsafe { (*self.tree.get()).height(id) }
|
||||
}
|
||||
|
||||
fn size(&self) -> usize {
|
||||
unsafe { (*self.tree.get()).size() }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn creation() {
|
||||
let mut tree = Tree::new(1);
|
||||
@@ -785,59 +527,6 @@ fn deletion() {
|
||||
assert_eq!(tree.children_ids(parent).unwrap(), &[]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn shared_view() {
|
||||
use std::thread;
|
||||
let mut tree = Tree::new(1);
|
||||
let parent = tree.root();
|
||||
let child = tree.create_node(0);
|
||||
tree.add_child(parent, child);
|
||||
|
||||
let shared = SharedView::new(&mut tree);
|
||||
|
||||
thread::scope(|s| {
|
||||
let (mut shared1, mut shared2, mut shared3) =
|
||||
(shared.clone(), shared.clone(), shared.clone());
|
||||
s.spawn(move || {
|
||||
assert_eq!(*shared1.get_mut(parent).unwrap(), 1);
|
||||
assert_eq!(*shared1.get_mut(child).unwrap(), 0);
|
||||
});
|
||||
s.spawn(move || {
|
||||
assert_eq!(*shared2.get_mut(child).unwrap(), 0);
|
||||
assert_eq!(*shared2.get_mut(parent).unwrap(), 1);
|
||||
});
|
||||
s.spawn(move || {
|
||||
assert_eq!(*shared3.get_mut(parent).unwrap(), 1);
|
||||
assert_eq!(*shared3.get_mut(child).unwrap(), 0);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn map() {
|
||||
#[derive(Debug, PartialEq)]
|
||||
struct Value {
|
||||
value: i32,
|
||||
}
|
||||
impl Value {
|
||||
fn new(value: i32) -> Self {
|
||||
Self { value }
|
||||
}
|
||||
}
|
||||
let mut tree = Tree::new(Value::new(1));
|
||||
let parent = tree.root();
|
||||
let child = tree.create_node(Value::new(0));
|
||||
tree.add_child(parent, child);
|
||||
|
||||
let mut mapped = tree.map(|x| &x.value, |x| &mut x.value);
|
||||
|
||||
*mapped.get_mut(child).unwrap() = 1;
|
||||
*mapped.get_mut(parent).unwrap() = 2;
|
||||
|
||||
assert_eq!(*tree.get(parent).unwrap(), Value::new(2));
|
||||
assert_eq!(*tree.get(child).unwrap(), Value::new(1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn traverse_depth_first() {
|
||||
let mut tree = Tree::new(0);
|
||||
@@ -857,3 +546,124 @@ fn traverse_depth_first() {
|
||||
node_count += 1;
|
||||
});
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_node_children_mut() {
|
||||
let mut tree = Tree::new(0);
|
||||
let parent = tree.root();
|
||||
let child1 = tree.create_node(1);
|
||||
tree.add_child(parent, child1);
|
||||
let child2 = tree.create_node(2);
|
||||
tree.add_child(parent, child2);
|
||||
let child3 = tree.create_node(3);
|
||||
tree.add_child(parent, child3);
|
||||
|
||||
let (parent, children) = tree.parent_child_mut(parent).unwrap();
|
||||
for (i, child) in children.enumerate() {
|
||||
assert_eq!(*child, i + 1);
|
||||
}
|
||||
println!("Parent: {:#?}", parent);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn get_many_mut_unchecked() {
|
||||
let mut slab = Slab::new();
|
||||
let parent = slab.insert(0);
|
||||
let child = slab.insert(1);
|
||||
let grandchild = slab.insert(2);
|
||||
|
||||
let all =
|
||||
unsafe { slab.get_many_mut_unchecked([parent, child, grandchild].into_iter()) }.unwrap();
|
||||
println!("All: {:#?}", all);
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct Slab<T> {
|
||||
data: Vec<Option<T>>,
|
||||
free: VecDeque<usize>,
|
||||
}
|
||||
|
||||
impl<T> Default for Slab<T> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Slab<T> {
|
||||
fn new() -> Self {
|
||||
Self {
|
||||
data: Vec::new(),
|
||||
free: VecDeque::new(),
|
||||
}
|
||||
}
|
||||
|
||||
fn get(&self, id: usize) -> Option<&T> {
|
||||
self.data.get(id).and_then(|x| x.as_ref())
|
||||
}
|
||||
|
||||
unsafe fn get_unchecked(&self, id: usize) -> &T {
|
||||
self.data.get_unchecked(id).as_ref().unwrap()
|
||||
}
|
||||
|
||||
fn get_mut(&mut self, id: usize) -> Option<&mut T> {
|
||||
self.data.get_mut(id).and_then(|x| x.as_mut())
|
||||
}
|
||||
|
||||
unsafe fn get_unchecked_mut(&mut self, id: usize) -> &mut T {
|
||||
self.data.get_unchecked_mut(id).as_mut().unwrap()
|
||||
}
|
||||
|
||||
fn get2_mut(&mut self, id1: usize, id2: usize) -> Option<(&mut T, &mut T)> {
|
||||
assert!(id1 != id2);
|
||||
let ptr = self.data.as_mut_ptr();
|
||||
let first = unsafe { &mut *ptr.add(id1) };
|
||||
let second = unsafe { &mut *ptr.add(id2) };
|
||||
if let (Some(first), Some(second)) = (first, second) {
|
||||
Some((first, second))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
unsafe fn get_many_mut_unchecked(
|
||||
&mut self,
|
||||
ids: impl Iterator<Item = usize>,
|
||||
) -> Option<Vec<&mut T>> {
|
||||
let ptr = self.data.as_mut_ptr();
|
||||
let mut result = Vec::new();
|
||||
for id in ids {
|
||||
let item = unsafe { &mut *ptr.add(id) };
|
||||
if let Some(item) = item {
|
||||
result.push(item);
|
||||
} else {
|
||||
return None;
|
||||
}
|
||||
}
|
||||
Some(result)
|
||||
}
|
||||
|
||||
fn insert(&mut self, value: T) -> usize {
|
||||
if let Some(id) = self.free.pop_front() {
|
||||
self.data[id] = Some(value);
|
||||
id
|
||||
} else {
|
||||
self.data.push(Some(value));
|
||||
self.data.len() - 1
|
||||
}
|
||||
}
|
||||
|
||||
fn try_remove(&mut self, id: usize) -> Option<T> {
|
||||
self.data.get_mut(id).and_then(|x| {
|
||||
self.free.push_back(id);
|
||||
x.take()
|
||||
})
|
||||
}
|
||||
|
||||
fn remove(&mut self, id: usize) -> T {
|
||||
self.try_remove(id).unwrap()
|
||||
}
|
||||
|
||||
fn len(&self) -> usize {
|
||||
self.data.len() - self.free.len()
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,10 @@
|
||||
use crate::{node::NodeType, real_dom::RealDom, state::State, tree::TreeView, NodeId, RealNodeId};
|
||||
use crate::{
|
||||
node::{FromAnyValue, NodeType},
|
||||
real_dom::RealDom,
|
||||
state::State,
|
||||
tree::TreeView,
|
||||
NodeId, RealNodeId,
|
||||
};
|
||||
use dioxus_core::{Mutation, Mutations};
|
||||
use std::fmt::Debug;
|
||||
|
||||
@@ -67,7 +73,11 @@ impl PersistantElementIter {
|
||||
|
||||
/// remove stale element refreneces
|
||||
/// returns true if the focused element is removed
|
||||
pub fn prune<S: State>(&mut self, mutations: &Mutations, rdom: &RealDom<S>) -> bool {
|
||||
pub fn prune<S: State<V>, V: FromAnyValue>(
|
||||
&mut self,
|
||||
mutations: &Mutations,
|
||||
rdom: &RealDom<S, V>,
|
||||
) -> bool {
|
||||
let mut changed = false;
|
||||
let ids_removed: Vec<_> = mutations
|
||||
.edits
|
||||
@@ -124,7 +134,7 @@ impl PersistantElementIter {
|
||||
}
|
||||
|
||||
/// get the next element
|
||||
pub fn next<S: State>(&mut self, rdom: &RealDom<S>) -> ElementProduced {
|
||||
pub fn next<S: State<V>, V: FromAnyValue>(&mut self, rdom: &RealDom<S, V>) -> ElementProduced {
|
||||
if self.stack.is_empty() {
|
||||
let id = NodeId(0);
|
||||
let new = (id, NodePosition::AtNode);
|
||||
@@ -160,12 +170,12 @@ impl PersistantElementIter {
|
||||
}
|
||||
|
||||
/// get the previous element
|
||||
pub fn prev<S: State>(&mut self, rdom: &RealDom<S>) -> ElementProduced {
|
||||
pub fn prev<S: State<V>, V: FromAnyValue>(&mut self, rdom: &RealDom<S, V>) -> ElementProduced {
|
||||
// recursively add the last child element to the stack
|
||||
fn push_back<S: State>(
|
||||
fn push_back<S: State<V>, V: FromAnyValue>(
|
||||
stack: &mut smallvec::SmallVec<[(RealNodeId, NodePosition); 5]>,
|
||||
new_node: RealNodeId,
|
||||
rdom: &RealDom<S>,
|
||||
rdom: &RealDom<S, V>,
|
||||
) -> RealNodeId {
|
||||
match &rdom[new_node].node_data.node_type {
|
||||
NodeType::Element { .. } => {
|
||||
@@ -228,8 +238,8 @@ impl PersistantElementIter {
|
||||
|
||||
#[derive(Default, Clone, Debug)]
|
||||
struct Empty {}
|
||||
impl State for Empty {
|
||||
const PASSES: &'static [crate::AnyPass<crate::node::Node<Self>>] = &[];
|
||||
impl State<()> for Empty {
|
||||
const PASSES: &'static [crate::AnyPass<crate::node::Node<Self, ()>>] = &[];
|
||||
|
||||
const MASKS: &'static [crate::NodeMask] = &[];
|
||||
}
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dioxus-router"
|
||||
version = "0.2.3"
|
||||
version = "0.3.0"
|
||||
edition = "2018"
|
||||
description = "Cross-platform router for Dioxus apps"
|
||||
license = "MIT/Apache-2.0"
|
||||
@@ -11,7 +11,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus = { path = "../dioxus" }
|
||||
dioxus = { path = "../dioxus", version = "0.3.0"}
|
||||
futures-channel = "0.3.21"
|
||||
url = { version = "2.2.2", default-features = false }
|
||||
|
||||
@@ -50,7 +50,7 @@ log = "0.4.14"
|
||||
wasm-logger = "0.2.0"
|
||||
wasm-bindgen-test = "0.3"
|
||||
gloo-utils = "0.1.2"
|
||||
dioxus-web = { path = "../web" }
|
||||
dioxus-web = { path = "../web", version = "0.3.0" }
|
||||
# dioxus-desktop = { path = "../desktop", optional = true }
|
||||
|
||||
# not wasm
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dioxus-rsx"
|
||||
version = "0.0.1"
|
||||
version = "0.0.2"
|
||||
edition = "2018"
|
||||
license = "MIT/Apache-2.0"
|
||||
description = "Core functionality for Dioxus - a concurrent renderer-agnostic Virtual DOM for interactive user experiences"
|
||||
@@ -13,8 +13,8 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
|
||||
[dependencies]
|
||||
proc-macro2 = { version = "1.0", features = ["span-locations"] }
|
||||
dioxus-core = { path = "../core" }
|
||||
dioxus-core = { path = "../core", version = "0.3.0"}
|
||||
syn = { version = "1.0", features = ["full", "extra-traits"] }
|
||||
quote = { version = "1.0" }
|
||||
serde = { version = "1.0", features = ["derive"] }
|
||||
internment = "0.7.0"
|
||||
internment = "0.7.0"
|
||||
@@ -19,8 +19,7 @@ use syn::{
|
||||
ext::IdentExt,
|
||||
parse::{Parse, ParseBuffer, ParseStream},
|
||||
spanned::Spanned,
|
||||
token, AngleBracketedGenericArguments, Error, Expr, Ident, LitStr, PathArguments, Result,
|
||||
Token,
|
||||
AngleBracketedGenericArguments, Error, Expr, Ident, LitStr, PathArguments, Result, Token,
|
||||
};
|
||||
|
||||
#[derive(PartialEq, Eq, Clone, Debug, Hash)]
|
||||
@@ -30,6 +29,7 @@ pub struct Component {
|
||||
pub fields: Vec<ComponentField>,
|
||||
pub children: Vec<BodyNode>,
|
||||
pub manual_props: Option<Expr>,
|
||||
pub brace: syn::token::Brace,
|
||||
}
|
||||
|
||||
impl Component {
|
||||
@@ -88,13 +88,9 @@ impl Parse for Component {
|
||||
|
||||
// if we see a `{` then we have a block
|
||||
// else parse as a function-like call
|
||||
if stream.peek(token::Brace) {
|
||||
syn::braced!(content in stream);
|
||||
} else {
|
||||
syn::parenthesized!(content in stream);
|
||||
}
|
||||
let brace = syn::braced!(content in stream);
|
||||
|
||||
let mut body = Vec::new();
|
||||
let mut fields = Vec::new();
|
||||
let mut children = Vec::new();
|
||||
let mut manual_props = None;
|
||||
|
||||
@@ -104,7 +100,7 @@ impl Parse for Component {
|
||||
content.parse::<Token![..]>()?;
|
||||
manual_props = Some(content.parse::<Expr>()?);
|
||||
} else if content.peek(Ident) && content.peek2(Token![:]) && !content.peek3(Token![:]) {
|
||||
body.push(content.parse::<ComponentField>()?);
|
||||
fields.push(content.parse::<ComponentField>()?);
|
||||
} else {
|
||||
children.push(content.parse::<BodyNode>()?);
|
||||
}
|
||||
@@ -117,9 +113,10 @@ impl Parse for Component {
|
||||
Ok(Self {
|
||||
name,
|
||||
prop_gen_args,
|
||||
fields: body,
|
||||
fields,
|
||||
children,
|
||||
manual_props,
|
||||
brace,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,6 +17,7 @@ pub struct Element {
|
||||
pub attributes: Vec<ElementAttrNamed>,
|
||||
pub children: Vec<BodyNode>,
|
||||
pub _is_static: bool,
|
||||
pub brace: syn::token::Brace,
|
||||
}
|
||||
|
||||
impl Parse for Element {
|
||||
@@ -25,7 +26,7 @@ impl Parse for Element {
|
||||
|
||||
// parse the guts
|
||||
let content: ParseBuffer;
|
||||
syn::braced!(content in stream);
|
||||
let brace = syn::braced!(content in stream);
|
||||
|
||||
let mut attributes: Vec<ElementAttrNamed> = vec![];
|
||||
let mut children: Vec<BodyNode> = vec![];
|
||||
@@ -152,6 +153,7 @@ impl Parse for Element {
|
||||
name: el_name,
|
||||
attributes,
|
||||
children,
|
||||
brace,
|
||||
_is_static: false,
|
||||
})
|
||||
}
|
||||
@@ -246,8 +248,8 @@ impl ToTokens for ElementAttrNamed {
|
||||
__cx.attr(
|
||||
dioxus_elements::#el_name::#name.0,
|
||||
#value,
|
||||
None,
|
||||
false
|
||||
dioxus_elements::#el_name::#name.1,
|
||||
dioxus_elements::#el_name::#name.2
|
||||
)
|
||||
}
|
||||
}
|
||||
@@ -256,8 +258,8 @@ impl ToTokens for ElementAttrNamed {
|
||||
__cx.attr(
|
||||
dioxus_elements::#el_name::#name.0,
|
||||
#value,
|
||||
None,
|
||||
false
|
||||
dioxus_elements::#el_name::#name.1,
|
||||
dioxus_elements::#el_name::#name.2
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,10 @@
|
||||
[package]
|
||||
name = "dioxus-signals"
|
||||
version = "0.0.0"
|
||||
edition = "2018"
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "../core" }
|
||||
slab = "0.4.7"
|
||||
@@ -0,0 +1,131 @@
|
||||
use std::{
|
||||
cell::{Ref, RefMut},
|
||||
fmt::Display,
|
||||
marker::PhantomData,
|
||||
ops::{Add, Div, Mul, Sub},
|
||||
};
|
||||
|
||||
mod rt;
|
||||
|
||||
use dioxus_core::ScopeState;
|
||||
pub use rt::*;
|
||||
|
||||
pub fn use_init_signal_rt(cx: &ScopeState) {
|
||||
cx.use_hook(|| {
|
||||
let rt = crate::rt::claim_rt(cx.schedule_update_any());
|
||||
cx.provide_context(rt);
|
||||
});
|
||||
}
|
||||
|
||||
pub fn use_signal<T: 'static>(cx: &ScopeState, f: impl FnOnce() -> T) -> Signal<T> {
|
||||
cx.use_hook(|| {
|
||||
let rt: &'static SignalRt = cx.consume_context().unwrap();
|
||||
let id = rt.init(f());
|
||||
rt.subscribe(id, cx.scope_id());
|
||||
|
||||
struct SignalHook<T> {
|
||||
signal: Signal<T>,
|
||||
}
|
||||
|
||||
impl<T> Drop for SignalHook<T> {
|
||||
fn drop(&mut self) {
|
||||
self.signal.rt.remove(self.signal.id);
|
||||
}
|
||||
}
|
||||
|
||||
SignalHook {
|
||||
signal: Signal {
|
||||
id,
|
||||
rt,
|
||||
t: PhantomData,
|
||||
},
|
||||
}
|
||||
})
|
||||
.signal
|
||||
}
|
||||
|
||||
pub struct Signal<T> {
|
||||
id: usize,
|
||||
rt: &'static SignalRt,
|
||||
t: PhantomData<T>,
|
||||
}
|
||||
|
||||
impl<T: 'static> Signal<T> {
|
||||
pub fn read(&self) -> Ref<T> {
|
||||
self.rt.read(self.id)
|
||||
}
|
||||
|
||||
pub fn write(&self) -> RefMut<T> {
|
||||
self.rt.write(self.id)
|
||||
}
|
||||
|
||||
pub fn set(&mut self, value: T) {
|
||||
self.rt.set(self.id, value);
|
||||
}
|
||||
|
||||
pub fn with<O>(&self, f: impl FnOnce(&T) -> O) -> O {
|
||||
let write = self.read();
|
||||
f(&*write)
|
||||
}
|
||||
|
||||
pub fn update<O>(&self, _f: impl FnOnce(&mut T) -> O) -> O {
|
||||
let mut write = self.write();
|
||||
_f(&mut *write)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + 'static> Signal<T> {
|
||||
pub fn get(&self) -> T {
|
||||
self.rt.get(self.id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Clone + 'static> std::ops::Deref for Signal<T> {
|
||||
type Target = dyn Fn() -> T;
|
||||
|
||||
fn deref(&self) -> &Self::Target {
|
||||
self.rt.getter(self.id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> std::clone::Clone for Signal<T> {
|
||||
fn clone(&self) -> Self {
|
||||
Self {
|
||||
t: PhantomData,
|
||||
id: self.id,
|
||||
rt: self.rt,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> Copy for Signal<T> {}
|
||||
|
||||
impl<T: Display + 'static> Display for Signal<T> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
self.rt.with::<T, _>(self.id, |v| T::fmt(v, f))
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Add<Output = T> + Copy + 'static> std::ops::AddAssign<T> for Signal<T> {
|
||||
fn add_assign(&mut self, rhs: T) {
|
||||
self.set(self.get() + rhs);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Sub<Output = T> + Copy + 'static> std::ops::SubAssign<T> for Signal<T> {
|
||||
fn sub_assign(&mut self, rhs: T) {
|
||||
self.set(self.get() - rhs);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Mul<Output = T> + Copy + 'static> std::ops::MulAssign<T> for Signal<T> {
|
||||
fn mul_assign(&mut self, rhs: T) {
|
||||
self.set(self.get() * rhs);
|
||||
}
|
||||
}
|
||||
|
||||
impl<T: Div<Output = T> + Copy + 'static> std::ops::DivAssign<T> for Signal<T> {
|
||||
fn div_assign(&mut self, rhs: T) {
|
||||
self.set(self.get() / rhs);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,121 @@
|
||||
use std::{any::Any, cell::RefCell, sync::Arc};
|
||||
|
||||
use dioxus_core::ScopeId;
|
||||
use slab::Slab;
|
||||
|
||||
thread_local! {
|
||||
// we cannot drop these since any future might be using them
|
||||
static RUNTIMES: RefCell<Vec<&'static SignalRt>> = RefCell::new(Vec::new());
|
||||
}
|
||||
|
||||
/// Provide the runtime for signals
|
||||
///
|
||||
/// This will reuse dead runtimes
|
||||
pub fn claim_rt(update_any: Arc<dyn Fn(ScopeId)>) -> &'static SignalRt {
|
||||
RUNTIMES.with(|runtimes| {
|
||||
if let Some(rt) = runtimes.borrow_mut().pop() {
|
||||
return rt;
|
||||
}
|
||||
|
||||
Box::leak(Box::new(SignalRt {
|
||||
signals: RefCell::new(Slab::new()),
|
||||
update_any,
|
||||
}))
|
||||
})
|
||||
}
|
||||
|
||||
/// Push this runtime into the global runtime list
|
||||
pub fn reclam_rt(_rt: &'static SignalRt) {
|
||||
RUNTIMES.with(|runtimes| {
|
||||
runtimes.borrow_mut().push(_rt);
|
||||
});
|
||||
}
|
||||
|
||||
pub struct SignalRt {
|
||||
pub(crate) signals: RefCell<Slab<Inner>>,
|
||||
pub(crate) update_any: Arc<dyn Fn(ScopeId)>,
|
||||
}
|
||||
|
||||
impl SignalRt {
|
||||
pub fn init<T: 'static>(&'static self, val: T) -> usize {
|
||||
self.signals.borrow_mut().insert(Inner {
|
||||
value: Box::new(val),
|
||||
subscribers: Vec::new(),
|
||||
getter: None,
|
||||
})
|
||||
}
|
||||
|
||||
pub fn subscribe(&self, id: usize, subscriber: ScopeId) {
|
||||
self.signals.borrow_mut()[id].subscribers.push(subscriber);
|
||||
}
|
||||
|
||||
pub fn get<T: Clone + 'static>(&self, id: usize) -> T {
|
||||
self.signals.borrow()[id]
|
||||
.value
|
||||
.downcast_ref::<T>()
|
||||
.cloned()
|
||||
.unwrap()
|
||||
}
|
||||
|
||||
pub fn set<T: 'static>(&self, id: usize, value: T) {
|
||||
let mut signals = self.signals.borrow_mut();
|
||||
let inner = &mut signals[id];
|
||||
inner.value = Box::new(value);
|
||||
|
||||
for subscriber in inner.subscribers.iter() {
|
||||
(self.update_any)(*subscriber);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn remove(&self, id: usize) {
|
||||
self.signals.borrow_mut().remove(id);
|
||||
}
|
||||
|
||||
pub fn with<T: 'static, O>(&self, id: usize, f: impl FnOnce(&T) -> O) -> O {
|
||||
let signals = self.signals.borrow();
|
||||
let inner = &signals[id];
|
||||
let inner = inner.value.downcast_ref::<T>().unwrap();
|
||||
f(inner)
|
||||
}
|
||||
|
||||
pub(crate) fn read<T: 'static>(&self, id: usize) -> std::cell::Ref<T> {
|
||||
let signals = self.signals.borrow();
|
||||
std::cell::Ref::map(signals, |signals| {
|
||||
signals[id].value.downcast_ref::<T>().unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn write<T: 'static>(&self, id: usize) -> std::cell::RefMut<T> {
|
||||
let signals = self.signals.borrow_mut();
|
||||
std::cell::RefMut::map(signals, |signals| {
|
||||
signals[id].value.downcast_mut::<T>().unwrap()
|
||||
})
|
||||
}
|
||||
|
||||
pub(crate) fn getter<T: 'static + Clone>(&self, id: usize) -> &dyn Fn() -> T {
|
||||
let mut signals = self.signals.borrow_mut();
|
||||
let inner = &mut signals[id];
|
||||
let r = inner.getter.as_mut();
|
||||
|
||||
if r.is_none() {
|
||||
let rt = self;
|
||||
let r = move || rt.get::<T>(id);
|
||||
let getter: Box<dyn Fn() -> T> = Box::new(r);
|
||||
let getter: Box<dyn Fn()> = unsafe { std::mem::transmute(getter) };
|
||||
|
||||
inner.getter = Some(getter);
|
||||
}
|
||||
|
||||
let r = inner.getter.as_ref().unwrap();
|
||||
|
||||
unsafe { std::mem::transmute::<&dyn Fn(), &dyn Fn() -> T>(r) }
|
||||
}
|
||||
}
|
||||
|
||||
pub(crate) struct Inner {
|
||||
pub value: Box<dyn Any>,
|
||||
pub subscribers: Vec<ScopeId>,
|
||||
|
||||
// todo: this has a soundness hole in it that you might not run into
|
||||
pub getter: Option<Box<dyn Fn()>>,
|
||||
}
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dioxus-ssr"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0"
|
||||
authors = ["Jonathan Kelley"]
|
||||
edition = "2018"
|
||||
description = "Dioxus render-to-string"
|
||||
@@ -13,10 +13,10 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "../core", version = "^0.2.1", features = ["serialize"] }
|
||||
dioxus-core = { path = "../core", version = "^0.3.0", features = ["serialize"] }
|
||||
|
||||
[dev-dependencies]
|
||||
dioxus = { path = "../dioxus" }
|
||||
dioxus = { path = "../dioxus", version = "0.3.0" }
|
||||
thiserror = "1.0.23"
|
||||
log = "0.4.13"
|
||||
fern = { version = "0.6.0", features = ["colored"] }
|
||||
|
||||
@@ -13,9 +13,9 @@ license = "MIT/Apache-2.0"
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
||||
dioxus = { path = "../dioxus", version = "^0.2.1" }
|
||||
dioxus-core = { path = "../core", version = "^0.2.1" }
|
||||
dioxus-html = { path = "../html", version = "^0.2.1" }
|
||||
dioxus = { path = "../dioxus", version = "^0.3.0" }
|
||||
dioxus-core = { path = "../core", version = "^0.3.0" }
|
||||
dioxus-html = { path = "../html", version = "^0.3.0" }
|
||||
dioxus-native-core = { path = "../native-core", version = "^0.2.0" }
|
||||
dioxus-native-core-macro = { path = "../native-core-macro", version = "^0.2.0" }
|
||||
|
||||
@@ -27,7 +27,7 @@ futures = "0.3.19"
|
||||
taffy = "0.2.1"
|
||||
smallvec = "1.6"
|
||||
rustc-hash = "1.1.0"
|
||||
anymap = "0.12.1"
|
||||
anymap = "1.0.0-beta.2"
|
||||
futures-channel = "0.3.25"
|
||||
|
||||
[dev-dependencies]
|
||||
|
||||
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
@@ -12,7 +12,7 @@ fn app(cx: Scope) -> Element {
|
||||
width: "100%",
|
||||
height: "100%",
|
||||
flex_direction: "column",
|
||||
onwheel: move |evt| alpha.set((**alpha + evt.inner().delta().strip_units().y as i64).min(100).max(0)),
|
||||
onwheel: move |evt| alpha.set((**alpha + evt.inner().delta().strip_units().y as i64).clamp(0, 100)),
|
||||
|
||||
p {
|
||||
background_color: "black",
|
||||
File renamed without changes.
@@ -79,7 +79,7 @@ impl NodeDepState for Focus {
|
||||
if let Some(index) = a
|
||||
.value
|
||||
.as_int()
|
||||
.or_else(|| a.value.as_text().and_then(|v| v.parse::<i32>().ok()))
|
||||
.or_else(|| a.value.as_text().and_then(|v| v.parse::<i64>().ok()))
|
||||
{
|
||||
match index.cmp(&0) {
|
||||
Ordering::Less => FocusLevel::Unfocusable,
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "dioxus-web"
|
||||
version = "0.2.1"
|
||||
version = "0.3.0"
|
||||
authors = ["Jonathan Kelley"]
|
||||
edition = "2018"
|
||||
description = "Dioxus VirtualDOM renderer for the web browser using websys"
|
||||
@@ -11,9 +11,9 @@ documentation = "https://dioxuslabs.com"
|
||||
keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
|
||||
[dependencies]
|
||||
dioxus-core = { path = "../core", version = "^0.2.1", features = ["serialize"] }
|
||||
dioxus-html = { path = "../html", version = "^0.2.1", features = ["wasm-bind"] }
|
||||
dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.1", features = [
|
||||
dioxus-core = { path = "../core", version = "^0.3.0", features = ["serialize"] }
|
||||
dioxus-html = { path = "../html", version = "^0.3.0", features = ["wasm-bind"] }
|
||||
dioxus-interpreter-js = { path = "../interpreter", version = "^0.3.0", features = [
|
||||
"sledgehammer"
|
||||
] }
|
||||
|
||||
@@ -84,8 +84,8 @@ panic_hook = ["console_error_panic_hook"]
|
||||
hydrate = []
|
||||
|
||||
[dev-dependencies]
|
||||
dioxus = { path = "../dioxus" }
|
||||
dioxus = { path = "../dioxus", version = "0.3.0" }
|
||||
wasm-bindgen-test = "0.3.29"
|
||||
dioxus-ssr = { path = "../ssr" }
|
||||
dioxus-ssr = { path = "../ssr", version = "0.3.0"}
|
||||
wasm-logger = "0.2.0"
|
||||
dioxus-web = { path = "." }
|
||||
+31
-10
@@ -7,7 +7,9 @@
|
||||
//! - tests to ensure dyn_into works for various event types.
|
||||
//! - Partial delegation?>
|
||||
|
||||
use dioxus_core::{ElementId, Mutation, Template, TemplateAttribute, TemplateNode};
|
||||
use dioxus_core::{
|
||||
BorrowedAttributeValue, ElementId, Mutation, Template, TemplateAttribute, TemplateNode,
|
||||
};
|
||||
use dioxus_html::{event_bubbles, CompositionData, FormData};
|
||||
use dioxus_interpreter_js::{save_template, Channel};
|
||||
use futures_channel::mpsc;
|
||||
@@ -175,16 +177,33 @@ impl WebsysDom {
|
||||
value,
|
||||
id,
|
||||
ns,
|
||||
} => i.set_attribute(id.0 as u32, name, value, ns.unwrap_or_default()),
|
||||
SetBoolAttribute { name, value, id } => {
|
||||
i.set_attribute(id.0 as u32, name, if *value { "true" } else { "false" }, "")
|
||||
}
|
||||
} => match value {
|
||||
BorrowedAttributeValue::Text(txt) => {
|
||||
i.set_attribute(id.0 as u32, name, txt, ns.unwrap_or_default())
|
||||
}
|
||||
BorrowedAttributeValue::Float(f) => {
|
||||
i.set_attribute(id.0 as u32, name, &f.to_string(), ns.unwrap_or_default())
|
||||
}
|
||||
BorrowedAttributeValue::Int(n) => {
|
||||
i.set_attribute(id.0 as u32, name, &n.to_string(), ns.unwrap_or_default())
|
||||
}
|
||||
BorrowedAttributeValue::Bool(b) => i.set_attribute(
|
||||
id.0 as u32,
|
||||
name,
|
||||
if *b { "true" } else { "false" },
|
||||
ns.unwrap_or_default(),
|
||||
),
|
||||
BorrowedAttributeValue::None => {
|
||||
i.remove_attribute(id.0 as u32, name, ns.unwrap_or_default())
|
||||
}
|
||||
_ => unreachable!(),
|
||||
},
|
||||
SetText { value, id } => i.set_text(id.0 as u32, value),
|
||||
NewEventListener { name, id, .. } => {
|
||||
i.new_event_listener(name, id.0 as u32, event_bubbles(&name[2..]) as u8);
|
||||
i.new_event_listener(name, id.0 as u32, event_bubbles(name) as u8);
|
||||
}
|
||||
RemoveEventListener { name, id } => {
|
||||
i.remove_event_listener(name, id.0 as u32, event_bubbles(&name[2..]) as u8)
|
||||
i.remove_event_listener(name, id.0 as u32, event_bubbles(name) as u8)
|
||||
}
|
||||
Remove { id } => i.remove(id.0 as u32),
|
||||
PushRoot { id } => i.push_root(id.0 as u32),
|
||||
@@ -210,11 +229,13 @@ pub fn virtual_event_from_websys_event(event: web_sys::Event, target: Element) -
|
||||
|
||||
"change" | "input" | "invalid" | "reset" | "submit" => read_input_to_data(target),
|
||||
|
||||
"click" | "contextmenu" | "dblclick" | "doubleclick" | "drag" | "dragend" | "dragenter"
|
||||
| "dragexit" | "dragleave" | "dragover" | "dragstart" | "drop" | "mousedown"
|
||||
| "mouseenter" | "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
|
||||
"click" | "contextmenu" | "dblclick" | "doubleclick" | "mousedown" | "mouseenter"
|
||||
| "mouseleave" | "mousemove" | "mouseout" | "mouseover" | "mouseup" => {
|
||||
Rc::new(MouseData::from(event))
|
||||
}
|
||||
"drag" | "dragend" | "dragenter" | "dragexit" | "dragleave" | "dragover" | "dragstart"
|
||||
| "drop" => Rc::new(DragData::from(event)),
|
||||
|
||||
"pointerdown" | "pointermove" | "pointerup" | "pointercancel" | "gotpointercapture"
|
||||
| "lostpointercapture" | "pointerenter" | "pointerleave" | "pointerover" | "pointerout" => {
|
||||
Rc::new(PointerData::from(event))
|
||||
|
||||
@@ -189,10 +189,12 @@ pub async fn run_with_props<T: 'static>(root: fn(Scope<T>) -> Element, root_prop
|
||||
|
||||
// if should_hydrate {
|
||||
// } else {
|
||||
let edits = dom.rebuild();
|
||||
{
|
||||
let edits = dom.rebuild();
|
||||
|
||||
websys_dom.load_templates(&edits.templates);
|
||||
websys_dom.apply_edits(edits.edits);
|
||||
websys_dom.load_templates(&edits.templates);
|
||||
websys_dom.apply_edits(edits.edits);
|
||||
}
|
||||
|
||||
// the mutations come back with nothing - we need to actually mount them
|
||||
websys_dom.mount();
|
||||
|
||||
Reference in new issue
Block a user