diff --git a/packages/core/src/arbitrary_value.rs b/packages/core/src/arbitrary_value.rs new file mode 100644 index 000000000..80acc0ca6 --- /dev/null +++ b/packages/core/src/arbitrary_value.rs @@ -0,0 +1,265 @@ +use std::fmt::Formatter; + +// trying to keep values at 3 bytes +#[cfg_attr(feature = "serialize", derive(serde::Serialize, serde::Deserialize))] +#[derive(Clone, Debug, PartialEq)] +pub enum AttributeValue<'a> { + Text(&'a str), + Float32(f32), + Float64(f64), + Int32(i32), + Int64(i64), + Uint32(u32), + Uint64(u64), + Bool(bool), + ColorRGB(u8, u8, u8), + ColorRGBA(u8, u8, u8, u8), + ColorHex(u32), + ColorHexAlpha(u32, u8), + Vec3Float(f32, f32, f32), + Vec3Int(i32, i32, i32), + Vec3Uint(u32, u32, u32), + Vec4Float(f32, f32, f32, f32), + Vec4Int(i32, i32, i32, i32), + Vec4Uint(u32, u32, u32, u32), + Bytes(&'a [u8]), + Any(ArbitraryAttributeValue<'a>), +} + +impl<'a> AttributeValue<'a> { + pub fn is_truthy(&self) -> bool { + match self { + AttributeValue::Text(t) => *t == "true", + AttributeValue::Bool(t) => *t, + _ => false, + } + } + + pub fn is_falsy(&self) -> bool { + match self { + AttributeValue::Text(t) => *t == "false", + AttributeValue::Bool(t) => !(*t), + _ => false, + } + } +} + +impl<'a> std::fmt::Display for AttributeValue<'a> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + match self { + AttributeValue::Text(a) => write!(f, "{}", a), + AttributeValue::Float32(a) => write!(f, "{}", a), + AttributeValue::Float64(a) => write!(f, "{}", a), + AttributeValue::Int32(a) => write!(f, "{}", a), + AttributeValue::Int64(a) => write!(f, "{}", a), + AttributeValue::Uint32(a) => write!(f, "{}", a), + AttributeValue::Uint64(a) => write!(f, "{}", a), + AttributeValue::Bool(a) => write!(f, "{}", a), + AttributeValue::ColorRGB(_, _, _) => todo!(), + AttributeValue::ColorRGBA(_, _, _, _) => todo!(), + AttributeValue::ColorHex(_) => todo!(), + AttributeValue::ColorHexAlpha(_, _) => todo!(), + AttributeValue::Vec3Float(_, _, _) => todo!(), + AttributeValue::Vec3Int(_, _, _) => todo!(), + AttributeValue::Vec3Uint(_, _, _) => todo!(), + AttributeValue::Vec4Float(_, _, _, _) => todo!(), + AttributeValue::Vec4Int(_, _, _, _) => todo!(), + AttributeValue::Vec4Uint(_, _, _, _) => todo!(), + AttributeValue::Bytes(_) => todo!(), + AttributeValue::Any(_) => todo!(), + } + } +} + +#[derive(Clone, Copy)] +pub struct ArbitraryAttributeValue<'a> { + pub value: &'a dyn std::any::Any, + pub cmp: fn(&'a dyn std::any::Any, &'a dyn std::any::Any) -> bool, +} + +impl PartialEq for ArbitraryAttributeValue<'_> { + fn eq(&self, other: &Self) -> bool { + (self.cmp)(self.value, other.value) + } +} + +impl std::fmt::Debug for ArbitraryAttributeValue<'_> { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ArbitraryAttributeValue").finish() + } +} + +#[cfg(feature = "serialize")] +impl<'a> serde::Serialize for ArbitraryAttributeValue<'a> { + fn serialize(&self, _serializer: S) -> Result + where + S: serde::Serializer, + { + panic!("ArbitraryAttributeValue should not be serialized") + } +} +#[cfg(feature = "serialize")] +impl<'de, 'a> serde::Deserialize<'de> for &'a ArbitraryAttributeValue<'a> { + fn deserialize(_deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + panic!("ArbitraryAttributeValue is not deserializable!") + } +} +#[cfg(feature = "serialize")] +impl<'de, 'a> serde::Deserialize<'de> for ArbitraryAttributeValue<'a> { + fn deserialize(_deserializer: D) -> Result + where + D: serde::Deserializer<'de>, + { + panic!("ArbitraryAttributeValue is not deserializable!") + } +} + +impl<'a> AttributeValue<'a> { + pub fn as_text(&self) -> Option<&'a str> { + match self { + AttributeValue::Text(s) => Some(s), + _ => None, + } + } + + pub fn as_float32(&self) -> Option { + match self { + AttributeValue::Float32(f) => Some(*f), + _ => None, + } + } + + pub fn as_float64(&self) -> Option { + match self { + AttributeValue::Float64(f) => Some(*f), + _ => None, + } + } + + pub fn as_int32(&self) -> Option { + match self { + AttributeValue::Int32(i) => Some(*i), + _ => None, + } + } + + pub fn as_int64(&self) -> Option { + match self { + AttributeValue::Int64(i) => Some(*i), + _ => None, + } + } + + pub fn as_uint32(&self) -> Option { + match self { + AttributeValue::Uint32(i) => Some(*i), + _ => None, + } + } + + pub fn as_uint64(&self) -> Option { + match self { + AttributeValue::Uint64(i) => Some(*i), + _ => None, + } + } + + pub fn as_bool(&self) -> Option { + match self { + AttributeValue::Bool(b) => Some(*b), + _ => None, + } + } + + pub fn as_color_rgb(&self) -> Option<(u8, u8, u8)> { + match self { + AttributeValue::ColorRGB(r, g, b) => Some((*r, *g, *b)), + _ => None, + } + } + + pub fn as_color_rgba(&self) -> Option<(u8, u8, u8, u8)> { + match self { + AttributeValue::ColorRGBA(r, g, b, a) => Some((*r, *g, *b, *a)), + _ => None, + } + } + + pub fn as_color_hex(&self) -> Option { + match self { + AttributeValue::ColorHex(c) => Some(*c), + _ => None, + } + } + + pub fn as_color_hex_alpha(&self) -> Option<(u32, u8)> { + match self { + AttributeValue::ColorHexAlpha(c, a) => Some((*c, *a)), + _ => None, + } + } + + pub fn as_vec3_float(&self) -> Option<(f32, f32, f32)> { + match self { + AttributeValue::Vec3Float(x, y, z) => Some((*x, *y, *z)), + _ => None, + } + } + + pub fn as_vec3_int(&self) -> Option<(i32, i32, i32)> { + match self { + AttributeValue::Vec3Int(x, y, z) => Some((*x, *y, *z)), + _ => None, + } + } + + pub fn as_vec3_uint(&self) -> Option<(u32, u32, u32)> { + match self { + AttributeValue::Vec3Uint(x, y, z) => Some((*x, *y, *z)), + _ => None, + } + } + + pub fn as_vec4_float(&self) -> Option<(f32, f32, f32, f32)> { + match self { + AttributeValue::Vec4Float(x, y, z, w) => Some((*x, *y, *z, *w)), + _ => None, + } + } + + pub fn as_vec4_int(&self) -> Option<(i32, i32, i32, i32)> { + match self { + AttributeValue::Vec4Int(x, y, z, w) => Some((*x, *y, *z, *w)), + _ => None, + } + } + + pub fn as_vec4_uint(&self) -> Option<(u32, u32, u32, u32)> { + match self { + AttributeValue::Vec4Uint(x, y, z, w) => Some((*x, *y, *z, *w)), + _ => None, + } + } + + pub fn as_bytes(&self) -> Option<&[u8]> { + match self { + AttributeValue::Bytes(b) => Some(b), + _ => None, + } + } + + pub fn as_any(&self) -> Option<&'a ArbitraryAttributeValue> { + match self { + AttributeValue::Any(a) => Some(a), + _ => None, + } + } +} + +#[test] +fn test_attribute_value_size() { + assert_eq!(std::mem::size_of::>(), 24); +} diff --git a/packages/core/src/lib.rs b/packages/core/src/lib.rs index f31e2e328..708df1020 100644 --- a/packages/core/src/lib.rs +++ b/packages/core/src/lib.rs @@ -2,6 +2,7 @@ #![doc = include_str!("../README.md")] #![deny(missing_docs)] +pub(crate) mod arbitrary_value; pub(crate) mod diff; pub(crate) mod events; pub(crate) mod lazynodes; @@ -13,6 +14,7 @@ pub(crate) mod util; pub(crate) mod virtual_dom; pub(crate) mod innerlude { + pub use crate::arbitrary_value::*; pub use crate::events::*; pub use crate::lazynodes::*; pub use crate::mutations::*; diff --git a/packages/core/src/mutations.rs b/packages/core/src/mutations.rs index 2d3115ce3..4218e613f 100644 --- a/packages/core/src/mutations.rs +++ b/packages/core/src/mutations.rs @@ -167,8 +167,9 @@ pub enum DomEdit<'bump> { field: &'static str, /// The value of the attribute. - value: &'bump str, + value: AttributeValue<'bump>, + // value: &'bump str, /// The (optional) namespace of the attribute. /// For instance, "style" is in the "style" namespace. ns: Option<&'bump str>, @@ -286,7 +287,7 @@ impl<'a> Mutations<'a> { self.edits.push(SetText { text, root }); } - pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute, root: u64) { + pub(crate) fn set_attribute(&mut self, attribute: &'a Attribute<'a>, root: u64) { let Attribute { name, value, @@ -296,7 +297,7 @@ impl<'a> Mutations<'a> { self.edits.push(SetAttribute { field: name, - value, + value: value.clone(), ns: *namespace, root, }); diff --git a/packages/core/src/nodes.rs b/packages/core/src/nodes.rs index 0d3359a8c..086c6b20d 100644 --- a/packages/core/src/nodes.rs +++ b/packages/core/src/nodes.rs @@ -4,7 +4,7 @@ //! cheap and *very* fast to construct - building a full tree should be quick. use crate::{ - innerlude::{ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState}, + innerlude::{ComponentPtr, Element, Properties, Scope, ScopeId, ScopeState, AttributeValue}, lazynodes::LazyNodes, AnyEvent, Component, }; @@ -339,7 +339,7 @@ pub struct Attribute<'a> { pub name: &'static str, /// The value of the attribute. - pub value: &'a str, + pub value: AttributeValue<'a>, /// An indication if this attribute can be ignored during diffing /// @@ -357,6 +357,7 @@ pub struct Attribute<'a> { pub namespace: Option<&'static str>, } + /// An event listener. /// IE onclick, onkeydown, etc pub struct Listener<'bump> { @@ -610,6 +611,24 @@ impl<'a> NodeFactory<'a> { is_volatile: bool, ) -> Attribute<'a> { let (value, is_static) = self.raw_text(val); + Attribute { + name, + value: AttributeValue::Text(value), + is_static, + namespace, + is_volatile, + } + } + + /// Create a new [`Attribute`] using non-arguments + pub fn custom_attr( + &self, + name: &'static str, + value: AttributeValue<'a>, + namespace: Option<&'static str>, + is_volatile: bool, + is_static: bool, + ) -> Attribute<'a> { Attribute { name, value, diff --git a/packages/ssr/src/lib.rs b/packages/ssr/src/lib.rs index b473e7fa8..9eb204f78 100644 --- a/packages/ssr/src/lib.rs +++ b/packages/ssr/src/lib.rs @@ -186,7 +186,9 @@ impl<'a> TextRenderer<'a, '_> { while let Some(attr) = attr_iter.next() { match attr.namespace { None => match attr.name { - "dangerous_inner_html" => inner_html = Some(attr.value), + "dangerous_inner_html" => { + inner_html = Some(attr.value.as_text().unwrap()) + } "allowfullscreen" | "allowpaymentrequest" | "async" @@ -213,7 +215,7 @@ impl<'a> TextRenderer<'a, '_> { | "reversed" | "selected" | "truespeed" => { - if attr.value != "false" { + if attr.value.is_truthy() { write!(f, " {}=\"{}\"", attr.name, attr.value)? } } diff --git a/packages/web/Cargo.toml b/packages/web/Cargo.toml index d9b66b7a5..387349ec5 100644 --- a/packages/web/Cargo.toml +++ b/packages/web/Cargo.toml @@ -14,7 +14,7 @@ keywords = ["dom", "ui", "gui", "react", "wasm"] dioxus-core = { path = "../core", version = "^0.2.1" } dioxus-html = { path = "../html", version = "^0.2.1", features = ["wasm-bind"] } dioxus-interpreter-js = { path = "../interpreter", version = "^0.2.1", features = [ - "web", + "web" ] } js-sys = "0.3.56" diff --git a/packages/web/src/dom.rs b/packages/web/src/dom.rs index 84101423b..94d66460f 100644 --- a/packages/web/src/dom.rs +++ b/packages/web/src/dom.rs @@ -146,7 +146,7 @@ impl WebsysDom { value, ns, } => { - let value = serde_wasm_bindgen::to_value(value).unwrap(); + let value = serde_wasm_bindgen::to_value(&value).unwrap(); self.interpreter.SetAttribute(root, field, value, ns) } }