diff --git a/packages/native-core-macro/src/lib.rs b/packages/native-core-macro/src/lib.rs index 43fed66ac..fd25c3868 100644 --- a/packages/native-core-macro/src/lib.rs +++ b/packages/native-core-macro/src/lib.rs @@ -305,7 +305,7 @@ pub fn partial_derive_state(_: TokenStream, input: TokenStream) -> TokenStream { let create_fn = (!has_create_fn).then(|| { quote! { fn create<'a>( - node_view: dioxus_native_core::prelude::NodeView<()>, + node_view: dioxus_native_core::prelude::NodeView # trait_generics, node: ::ElementBorrowed<'a>, parent: Option<::ElementBorrowed<'a>>, children: Vec<::ElementBorrowed<'a>>, diff --git a/packages/native-core/examples/custom_attr.rs b/packages/native-core/examples/custom_attr.rs new file mode 100644 index 000000000..5a1fcfb8c --- /dev/null +++ b/packages/native-core/examples/custom_attr.rs @@ -0,0 +1,231 @@ +use dioxus_native_core::exports::shipyard::Component; +use dioxus_native_core::node_ref::*; +use dioxus_native_core::prelude::*; +use dioxus_native_core::real_dom::NodeTypeMut; +use dioxus_native_core_macro::partial_derive_state; + +struct FontSize(f64); + +// All states need to derive Component +#[derive(Default, Debug, Copy, Clone, Component)] +struct Size(f64, f64); + +#[derive(Default, Debug, Copy, Clone, Component)] +struct X; + +impl FromAnyValue for X { + fn from_any_value(_: &dyn std::any::Any) -> Self { + X + } +} + +/// Derive some of the boilerplate for the State implementation +#[partial_derive_state] +impl State for Size { + type ParentDependencies = (); + + // The size of the current node depends on the size of its children + type ChildDependencies = (Self,); + + type NodeDependencies = (); + + // Size only cares about the width, height, and text parts of the current node + const NODE_MASK: NodeMaskBuilder<'static> = NodeMaskBuilder::new() + // Get access to the width and height attributes + .with_attrs(AttributeMaskBuilder::Some(&["width", "height"])) + // Get access to the text of the node + .with_text(); + + fn update<'a>( + &mut self, + node_view: NodeView, + _node: ::ElementBorrowed<'a>, + _parent: Option<::ElementBorrowed<'a>>, + children: Vec<::ElementBorrowed<'a>>, + context: &SendAnyMap, + ) -> bool { + let font_size = context.get::().unwrap().0; + let mut width; + let mut height; + if let Some(text) = node_view.text() { + // if the node has text, use the text to size our object + width = text.len() as f64 * font_size; + height = font_size; + } else { + // otherwise, the size is the maximum size of the children + width = children + .iter() + .map(|(item,)| item.0) + .reduce(|accum, item| if accum >= item { accum } else { item }) + .unwrap_or(0.0); + + height = children + .iter() + .map(|(item,)| item.1) + .reduce(|accum, item| if accum >= item { accum } else { item }) + .unwrap_or(0.0); + } + // if the node contains a width or height attribute it overrides the other size + for a in node_view.attributes().into_iter().flatten() { + match &*a.attribute.name { + "width" => width = a.value.as_float().unwrap(), + "height" => height = a.value.as_float().unwrap(), + // because Size only depends on the width and height, no other attributes will be passed to the member + _ => panic!(), + } + } + // to determine what other parts of the dom need to be updated we return a boolean that marks if this member changed + let changed = (width != self.0) || (height != self.1); + *self = Self(width, height); + changed + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Default, Component)] +struct TextColor { + r: u8, + g: u8, + b: u8, +} + +#[partial_derive_state] +impl State for TextColor { + // TextColor depends on the TextColor part of the parent + type ParentDependencies = (Self,); + + type ChildDependencies = (); + + type NodeDependencies = (); + + // TextColor only cares about the color attribute of the current node + const NODE_MASK: NodeMaskBuilder<'static> = + // Get access to the color attribute + NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["color"])); + + fn update<'a>( + &mut self, + node_view: NodeView, + _node: ::ElementBorrowed<'a>, + parent: Option<::ElementBorrowed<'a>>, + _children: Vec<::ElementBorrowed<'a>>, + _context: &SendAnyMap, + ) -> bool { + // TextColor only depends on the color tag, so getting the first tag is equivilent to looking through all tags + let new = match node_view + .attributes() + .and_then(|mut attrs| attrs.next()) + .and_then(|attr| attr.value.as_text()) + { + // if there is a color tag, translate it + Some("red") => TextColor { r: 255, g: 0, b: 0 }, + Some("green") => TextColor { r: 0, g: 255, b: 0 }, + Some("blue") => TextColor { r: 0, g: 0, b: 255 }, + Some(color) => panic!("unknown color {color}"), + // otherwise check if the node has a parent and inherit that color + None => match parent { + Some((parent,)) => *parent, + None => Self::default(), + }, + }; + // check if the member has changed + let changed = new != *self; + *self = new; + changed + } +} + +#[derive(Debug, Clone, Copy, PartialEq, Default, Component)] +struct Border(bool); + +#[partial_derive_state] +impl State for Border { + // TextColor depends on the TextColor part of the parent + type ParentDependencies = (Self,); + + type ChildDependencies = (); + + type NodeDependencies = (); + + // Border does not depended on any other member in the current node + const NODE_MASK: NodeMaskBuilder<'static> = + // Get access to the border attribute + NodeMaskBuilder::new().with_attrs(AttributeMaskBuilder::Some(&["border"])); + + fn update<'a>( + &mut self, + node_view: NodeView, + _node: ::ElementBorrowed<'a>, + _parent: Option<::ElementBorrowed<'a>>, + _children: Vec<::ElementBorrowed<'a>>, + _context: &SendAnyMap, + ) -> bool { + // check if the node contians a border attribute + let new = Self( + node_view + .attributes() + .and_then(|mut attrs| attrs.next().map(|a| a.attribute.name == "border")) + .is_some(), + ); + // check if the member has changed + let changed = new != *self; + *self = new; + changed + } +} + +fn main() -> Result<(), Box> { + let mut rdom: RealDom = RealDom::new([ + Border::to_type_erased(), + TextColor::to_type_erased(), + Size::to_type_erased(), + ]); + + let mut count = 0; + + // intial render + let text_id = rdom.create_node(format!("Count: {count}")).id(); + let mut root = rdom.get_mut(rdom.root_id()).unwrap(); + // set the color to red + if let NodeTypeMut::Element(mut element) = root.node_type_mut() { + element.set_attribute(("color", "style"), "red".to_string()); + } + root.add_child(text_id); + + let mut ctx = SendAnyMap::new(); + // set the font size to 3.3 + ctx.insert(FontSize(3.3)); + // update the State for nodes in the real_dom tree + let _to_rerender = rdom.update_state(ctx); + + // we need to run the vdom in a async runtime + tokio::runtime::Builder::new_current_thread() + .enable_all() + .build()? + .block_on(async { + loop { + // update the count + count += 1; + let mut text = rdom.get_mut(text_id).unwrap(); + if let NodeTypeMut::Text(mut text) = text.node_type_mut() { + *text = format!("Count: {count}"); + } + + let mut ctx = SendAnyMap::new(); + ctx.insert(FontSize(3.3)); + let _to_rerender = rdom.update_state(ctx); + + // render... + rdom.traverse_depth_first(|node| { + let indent = " ".repeat(node.height() as usize); + let color = *node.get::().unwrap(); + let size = *node.get::().unwrap(); + let border = *node.get::().unwrap(); + let id = node.id(); + println!("{indent}{id:?} {color:?} {size:?} {border:?}"); + }); + + // wait 1 second + tokio::time::sleep(std::time::Duration::from_secs(1)).await; + } + }) +} diff --git a/packages/native-core/src/node.rs b/packages/native-core/src/node.rs index 1cb39cff4..42ff8229f 100644 --- a/packages/native-core/src/node.rs +++ b/packages/native-core/src/node.rs @@ -62,13 +62,13 @@ pub enum NodeType { Placeholder, } -impl> From for NodeType { +impl> From for NodeType { fn from(text: S) -> Self { Self::Text(TextNode::new(text.into())) } } -impl From for NodeType { +impl From for NodeType { fn from(text: TextNode) -> Self { Self::Text(text) } @@ -136,25 +136,25 @@ pub enum OwnedAttributeValue { Custom(V), } -impl From for OwnedAttributeValue { +impl From for OwnedAttributeValue { fn from(value: String) -> Self { Self::Text(value) } } -impl From for OwnedAttributeValue { +impl From for OwnedAttributeValue { fn from(value: f64) -> Self { Self::Float(value) } } -impl From for OwnedAttributeValue { +impl From for OwnedAttributeValue { fn from(value: i64) -> Self { Self::Int(value) } } -impl From for OwnedAttributeValue { +impl From for OwnedAttributeValue { fn from(value: bool) -> Self { Self::Bool(value) } diff --git a/packages/native-core/src/passes.rs b/packages/native-core/src/passes.rs index d5c07f352..70dfc0f08 100644 --- a/packages/native-core/src/passes.rs +++ b/packages/native-core/src/passes.rs @@ -299,7 +299,7 @@ pub trait Dependancy { macro_rules! impl_dependancy { ($($t:ident),*) => { - impl< $($t: Send + Sync + Component + State),* > Dependancy for ($($t,)*) { + impl< $($t: Send + Sync + Component),* > Dependancy for ($($t,)*) { type ElementBorrowed<'a> = ($(DependancyView<'a, $t>,)*); fn type_ids() -> Box<[TypeId]> {