Merge branch 'upstream' into simplify-native-core
This commit is contained in:
commit
ecf4ee275a
142 files changed
+2951
-1853
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"] }
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
_ => (),
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
+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);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -34,7 +34,7 @@ impl VirtualDom {
|
||||
self.scopes[task.scope.0].spawned_tasks.remove(&id);
|
||||
|
||||
// Remove it from the scheduler
|
||||
tasks.remove(id.0);
|
||||
tasks.try_remove(id.0);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -44,7 +44,7 @@ impl VirtualDom {
|
||||
hook_idx: Default::default(),
|
||||
shared_contexts: Default::default(),
|
||||
borrowed_props: Default::default(),
|
||||
listeners: Default::default(),
|
||||
attributes_to_drop: Default::default(),
|
||||
}))
|
||||
}
|
||||
|
||||
|
||||
@@ -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};
|
||||
@@ -88,7 +88,7 @@ pub struct ScopeState {
|
||||
pub(crate) spawned_tasks: 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>>,
|
||||
@@ -374,11 +374,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 +503,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 +513,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 +556,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;
|
||||
}
|
||||
}
|
||||
@@ -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 },
|
||||
]
|
||||
});
|
||||
}
|
||||
@@ -9,32 +9,34 @@ use std::time::Duration;
|
||||
async fn it_works() {
|
||||
let mut dom = VirtualDom::new(app);
|
||||
|
||||
let mutations = dom.rebuild().santize();
|
||||
{
|
||||
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 }
|
||||
// ]
|
||||
// );
|
||||
// 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) },
|
||||
]
|
||||
);
|
||||
// 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) },
|
||||
]
|
||||
);
|
||||
}
|
||||
|
||||
// wait just a moment, not enough time for the boundary to resolve
|
||||
|
||||
|
||||
@@ -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"
|
||||
@@ -12,11 +12,11 @@ keywords = ["dom", "ui", "gui", "react", "wasm"]
|
||||
rust-version = "1.60.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,
|
||||
};
|
||||
|
||||
|
||||
|
||||
@@ -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,15 @@ class Interpreter {
|
||||
this.nodes[root].textContent = text;
|
||||
}
|
||||
SetAttribute(id, field, value, ns) {
|
||||
const node = this.nodes[id];
|
||||
this.SetAttributeInner(node, field, value, ns);
|
||||
console.log("set attribute", id, 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;
|
||||
@@ -334,9 +341,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"] }
|
||||
|
||||
@@ -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"
|
||||
@@ -23,6 +23,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 {
|
||||
|
||||
@@ -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,19 +11,22 @@ 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" }
|
||||
@@ -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,11 +1,11 @@
|
||||
use std::any::Any;
|
||||
use std::hash::BuildHasherDefault;
|
||||
|
||||
pub use node_ref::NodeMask;
|
||||
pub use passes::AnyMapLike;
|
||||
pub use passes::{Dependancy, Pass, TypeErasedPass};
|
||||
use rustc_hash::FxHasher;
|
||||
pub use state::State;
|
||||
pub use tree::NodeId;
|
||||
use tree::NodeId;
|
||||
|
||||
pub mod layout_attributes;
|
||||
pub mod node;
|
||||
@@ -18,7 +18,7 @@ pub mod utils;
|
||||
|
||||
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,44 +1,45 @@
|
||||
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 = ()> {
|
||||
/// 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>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct ElementNode {
|
||||
pub struct ElementNode<V: FromAnyValue> {
|
||||
pub tag: String,
|
||||
pub namespace: Option<String>,
|
||||
pub attributes: FxHashMap<OwnedAttributeDiscription, OwnedAttributeValue>,
|
||||
pub attributes: FxHashMap<OwnedAttributeDiscription, OwnedAttributeValue<V>>,
|
||||
pub listeners: FxHashSet<String>,
|
||||
}
|
||||
|
||||
/// 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(String),
|
||||
Element(ElementNode),
|
||||
Element(ElementNode<V>),
|
||||
Placeholder,
|
||||
}
|
||||
|
||||
impl<S: State> Node<S> {
|
||||
pub 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 {
|
||||
@@ -65,24 +66,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 + 'static {
|
||||
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),
|
||||
@@ -90,14 +124,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,
|
||||
@@ -111,9 +145,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::{ElementNode, NodeData, NodeType, OwnedAttributeView},
|
||||
node::{ElementNode, FromAnyValue, NodeData, NodeType, OwnedAttributeView},
|
||||
state::union_ordered_iter,
|
||||
NodeId,
|
||||
};
|
||||
|
||||
/// 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(ElementNode { attributes, .. }) => Some(
|
||||
attributes
|
||||
|
||||
@@ -4,7 +4,7 @@ use std::any::{Any, TypeId};
|
||||
use std::collections::BTreeMap;
|
||||
use std::sync::Arc;
|
||||
|
||||
use crate::node::Node;
|
||||
use crate::node::{FromAnyValue, Node};
|
||||
use crate::node_ref::NodeView;
|
||||
use crate::state::State;
|
||||
use crate::tree::TreeViewMut;
|
||||
@@ -12,7 +12,7 @@ use crate::tree::{Tree, TreeView};
|
||||
use crate::{FxDashMap, FxDashSet, SendAnyMap};
|
||||
use crate::{NodeId, NodeMask};
|
||||
|
||||
pub trait Pass: Any {
|
||||
pub trait Pass<V: FromAnyValue>: Any {
|
||||
/// This is a tuple of (T: Any, ..)
|
||||
type ParentDependencies: Dependancy;
|
||||
/// This is a tuple of (T: Any, ..)
|
||||
@@ -23,7 +23,7 @@ pub trait Pass: Any {
|
||||
|
||||
fn pass<'a>(
|
||||
&mut self,
|
||||
node_view: NodeView,
|
||||
node_view: NodeView<V>,
|
||||
node: <Self::NodeDependencies as Dependancy>::ElementBorrowed<'a>,
|
||||
parent: Option<<Self::ParentDependencies as Dependancy>::ElementBorrowed<'a>>,
|
||||
children: Option<
|
||||
@@ -58,7 +58,7 @@ pub trait Pass: Any {
|
||||
}
|
||||
}
|
||||
|
||||
fn to_type_erased<T: AnyMapLike + State>() -> TypeErasedPass<T>
|
||||
fn to_type_erased<T: AnyMapLike + State<V>>() -> TypeErasedPass<T, V>
|
||||
where
|
||||
Self: Sized,
|
||||
{
|
||||
@@ -72,11 +72,11 @@ pub trait Pass: Any {
|
||||
mask: Self::NODE_MASK,
|
||||
pass_direction: Self::pass_direction(),
|
||||
pass: Box::new(
|
||||
|node_id: NodeId, any_map: &mut Tree<Node<T>>, context: &SendAnyMap| {
|
||||
|node_id: NodeId, any_map: &mut Tree<Node<T, V>>, context: &SendAnyMap| {
|
||||
let (current_node, parent, children) = any_map
|
||||
.node_parent_children_mut(node_id)
|
||||
.expect("tried to run pass on node that does not exist");
|
||||
let current_node_raw = current_node as *mut Node<T>;
|
||||
let current_node_raw = current_node as *mut Node<T, V>;
|
||||
let node = Self::NodeDependencies::borrow_elements_from(¤t_node.state)
|
||||
.expect("tried to get a pass that does not exist");
|
||||
let parent = parent.map(|parent| {
|
||||
@@ -105,7 +105,7 @@ pub trait Pass: Any {
|
||||
)
|
||||
},
|
||||
)
|
||||
as Box<dyn Fn(NodeId, &mut Tree<Node<T>>, &SendAnyMap) -> bool + Send + Sync>,
|
||||
as Box<dyn Fn(NodeId, &mut Tree<Node<T, V>>, &SendAnyMap) -> bool + Send + Sync>,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -145,21 +145,21 @@ pub trait Pass: Any {
|
||||
}
|
||||
}
|
||||
|
||||
pub struct TypeErasedPass<T: AnyMapLike + State> {
|
||||
pub struct TypeErasedPass<T: AnyMapLike + State<V>, V: FromAnyValue = ()> {
|
||||
pub(crate) this_type_id: TypeId,
|
||||
pub(crate) parent_dependant: bool,
|
||||
pub(crate) child_dependant: bool,
|
||||
pub(crate) combined_dependancy_type_ids: FxHashSet<TypeId>,
|
||||
pub(crate) dependants: FxHashSet<TypeId>,
|
||||
pub(crate) mask: NodeMask,
|
||||
pass: PassCallback<T>,
|
||||
pass: PassCallback<T, V>,
|
||||
pub(crate) pass_direction: PassDirection,
|
||||
}
|
||||
|
||||
impl<T: AnyMapLike + State> TypeErasedPass<T> {
|
||||
impl<T: AnyMapLike + State<V>, V: FromAnyValue> TypeErasedPass<T, V> {
|
||||
fn resolve(
|
||||
&self,
|
||||
tree: &mut Tree<Node<T>>,
|
||||
tree: &mut Tree<Node<T, V>>,
|
||||
mut dirty: DirtyNodes,
|
||||
dirty_states: &DirtyNodeStates,
|
||||
nodes_updated: &FxDashSet<NodeId>,
|
||||
@@ -217,7 +217,8 @@ pub enum PassDirection {
|
||||
AnyOrder,
|
||||
}
|
||||
|
||||
type PassCallback<T> = Box<dyn Fn(NodeId, &mut Tree<Node<T>>, &SendAnyMap) -> bool + Send + Sync>;
|
||||
type PassCallback<T, V> =
|
||||
Box<dyn Fn(NodeId, &mut Tree<Node<T, V>>, &SendAnyMap) -> bool + Send + Sync>;
|
||||
|
||||
pub trait AnyMapLike {
|
||||
fn get<T: Any>(&self) -> Option<&T>;
|
||||
@@ -364,10 +365,10 @@ impl DirtyNodeStates {
|
||||
}
|
||||
}
|
||||
|
||||
pub fn resolve_passes<T: AnyMapLike + State + Send>(
|
||||
tree: &mut Tree<Node<T>>,
|
||||
pub fn resolve_passes<T: AnyMapLike + State<V> + Send, V: FromAnyValue + Send>(
|
||||
tree: &mut Tree<Node<T, V>>,
|
||||
dirty_nodes: DirtyNodeStates,
|
||||
passes: &[TypeErasedPass<T>],
|
||||
passes: &[TypeErasedPass<T, V>],
|
||||
ctx: SendAnyMap,
|
||||
) -> FxDashSet<NodeId> {
|
||||
let dirty_states = Arc::new(dirty_nodes);
|
||||
@@ -411,7 +412,6 @@ pub fn resolve_passes<T: AnyMapLike + State + Send>(
|
||||
i += 1;
|
||||
}
|
||||
}
|
||||
// all passes are resolved at the end of the scope
|
||||
});
|
||||
resolved_passes.extend(resolving.iter().copied());
|
||||
resolving.clear()
|
||||
|
||||
@@ -1,10 +1,10 @@
|
||||
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::{Index, IndexMut};
|
||||
|
||||
use crate::node::{
|
||||
ElementNode, Node, NodeData, NodeType, OwnedAttributeDiscription, OwnedAttributeValue,
|
||||
ElementNode, FromAnyValue, Node, NodeData, NodeType, OwnedAttributeDiscription,
|
||||
OwnedAttributeValue,
|
||||
};
|
||||
use crate::node_ref::{AttributeMask, NodeMask};
|
||||
use crate::passes::{resolve_passes, DirtyNodeStates, TypeErasedPass};
|
||||
@@ -15,14 +15,17 @@ use crate::{FxDashSet, SendAnyMap};
|
||||
/// 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.
|
||||
pub struct RealDom<S: State + Send> {
|
||||
pub tree: Tree<Node<S>>,
|
||||
///
|
||||
/// # 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.
|
||||
pub struct RealDom<S: State<V>, V: FromAnyValue = ()> {
|
||||
tree: Tree<Node<S, V>>,
|
||||
/// a map from element id to real node id
|
||||
node_id_mapping: Vec<Option<NodeId>>,
|
||||
nodes_listening: FxHashMap<String, FxHashSet<NodeId>>,
|
||||
stack: Vec<NodeId>,
|
||||
templates: FxHashMap<String, Vec<NodeId>>,
|
||||
pub(crate) passes: Box<[TypeErasedPass<S>]>,
|
||||
pub(crate) passes: Box<[TypeErasedPass<S, V>]>,
|
||||
pub(crate) nodes_updated: FxHashMap<NodeId, NodeMask>,
|
||||
passes_updated: DirtyNodeStates,
|
||||
parent_changed_nodes: FxHashSet<NodeId>,
|
||||
@@ -30,26 +33,14 @@ pub struct RealDom<S: State + Send> {
|
||||
nodes_created: FxHashSet<NodeId>,
|
||||
}
|
||||
|
||||
impl<S: State + Send + Debug> Debug for RealDom<S> {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
f.debug_struct("RealDom")
|
||||
.field("tree", &self.tree)
|
||||
.field("node_id_mapping", &self.node_id_mapping)
|
||||
.field("nodes_listening", &self.nodes_listening)
|
||||
.field("stack", &self.stack)
|
||||
.field("templates", &self.templates)
|
||||
.finish()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: State + Send> Default for RealDom<S> {
|
||||
impl<S: State<V>, V: FromAnyValue> Default for RealDom<S, V> {
|
||||
fn default() -> Self {
|
||||
Self::new()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: State + Send> 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(ElementNode {
|
||||
tag: "Root".to_string(),
|
||||
namespace: Some("Root".to_string()),
|
||||
@@ -122,10 +113,6 @@ impl<S: State + Send> 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);
|
||||
}
|
||||
|
||||
@@ -137,7 +124,7 @@ impl<S: State + Send> RealDom<S> {
|
||||
current
|
||||
}
|
||||
|
||||
fn create_node(&mut self, node: Node<S>, mark_dirty: bool) -> NodeId {
|
||||
fn create_node(&mut self, node: Node<S, V>, mark_dirty: bool) -> NodeId {
|
||||
let node_id = self.tree.create_node(node);
|
||||
let node = self.tree.get_mut(node_id).unwrap();
|
||||
node.node_data.node_id = node_id;
|
||||
@@ -263,7 +250,7 @@ impl<S: State + Send> RealDom<S> {
|
||||
for new in new_nodes {
|
||||
self.tree.insert_before(old_node_id, new);
|
||||
}
|
||||
self.tree.remove(old_node_id);
|
||||
self.remove(old_node_id);
|
||||
}
|
||||
ReplacePlaceholder { path, m } => {
|
||||
let new_nodes = self.stack.split_off(self.stack.len() - m);
|
||||
@@ -271,7 +258,7 @@ impl<S: State + Send> RealDom<S> {
|
||||
for new in new_nodes {
|
||||
self.tree.insert_before(old_node_id, new);
|
||||
}
|
||||
self.tree.remove(old_node_id);
|
||||
self.remove(old_node_id);
|
||||
}
|
||||
InsertAfter { id, m } => {
|
||||
let new_nodes = self.stack.split_off(self.stack.len() - m);
|
||||
@@ -295,29 +282,23 @@ impl<S: State + Send> RealDom<S> {
|
||||
} => {
|
||||
let node_id = self.element_to_node_id(id);
|
||||
let mut node = self.get_mut(node_id).unwrap();
|
||||
if let NodeTypeMut::Element(mut element) = node.node_type_mut() {
|
||||
element.set_attribute(
|
||||
OwnedAttributeDiscription {
|
||||
if let NodeTypeMut::Element(element) = &mut node.node_type_mut() {
|
||||
if let BorrowedAttributeValue::None = &value {
|
||||
element.remove_attributes(&OwnedAttributeDiscription {
|
||||
name: name.to_string(),
|
||||
namespace: ns.map(|s| s.to_string()),
|
||||
volatile: false,
|
||||
},
|
||||
crate::node::OwnedAttributeValue::Text(value.to_string()),
|
||||
);
|
||||
}
|
||||
}
|
||||
SetBoolAttribute { name, value, id } => {
|
||||
let node_id = self.element_to_node_id(id);
|
||||
let mut node = self.get_mut(node_id).unwrap();
|
||||
if let NodeTypeMut::Element(mut element) = node.node_type_mut() {
|
||||
element.set_attribute(
|
||||
OwnedAttributeDiscription {
|
||||
name: name.to_string(),
|
||||
namespace: None,
|
||||
volatile: false,
|
||||
},
|
||||
crate::node::OwnedAttributeValue::Bool(value),
|
||||
);
|
||||
});
|
||||
} else {
|
||||
element.set_attribute(
|
||||
OwnedAttributeDiscription {
|
||||
name: name.to_string(),
|
||||
namespace: ns.map(|s| s.to_string()),
|
||||
volatile: false,
|
||||
},
|
||||
OwnedAttributeValue::from(value),
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
SetText { value, id } => {
|
||||
@@ -357,7 +338,7 @@ impl<S: State + Send> RealDom<S> {
|
||||
}
|
||||
Remove { id } => {
|
||||
let node_id = self.element_to_node_id(id);
|
||||
self.tree.remove(node_id);
|
||||
self.remove(node_id);
|
||||
}
|
||||
PushRoot { id } => {
|
||||
let node_id = self.element_to_node_id(id);
|
||||
@@ -367,6 +348,68 @@ impl<S: State + Send> RealDom<S> {
|
||||
}
|
||||
}
|
||||
|
||||
/// 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, V>> {
|
||||
if let Some(nodes) = self.nodes_listening.get(event) {
|
||||
let mut listening: Vec<_> = nodes.iter().map(|id| self.get(*id).unwrap()).collect();
|
||||
listening.sort_by(|n1, n2| {
|
||||
(self.tree.height(n1.node_data.node_id))
|
||||
.cmp(&self.tree.height(n2.node_data.node_id))
|
||||
.reverse()
|
||||
});
|
||||
listening
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the number of nodes in the dom.
|
||||
pub fn size(&self) -> usize {
|
||||
// The dom has a root node, ignore it.
|
||||
self.tree.size() - 1
|
||||
}
|
||||
|
||||
/// Returns the id of the root node.
|
||||
pub fn root_id(&self) -> NodeId {
|
||||
self.tree.root()
|
||||
}
|
||||
|
||||
fn clone_node(&mut self, node_id: NodeId) -> NodeId {
|
||||
let node = self.tree.get(node_id).unwrap();
|
||||
let new_node = Node {
|
||||
state: node.state.clone(),
|
||||
node_data: node.node_data.clone(),
|
||||
};
|
||||
let new_id = self.create_node(new_node, true);
|
||||
|
||||
let self_ptr = self as *mut Self;
|
||||
for child in self.tree.children_ids(node_id).unwrap() {
|
||||
unsafe {
|
||||
// this is safe because no node has itself as a child
|
||||
let self_mut = &mut *self_ptr;
|
||||
let child_id = self_mut.clone_node(*child);
|
||||
self_mut.add_child(new_id, child_id);
|
||||
}
|
||||
}
|
||||
new_id
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, id: NodeId) -> Option<NodeMut<'_, V>> {
|
||||
self.tree.get_mut(id).map(|node| NodeMut {
|
||||
node: &mut node.node_data,
|
||||
dirty: NodeMask::new(),
|
||||
nodes_updated: &mut self.nodes_updated,
|
||||
})
|
||||
}
|
||||
|
||||
/// WARNING: This escapes the reactive system that the real dom uses. Any changes made with this method will not trigger updates in states when [RealDom::update_state] is called.
|
||||
pub fn get_mut_raw(&mut self, id: NodeId) -> Option<&mut Node<S, V>> {
|
||||
self.tree.get_mut(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: State<V> + Send, V: FromAnyValue + Send> RealDom<S, V> {
|
||||
/// 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(
|
||||
&mut self,
|
||||
@@ -421,76 +464,16 @@ impl<S: State + Send> RealDom<S> {
|
||||
nodes_updated,
|
||||
)
|
||||
}
|
||||
|
||||
/// 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>> {
|
||||
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| {
|
||||
(self.tree.height(n1.node_data.node_id))
|
||||
.cmp(&self.tree.height(n2.node_data.node_id))
|
||||
.reverse()
|
||||
});
|
||||
listening
|
||||
} else {
|
||||
Vec::new()
|
||||
}
|
||||
}
|
||||
|
||||
/// Return the number of nodes in the dom.
|
||||
pub fn size(&self) -> usize {
|
||||
// The dom has a root node, ignore it.
|
||||
self.tree.size() - 1
|
||||
}
|
||||
|
||||
/// Returns the id of the root node.
|
||||
pub fn root_id(&self) -> NodeId {
|
||||
self.tree.root()
|
||||
}
|
||||
|
||||
fn clone_node(&mut self, node_id: NodeId) -> NodeId {
|
||||
let node = self.tree.get(node_id).unwrap();
|
||||
let new_node = Node {
|
||||
state: node.state.clone(),
|
||||
node_data: node.node_data.clone(),
|
||||
};
|
||||
let new_id = self.create_node(new_node, true);
|
||||
|
||||
let self_ptr = self as *mut Self;
|
||||
for child in self.tree.children_ids(node_id).unwrap() {
|
||||
unsafe {
|
||||
// this is safe because no node has itself as a child
|
||||
let self_mut = &mut *self_ptr;
|
||||
let child_id = self_mut.clone_node(*child);
|
||||
self_mut.add_child(new_id, child_id);
|
||||
}
|
||||
}
|
||||
new_id
|
||||
}
|
||||
|
||||
pub fn get_mut(&mut self, id: NodeId) -> Option<NodeMut<'_>> {
|
||||
self.tree.get_mut(id).map(|node| NodeMut {
|
||||
node: &mut node.node_data,
|
||||
dirty: NodeMask::new(),
|
||||
nodes_updated: &mut self.nodes_updated,
|
||||
})
|
||||
}
|
||||
|
||||
/// WARNING: This escapes the reactive system that the real dom uses. Any changes made with this method will not trigger updates in states when [RealDom::update_state] is called.
|
||||
pub fn get_mut_raw(&mut self, id: NodeId) -> Option<&mut Node<S>> {
|
||||
self.tree.get_mut(id)
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: State + Send> TreeView<Node<S>> for RealDom<S> {
|
||||
type Iterator<'a> = <Tree<Node<S>> as TreeView<Node<S>>>::Iterator<'a>;
|
||||
impl<S: State<V>, V: FromAnyValue> TreeView<Node<S, V>> for RealDom<S, V> {
|
||||
type Iterator<'a> = <Tree<Node<S, V>> as TreeView<Node<S, V>>>::Iterator<'a>;
|
||||
|
||||
fn root(&self) -> NodeId {
|
||||
self.tree.root()
|
||||
}
|
||||
|
||||
fn get(&self, id: NodeId) -> Option<&Node<S>> {
|
||||
fn get(&self, id: NodeId) -> Option<&Node<S, V>> {
|
||||
self.tree.get(id)
|
||||
}
|
||||
|
||||
@@ -502,7 +485,7 @@ impl<S: State + Send> TreeView<Node<S>> for RealDom<S> {
|
||||
self.tree.children_ids(id)
|
||||
}
|
||||
|
||||
fn parent(&self, id: NodeId) -> Option<&Node<S>> {
|
||||
fn parent(&self, id: NodeId) -> Option<&Node<S, V>> {
|
||||
self.tree.parent(id)
|
||||
}
|
||||
|
||||
@@ -519,8 +502,8 @@ impl<S: State + Send> TreeView<Node<S>> for RealDom<S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: State + Send> TreeLike<Node<S>> for RealDom<S> {
|
||||
fn create_node(&mut self, node: Node<S>) -> NodeId {
|
||||
impl<S: State<V>, V: FromAnyValue> TreeLike<Node<S, V>> for RealDom<S, V> {
|
||||
fn create_node(&mut self, node: Node<S, V>) -> NodeId {
|
||||
let id = self.tree.create_node(node);
|
||||
self.tree.get_mut(id).unwrap().node_data.node_id = id;
|
||||
self.nodes_created.insert(id);
|
||||
@@ -535,14 +518,14 @@ impl<S: State + Send> TreeLike<Node<S>> for RealDom<S> {
|
||||
self.tree.add_child(parent, child);
|
||||
}
|
||||
|
||||
fn remove(&mut self, id: NodeId) -> Option<Node<S>> {
|
||||
fn remove(&mut self, id: NodeId) -> Option<Node<S, V>> {
|
||||
if let Some(parent_id) = self.tree.parent_id(id) {
|
||||
self.mark_child_changed(parent_id);
|
||||
}
|
||||
self.tree.remove(id)
|
||||
}
|
||||
|
||||
fn remove_all_children(&mut self, id: NodeId) -> Vec<Node<S>> {
|
||||
fn remove_all_children(&mut self, id: NodeId) -> Vec<Node<S, V>> {
|
||||
self.mark_child_changed(id);
|
||||
self.tree.remove_all_children(id)
|
||||
}
|
||||
@@ -572,46 +555,46 @@ impl<S: State + Send> TreeLike<Node<S>> for RealDom<S> {
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: State + Send> Index<ElementId> for RealDom<S> {
|
||||
type Output = Node<S>;
|
||||
impl<S: State<V> + Send, 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 + Send> Index<NodeId> for RealDom<S> {
|
||||
type Output = Node<S>;
|
||||
impl<S: State<V> + Send, V: FromAnyValue> Index<NodeId> for RealDom<S, V> {
|
||||
type Output = Node<S, V>;
|
||||
|
||||
fn index(&self, idx: NodeId) -> &Self::Output {
|
||||
self.tree.get(idx).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl<S: State + Send> IndexMut<ElementId> for RealDom<S> {
|
||||
impl<S: State<V> + Send, 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 + Send> IndexMut<NodeId> for RealDom<S> {
|
||||
impl<S: State<V> + Send, V: FromAnyValue> IndexMut<NodeId> for RealDom<S, V> {
|
||||
fn index_mut(&mut self, idx: NodeId) -> &mut Self::Output {
|
||||
self.tree.get_mut(idx).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
pub struct NodeMut<'a> {
|
||||
node: &'a mut NodeData,
|
||||
pub struct NodeMut<'a, V: FromAnyValue = ()> {
|
||||
node: &'a mut NodeData<V>,
|
||||
dirty: NodeMask,
|
||||
nodes_updated: &'a mut FxHashMap<NodeId, NodeMask>,
|
||||
}
|
||||
|
||||
impl<'a> NodeMut<'a> {
|
||||
pub fn node_type(&self) -> &NodeType {
|
||||
impl<'a, V: FromAnyValue> NodeMut<'a, V> {
|
||||
pub fn node_type(&self) -> &NodeType<V> {
|
||||
&self.node.node_type
|
||||
}
|
||||
|
||||
pub fn node_type_mut(&mut self) -> NodeTypeMut<'_> {
|
||||
pub fn node_type_mut(&mut self) -> NodeTypeMut<'_, V> {
|
||||
match &mut self.node.node_type {
|
||||
NodeType::Element(element) => NodeTypeMut::Element(ElementNodeMut {
|
||||
element,
|
||||
@@ -626,7 +609,7 @@ impl<'a> NodeMut<'a> {
|
||||
}
|
||||
}
|
||||
|
||||
impl Drop for NodeMut<'_> {
|
||||
impl<V: FromAnyValue> Drop for NodeMut<'_, V> {
|
||||
fn drop(&mut self) {
|
||||
let node_id = self.node.node_id;
|
||||
let mask = std::mem::take(&mut self.dirty);
|
||||
@@ -638,18 +621,18 @@ impl Drop for NodeMut<'_> {
|
||||
}
|
||||
}
|
||||
|
||||
pub enum NodeTypeMut<'a> {
|
||||
Element(ElementNodeMut<'a>),
|
||||
pub enum NodeTypeMut<'a, V: FromAnyValue = ()> {
|
||||
Element(ElementNodeMut<'a, V>),
|
||||
Text(&'a mut String),
|
||||
Placeholder,
|
||||
}
|
||||
|
||||
pub struct ElementNodeMut<'a> {
|
||||
element: &'a mut ElementNode,
|
||||
pub struct ElementNodeMut<'a, V: FromAnyValue = ()> {
|
||||
element: &'a mut ElementNode<V>,
|
||||
dirty: &'a mut NodeMask,
|
||||
}
|
||||
|
||||
impl ElementNodeMut<'_> {
|
||||
impl<V: FromAnyValue> ElementNodeMut<'_, V> {
|
||||
pub fn tag(&self) -> &str {
|
||||
&self.element.tag
|
||||
}
|
||||
@@ -668,13 +651,13 @@ impl ElementNodeMut<'_> {
|
||||
&mut self.element.namespace
|
||||
}
|
||||
|
||||
pub fn attributes(&self) -> &FxHashMap<OwnedAttributeDiscription, OwnedAttributeValue> {
|
||||
pub fn attributes(&self) -> &FxHashMap<OwnedAttributeDiscription, OwnedAttributeValue<V>> {
|
||||
&self.element.attributes
|
||||
}
|
||||
|
||||
pub fn attributes_mut(
|
||||
&mut self,
|
||||
) -> &mut FxHashMap<OwnedAttributeDiscription, OwnedAttributeValue> {
|
||||
) -> &mut FxHashMap<OwnedAttributeDiscription, OwnedAttributeValue<V>> {
|
||||
self.dirty.add_attributes(AttributeMask::All);
|
||||
&mut self.element.attributes
|
||||
}
|
||||
@@ -682,16 +665,24 @@ impl ElementNodeMut<'_> {
|
||||
pub fn set_attribute(
|
||||
&mut self,
|
||||
name: OwnedAttributeDiscription,
|
||||
value: OwnedAttributeValue,
|
||||
) -> Option<OwnedAttributeValue> {
|
||||
value: OwnedAttributeValue<V>,
|
||||
) -> Option<OwnedAttributeValue<V>> {
|
||||
self.dirty.add_attributes(AttributeMask::single(&name.name));
|
||||
self.element.attributes.insert(name, value)
|
||||
}
|
||||
|
||||
pub fn remove_attributes(
|
||||
&mut self,
|
||||
name: &OwnedAttributeDiscription,
|
||||
) -> Option<OwnedAttributeValue<V>> {
|
||||
self.dirty.add_attributes(AttributeMask::single(&name.name));
|
||||
self.element.attributes.remove(name)
|
||||
}
|
||||
|
||||
pub fn get_attribute_mut(
|
||||
&mut self,
|
||||
name: &OwnedAttributeDiscription,
|
||||
) -> Option<&mut OwnedAttributeValue> {
|
||||
) -> Option<&mut OwnedAttributeValue<V>> {
|
||||
self.dirty.add_attributes(AttributeMask::single(&name.name));
|
||||
self.element.attributes.get_mut(name)
|
||||
}
|
||||
|
||||
@@ -1,4 +1,7 @@
|
||||
use crate::passes::{AnyMapLike, TypeErasedPass};
|
||||
use crate::{
|
||||
node::FromAnyValue,
|
||||
passes::{AnyMapLike, TypeErasedPass},
|
||||
};
|
||||
use std::cmp::Ordering;
|
||||
|
||||
/// Join two sorted iterators
|
||||
@@ -37,7 +40,7 @@ pub(crate) fn union_ordered_iter<'a>(
|
||||
}
|
||||
|
||||
/// Do not implement this trait. It is only meant to be derived and used through [crate::real_dom::RealDom].
|
||||
pub trait State: Default + Clone + AnyMapLike + 'static {
|
||||
pub trait State<V: FromAnyValue = ()>: Default + Clone + AnyMapLike + 'static {
|
||||
#[doc(hidden)]
|
||||
fn create_passes() -> Box<[TypeErasedPass<Self>]>;
|
||||
fn create_passes() -> Box<[TypeErasedPass<Self, V>]>;
|
||||
}
|
||||
@@ -1,4 +1,3 @@
|
||||
use slab::Slab;
|
||||
use std::collections::VecDeque;
|
||||
use std::marker::PhantomData;
|
||||
|
||||
@@ -212,57 +211,38 @@ pub trait TreeLike<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: TreeViewMut<T> + 'a> {
|
||||
tree: *mut Tr,
|
||||
children_ids: &'a [NodeId],
|
||||
index: usize,
|
||||
node_type: PhantomData<T>,
|
||||
pub struct ChildNodeIteratorMut<'a, T> {
|
||||
children: Vec<&'a mut Node<T>>,
|
||||
}
|
||||
|
||||
unsafe impl<'a, T, Tr: TreeViewMut<T> + 'a> Sync for ChildNodeIteratorMut<'a, T, Tr> {}
|
||||
|
||||
impl<'a, T, Tr: TreeViewMut<T>> ChildNodeIteratorMut<'a, T, Tr> {
|
||||
fn tree_mut(&mut self) -> &'a mut Tr {
|
||||
unsafe { &mut *self.tree }
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, T: 'a, Tr: TreeViewMut<T>> Iterator for ChildNodeIteratorMut<'a, T, Tr> {
|
||||
impl<'a, T> 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.children.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 Iterator<'a> = ChildNodeIterator<'a, T> where T: 'a;
|
||||
|
||||
fn root(&self) -> NodeId {
|
||||
self.root
|
||||
@@ -274,8 +254,8 @@ 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,
|
||||
})
|
||||
@@ -310,7 +290,7 @@ impl<T> TreeView<T> for Tree<T> {
|
||||
}
|
||||
|
||||
impl<T> TreeViewMut<T> for Tree<T> {
|
||||
type IteratorMut<'a> = ChildNodeIteratorMut<'a, T, Tree<T>> where T: 'a;
|
||||
type IteratorMut<'a> = ChildNodeIteratorMut<'a, T> where T: 'a;
|
||||
|
||||
fn get_mut(&mut self, id: NodeId) -> Option<&mut T> {
|
||||
self.nodes.get_mut(id.0).map(|node| &mut node.value)
|
||||
@@ -331,16 +311,19 @@ impl<T> TreeViewMut<T> for Tree<T> {
|
||||
}
|
||||
|
||||
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();
|
||||
let children_ids = children_ids.to_vec();
|
||||
Some(ChildNodeIteratorMut {
|
||||
children: unsafe {
|
||||
self.nodes
|
||||
.get_many_mut_unchecked(children_ids.into_iter().rev().map(|id| id.0))
|
||||
.unwrap()
|
||||
},
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
@@ -373,6 +356,29 @@ impl<T> TreeViewMut<T> for Tree<T> {
|
||||
(node, parent, children)
|
||||
})
|
||||
}
|
||||
|
||||
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 { children: borrowed }))
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<T> TreeLike<T> for Tree<T> {
|
||||
@@ -584,3 +590,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,5 +1,5 @@
|
||||
use crate::{
|
||||
node::{ElementNode, NodeType},
|
||||
node::{ElementNode, FromAnyValue, NodeType},
|
||||
passes::{AnyMapLike, TypeErasedPass},
|
||||
real_dom::RealDom,
|
||||
state::State,
|
||||
@@ -74,7 +74,11 @@ impl PersistantElementIter {
|
||||
|
||||
/// remove stale element refreneces
|
||||
/// returns true if the focused element is removed
|
||||
pub fn prune<S: State + Send>(&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
|
||||
@@ -100,7 +104,7 @@ impl PersistantElementIter {
|
||||
// if a child is removed or inserted before or at the current element, update the child index
|
||||
for (el_id, child_idx) in self.stack.iter_mut() {
|
||||
if let NodePosition::InChild(child_idx) = child_idx {
|
||||
if let Some(children) = &rdom.tree.children_ids(*el_id) {
|
||||
if let Some(children) = &rdom.children_ids(*el_id) {
|
||||
for m in &mutations.edits {
|
||||
match m {
|
||||
Mutation::Remove { id } => {
|
||||
@@ -131,7 +135,7 @@ impl PersistantElementIter {
|
||||
}
|
||||
|
||||
/// get the next element
|
||||
pub fn next<S: State + Send>(&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);
|
||||
@@ -139,10 +143,10 @@ impl PersistantElementIter {
|
||||
ElementProduced::Looped(id)
|
||||
} else {
|
||||
let (last, old_child_idx) = self.stack.last_mut().unwrap();
|
||||
let node = &rdom[*last];
|
||||
let node = rdom.get(*last).unwrap();
|
||||
match &node.node_data.node_type {
|
||||
NodeType::Element(ElementNode { .. }) => {
|
||||
let children = rdom.tree.children_ids(*last).unwrap();
|
||||
let children = rdom.children_ids(*last).unwrap();
|
||||
*old_child_idx = old_child_idx.map(|i| i + 1);
|
||||
// if we have children, go to the next child
|
||||
let child_idx = old_child_idx.get_or_insert(0);
|
||||
@@ -151,7 +155,8 @@ impl PersistantElementIter {
|
||||
self.next(rdom)
|
||||
} else {
|
||||
let id = children[child_idx];
|
||||
if let NodeType::Element(ElementNode { .. }) = &rdom[id].node_data.node_type
|
||||
if let NodeType::Element(ElementNode { .. }) =
|
||||
rdom.get(id).unwrap().node_data.node_type
|
||||
{
|
||||
self.stack.push((id, NodePosition::AtNode));
|
||||
}
|
||||
@@ -168,16 +173,19 @@ impl PersistantElementIter {
|
||||
}
|
||||
|
||||
/// get the previous element
|
||||
pub fn prev<S: State + Send>(&mut self, rdom: &RealDom<S>) -> ElementProduced {
|
||||
pub fn prev<S: State<V> + Send, V: FromAnyValue>(
|
||||
&mut self,
|
||||
rdom: &RealDom<S, V>,
|
||||
) -> ElementProduced {
|
||||
// recursively add the last child element to the stack
|
||||
fn push_back<S: State + Send>(
|
||||
fn push_back<S: State<V> + Send, V: FromAnyValue>(
|
||||
stack: &mut smallvec::SmallVec<[(NodeId, NodePosition); 5]>,
|
||||
new_node: NodeId,
|
||||
rdom: &RealDom<S>,
|
||||
rdom: &RealDom<S, V>,
|
||||
) -> NodeId {
|
||||
match &rdom[new_node].node_data.node_type {
|
||||
NodeType::Element(ElementNode { .. }) => {
|
||||
let children = rdom.tree.children_ids(new_node).unwrap();
|
||||
let children = rdom.children_ids(new_node).unwrap();
|
||||
if children.is_empty() {
|
||||
new_node
|
||||
} else {
|
||||
@@ -196,7 +204,7 @@ impl PersistantElementIter {
|
||||
let node = &rdom[*last];
|
||||
match &node.node_data.node_type {
|
||||
NodeType::Element(ElementNode { .. }) => {
|
||||
let children = rdom.tree.children_ids(*last).unwrap();
|
||||
let children = rdom.children_ids(*last).unwrap();
|
||||
// if we have children, go to the next child
|
||||
if let NodePosition::InChild(0) = old_child_idx {
|
||||
ElementProduced::Progressed(self.pop())
|
||||
|
||||
@@ -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"
|
||||
@@ -246,8 +246,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 +256,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.
@@ -93,7 +93,7 @@ impl Pass 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 = "." }
|
||||
+26
-7
@@ -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),
|
||||
|
||||
@@ -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