diff --git a/crates/concoct-web/src/html.rs b/crates/concoct-web/src/html.rs index 6768f259..378ce0f5 100644 --- a/crates/concoct-web/src/html.rs +++ b/crates/concoct-web/src/html.rs @@ -1,8 +1,8 @@ use super::WebContext; use concoct::{ - body::Child, + view::Child, hook::{use_context, use_on_drop, use_provider, use_ref}, - Body, View, + View, ViewBuilder, }; use std::{borrow::Cow, cell::RefCell, rc::Rc}; use web_sys::{ @@ -13,7 +13,7 @@ use web_sys::{ macro_rules! make_tag_fns { ($($name:tt),*) => { $( - pub fn $name(child: C) -> Html { + pub fn $name(child: C) -> Html { Html::new(stringify!($name), child) } )* @@ -108,8 +108,8 @@ impl Html { ); } -impl View for Html { - fn body(&self) -> impl Body { +impl ViewBuilder for Html { + fn build(&self) -> impl View { let data = use_ref(|| RefCell::new(Data::default())); let mut data_ref = data.borrow_mut(); diff --git a/crates/concoct-web/src/lib.rs b/crates/concoct-web/src/lib.rs index 4690f333..882b1389 100644 --- a/crates/concoct-web/src/lib.rs +++ b/crates/concoct-web/src/lib.rs @@ -1,6 +1,6 @@ use concoct::{ hook::{use_context, use_on_drop, use_provider, use_ref}, - Body, TextViewContext, View, + View, TextViewContext, ViewBuilder, }; use std::{cell::RefCell, rc::Rc}; use web_sys::{Document, Node, Text, Window}; @@ -17,8 +17,8 @@ pub struct WebRoot { pub body: Rc, } -impl View for WebRoot { - fn body(&self) -> impl Body { +impl ViewBuilder for WebRoot { + fn build(&self) -> impl View { let window = web_sys::window().unwrap(); let document = window.document().unwrap(); let body = document.body().unwrap(); @@ -58,7 +58,7 @@ impl View for WebRoot { } } -pub async fn run(view: impl View) { +pub async fn run(view: impl ViewBuilder) { concoct::run(WebRoot { body: Rc::new(view), }) diff --git a/crates/concoct/src/body.rs b/crates/concoct/src/body.rs deleted file mode 100644 index 674bff40..00000000 --- a/crates/concoct/src/body.rs +++ /dev/null @@ -1,87 +0,0 @@ -use crate::{Node, Tree, View}; -use std::{cell::RefCell, hash::Hash, rc::Rc}; - -pub trait Body: 'static { - fn into_tree(self) -> impl Tree; -} - -impl Body for Option { - fn into_tree(self) -> impl Tree { - self.map(|me| me.into_tree()) - } -} - -impl Body for Vec<(K, B)> { - fn into_tree(self) -> impl Tree { - self.into_iter() - .map(|(key, body)| (key, body.into_tree())) - .collect::>() - } -} - -pub struct Child { - cell: Rc>>, -} - -impl Child { - pub fn new(body: B) -> Self { - Self { - cell: Rc::new(RefCell::new(Some(body))), - } - } -} - -impl Clone for Child { - fn clone(&self) -> Self { - Self { - cell: self.cell.clone(), - } - } -} - -impl Body for Child { - fn into_tree(self) -> impl Tree { - self.cell.take().unwrap().into_tree() - } -} - -pub struct Empty; - -impl Body for Empty { - fn into_tree(self) -> impl Tree { - self - } -} - -impl Body for V { - fn into_tree(self) -> impl Tree { - Node { - view: self, - body: None, - builder: |me: &'static V| me.body().into_tree(), - scope: None, - key: None, - } - } -} - -macro_rules! impl_body_for_tuple { - ($($t:tt : $idx:tt),*) => { - impl<$($t: Body),*> Body for ($($t),*) { - fn into_tree(self) -> impl Tree { - ($( self.$idx.into_tree() ),*) - - } - } - }; -} - -impl_body_for_tuple!(V1: 0, V2: 1); -impl_body_for_tuple!(V1: 0, V2: 1, V3: 2); -impl_body_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3); -impl_body_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3, V5: 4); -impl_body_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3, V5: 4, V6: 5); -impl_body_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3, V5: 4, V6: 5, V7: 6); -impl_body_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3, V5: 4, V6: 5, V7: 6, V8: 7); -impl_body_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3, V5: 4, V6: 5, V7: 6, V8: 7, V9: 8); -impl_body_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3, V5: 4, V6: 5, V7: 6, V8: 7, V9: 8, V10: 9); diff --git a/crates/concoct/src/hook/use_state.rs b/crates/concoct/src/hook/use_state.rs index fd988011..d9fa10bd 100644 --- a/crates/concoct/src/hook/use_state.rs +++ b/crates/concoct/src/hook/use_state.rs @@ -1,6 +1,6 @@ use super::use_ref; use crate::Context; -use std::{cell::RefCell, rc::Rc}; +use std::cell::RefCell; /// Hook to create render state. /// diff --git a/crates/concoct/src/lib.rs b/crates/concoct/src/lib.rs index 23587314..ef4965bf 100644 --- a/crates/concoct/src/lib.rs +++ b/crates/concoct/src/lib.rs @@ -2,19 +2,23 @@ use rustc_hash::FxHasher; use slotmap::{DefaultKey, SlotMap}; use std::any::{Any, TypeId}; use std::borrow::Cow; -use std::collections::{HashMap, HashSet, VecDeque}; +use std::collections::{HashMap, VecDeque}; use std::hash::{Hash, Hasher}; use std::task::{Poll, Waker}; -use std::{cell::RefCell, mem, rc::Rc}; - -pub mod body; -pub use self::body::Body; -use body::Empty; +use std::{cell::RefCell, rc::Rc}; pub mod hook; +mod tree; +pub(crate) use tree::Node; +pub use tree::Tree; + +mod view_builder; +pub use self::view_builder::ViewBuilder; + pub mod view; pub use self::view::View; +use view::Empty; #[derive(Default)] struct ScopeInner { @@ -80,144 +84,6 @@ thread_local! { static CONTEXT: RefCell> = RefCell::new(None); } -pub struct Node { - view: V, - body: Option, - builder: F, - scope: Option, - key: Option, -} - -pub trait Tree: 'static { - fn build(&mut self); - - fn rebuild(&mut self, last: &mut dyn Any); - - fn remove(&mut self); -} - -impl Tree for Option { - fn build(&mut self) { - if let Some(tree) = self { - tree.build() - } - } - - fn rebuild(&mut self, last: &mut dyn Any) { - if let Some(tree) = self { - if let Some(last_tree) = last.downcast_mut::().unwrap() { - tree.rebuild(last_tree) - } else { - tree.build(); - } - } else if let Some(last_tree) = last.downcast_mut::().unwrap() { - last_tree.remove(); - } - } - - fn remove(&mut self) { - if let Some(tree) = self { - tree.remove() - } - } -} - -macro_rules! one_of { - ($name:tt, $($t:tt),*) => { - pub enum $name<$($t),*> { - $($t($t)),* - } - - impl<$($t: Body),*> Body for $name<$($t),*> { - fn into_tree(self) -> impl Tree { - match self { - $( - $name::$t(body) => $name::$t(body.into_tree()), - )* - } - } - } - - impl<$($t: Tree),*> Tree for $name<$($t),*> { - fn build(&mut self) { - match self { - $( - $name::$t(tree) => tree.build(), - )* - } - } - - fn rebuild(&mut self, last: &mut dyn Any) { - let last = last.downcast_mut::().unwrap(); - match (self, last) { - $( - ($name::$t(tree), $name::$t(last_tree)) => { - tree.rebuild(last_tree) - } - ),* - (me, last) => { - last.remove(); - me.build(); - } - } - - } - - fn remove(&mut self) { - match self { - $( - $name::$t(tree) => tree.remove(), - )* - } - } - } - }; -} - -one_of!(OneOf2, A, B); -one_of!(OneOf3, A, B, C); -one_of!(OneOf4, A, B, C, D); -one_of!(OneOf5, A, B, C, D, E); -one_of!(OneOf6, A, B, C, D, E, F); -one_of!(OneOf7, A, B, C, D, E, F, G); -one_of!(OneOf8, A, B, C, D, E, F, G, H); - -macro_rules! impl_tree_for_tuple { - ($($t:tt : $idx:tt),*) => { - impl<$($t: Tree),*> Tree for ($($t),*) { - fn build(&mut self) { - $( - self.$idx.build(); - )* - } - - fn rebuild(&mut self, last: &mut dyn Any) { - if let Some(last) = last.downcast_mut::() { - $( - self.$idx.rebuild(&mut last.$idx); - )* - } - } - - fn remove(&mut self) { - $( - self.$idx.remove(); - )* - } - } - }; -} - -impl_tree_for_tuple!(V1: 0, V2: 1); -impl_tree_for_tuple!(V1: 0, V2: 1, V3: 2); -impl_tree_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3); -impl_tree_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3, V5: 4); -impl_tree_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3, V5: 4, V6: 5); -impl_tree_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3, V5: 4, V6: 5, V7: 6); -impl_tree_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3, V5: 4, V6: 5, V7: 6, V8: 7); -impl_tree_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3, V5: 4, V6: 5, V7: 6, V8: 7, V9: 8); -impl_tree_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3, V5: 4, V6: 5, V7: 6, V8: 7, V9: 8, V10: 9); - impl Tree for Empty { fn build(&mut self) {} @@ -226,165 +92,7 @@ impl Tree for Empty { fn remove(&mut self) {} } -impl Tree for Vec<(K, T)> { - fn build(&mut self) { - for (_, body) in self.iter_mut() { - body.build() - } - } - - fn rebuild(&mut self, last: &mut dyn Any) { - let mut visited = HashSet::new(); - let last = last.downcast_mut::().unwrap(); - - for (key, body) in self.iter_mut() { - if let Some((_, last_body)) = last.iter_mut().find(|(last_key, _)| last_key == key) { - body.rebuild(last_body); - visited.insert(key); - } else { - body.build(); - } - } - - for (key, body) in last.iter_mut() { - if !visited.contains(key) { - body.remove(); - } - } - } - - fn remove(&mut self) { - for (_, body) in self.iter_mut() { - body.remove() - } - } -} - -impl Tree for Node -where - V: View, - B: Tree + 'static, - F: FnMut(&'static V) -> B + 'static, -{ - fn build(&mut self) { - let cx = Context::current(); - let mut cx_ref = cx.inner.borrow_mut(); - - if let Some(key) = self.key { - let mut scope = self.scope.as_ref().unwrap().inner.borrow_mut(); - for (name, value) in cx_ref.contexts.iter() { - if !scope.contexts.contains_key(name) { - scope.contexts.insert(*name, value.clone()); - } - } - drop(scope); - - cx_ref.node = Some(key); - cx_ref.scope = Some(self.scope.clone().unwrap()); - drop(cx_ref); - - let view = unsafe { mem::transmute(&self.view) }; - let body = (self.builder)(view); - - let parent_contexts = { - let mut cx_ref = cx.inner.borrow_mut(); - let mut scope = self.scope.as_ref().unwrap().inner.borrow_mut(); - scope.hook_idx = 0; - mem::replace(&mut cx_ref.contexts, scope.contexts.clone()) - }; - - let mut last_body = mem::replace(&mut self.body, Some(body)).unwrap(); - self.body.as_mut().unwrap().rebuild(&mut last_body); - - let mut cx_ref = cx.inner.borrow_mut(); - cx_ref.contexts = parent_contexts; - } else { - let key = cx_ref.nodes.insert(self as _); - self.key = Some(key); - - let scope = Scope::default(); - scope.inner.borrow_mut().contexts = cx_ref.contexts.clone(); - self.scope = Some(scope); - - cx_ref.node = Some(key); - cx_ref.scope = Some(self.scope.clone().unwrap()); - drop(cx_ref); - - let view = unsafe { mem::transmute(&self.view) }; - let body = (self.builder)(view); - - let parent_contexts = { - let mut cx_ref = cx.inner.borrow_mut(); - let mut scope = self.scope.as_ref().unwrap().inner.borrow_mut(); - scope.hook_idx = 0; - mem::replace(&mut cx_ref.contexts, scope.contexts.clone()) - }; - - self.body = Some(body); - self.body.as_mut().unwrap().build(); - - let mut cx_ref = cx.inner.borrow_mut(); - cx_ref.contexts = parent_contexts; - } - } - - fn rebuild(&mut self, last: &mut dyn Any) { - let last = (*last).downcast_mut::().unwrap(); - let cx = Context::current(); - let mut cx_ref = cx.inner.borrow_mut(); - - let key = last.key.unwrap(); - self.key = Some(key); - self.scope = last.scope.clone(); - - let mut scope = self.scope.as_ref().unwrap().inner.borrow_mut(); - for (name, value) in cx_ref.contexts.iter() { - if !scope.contexts.contains_key(name) { - scope.contexts.insert(*name, value.clone()); - } - } - drop(scope); - - cx_ref.node = Some(key); - cx_ref.scope = Some(self.scope.clone().unwrap()); - drop(cx_ref); - - let view = unsafe { mem::transmute(&self.view) }; - let body = (self.builder)(view); - - let parent_contexts = { - let mut cx_ref = cx.inner.borrow_mut(); - let mut scope = self.scope.as_ref().unwrap().inner.borrow_mut(); - scope.hook_idx = 0; - mem::replace(&mut cx_ref.contexts, scope.contexts.clone()) - }; - - self.body = Some(body); - self.body - .as_mut() - .unwrap() - .rebuild(last.body.as_mut().unwrap()); - - let mut cx_ref = cx.inner.borrow_mut(); - cx_ref.contexts = parent_contexts; - } - - fn remove(&mut self) { - let cx = Context::current(); - let mut cx_ref = cx.inner.borrow_mut(); - let key = self.key.unwrap(); - cx_ref.nodes.remove(key); - drop(cx_ref); - - for dropper in &mut self.scope.as_ref().unwrap().inner.borrow_mut().droppers { - dropper() - } - - self.body.as_mut().unwrap().remove(); - } -} - -pub async fn run(view: impl View) { +pub async fn run(view: impl ViewBuilder) { let cx = Context::default(); cx.enter(); @@ -421,7 +129,7 @@ pub struct Memo { body: B, } -impl Body for Memo { +impl View for Memo { fn into_tree(self) -> impl Tree { Memo { hash: self.hash, diff --git a/crates/concoct/src/tree/mod.rs b/crates/concoct/src/tree/mod.rs new file mode 100644 index 00000000..32dd7a13 --- /dev/null +++ b/crates/concoct/src/tree/mod.rs @@ -0,0 +1,112 @@ +use std::{any::Any, collections::HashSet, hash::Hash}; + +mod node; +pub use node::Node; + +pub trait Tree: 'static { + fn build(&mut self); + + fn rebuild(&mut self, last: &mut dyn Any); + + fn remove(&mut self); +} + +impl Tree for Option { + fn build(&mut self) { + if let Some(tree) = self { + tree.build() + } + } + + fn rebuild(&mut self, last: &mut dyn Any) { + if let Some(tree) = self { + if let Some(last_tree) = last.downcast_mut::().unwrap() { + tree.rebuild(last_tree) + } else { + tree.build(); + } + } else if let Some(last_tree) = last.downcast_mut::().unwrap() { + last_tree.remove(); + } + } + + fn remove(&mut self) { + if let Some(tree) = self { + tree.remove() + } + } +} + +impl Tree for Vec<(K, T)> +where + K: Hash + Eq + 'static, + T: Tree, +{ + fn build(&mut self) { + for (_, body) in self.iter_mut() { + body.build() + } + } + + fn rebuild(&mut self, last: &mut dyn Any) { + let mut visited = HashSet::new(); + let last = last.downcast_mut::().unwrap(); + + for (key, body) in self.iter_mut() { + if let Some((_, last_body)) = last.iter_mut().find(|(last_key, _)| last_key == key) { + body.rebuild(last_body); + visited.insert(key); + } else { + body.build(); + } + } + + for (key, body) in last.iter_mut() { + if !visited.contains(key) { + body.remove(); + } + } + } + + fn remove(&mut self) { + for (_, body) in self.iter_mut() { + body.remove() + } + } +} + +macro_rules! impl_tree_for_tuple { + ($($t:tt : $idx:tt),*) => { + impl<$($t: Tree),*> Tree for ($($t),*) { + fn build(&mut self) { + $( + self.$idx.build(); + )* + } + + fn rebuild(&mut self, last: &mut dyn Any) { + if let Some(last) = last.downcast_mut::() { + $( + self.$idx.rebuild(&mut last.$idx); + )* + } + } + + fn remove(&mut self) { + $( + self.$idx.remove(); + )* + } + } + }; +} + +impl_tree_for_tuple!(V1: 0, V2: 1); +impl_tree_for_tuple!(V1: 0, V2: 1, V3: 2); +impl_tree_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3); +impl_tree_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3, V5: 4); +impl_tree_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3, V5: 4, V6: 5); +impl_tree_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3, V5: 4, V6: 5, V7: 6); +impl_tree_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3, V5: 4, V6: 5, V7: 6, V8: 7); +impl_tree_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3, V5: 4, V6: 5, V7: 6, V8: 7, V9: 8); +impl_tree_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3, V5: 4, V6: 5, V7: 6, V8: 7, V9: 8, V10: 9); diff --git a/crates/concoct/src/tree/node.rs b/crates/concoct/src/tree/node.rs new file mode 100644 index 00000000..7650ab4d --- /dev/null +++ b/crates/concoct/src/tree/node.rs @@ -0,0 +1,135 @@ +use crate::{Context, Scope, Tree, ViewBuilder}; +use slotmap::DefaultKey; +use std::{any::Any, mem}; + +pub struct Node { + pub(crate) view: V, + pub(crate) body: Option, + pub(crate) builder: F, + pub(crate) scope: Option, + pub(crate) key: Option, +} + +impl Tree for Node +where + V: ViewBuilder, + B: Tree + 'static, + F: FnMut(&'static V) -> B + 'static, +{ + fn build(&mut self) { + let cx = Context::current(); + let mut cx_ref = cx.inner.borrow_mut(); + + if let Some(key) = self.key { + let mut scope = self.scope.as_ref().unwrap().inner.borrow_mut(); + for (name, value) in cx_ref.contexts.iter() { + if !scope.contexts.contains_key(name) { + scope.contexts.insert(*name, value.clone()); + } + } + drop(scope); + + cx_ref.node = Some(key); + cx_ref.scope = Some(self.scope.clone().unwrap()); + drop(cx_ref); + + let view = unsafe { mem::transmute(&self.view) }; + let body = (self.builder)(view); + + let parent_contexts = { + let mut cx_ref = cx.inner.borrow_mut(); + let mut scope = self.scope.as_ref().unwrap().inner.borrow_mut(); + scope.hook_idx = 0; + mem::replace(&mut cx_ref.contexts, scope.contexts.clone()) + }; + + let mut last_body = mem::replace(&mut self.body, Some(body)).unwrap(); + self.body.as_mut().unwrap().rebuild(&mut last_body); + + let mut cx_ref = cx.inner.borrow_mut(); + cx_ref.contexts = parent_contexts; + } else { + let key = cx_ref.nodes.insert(self as _); + self.key = Some(key); + + let scope = Scope::default(); + scope.inner.borrow_mut().contexts = cx_ref.contexts.clone(); + self.scope = Some(scope); + + cx_ref.node = Some(key); + cx_ref.scope = Some(self.scope.clone().unwrap()); + drop(cx_ref); + + let view = unsafe { mem::transmute(&self.view) }; + let body = (self.builder)(view); + + let parent_contexts = { + let mut cx_ref = cx.inner.borrow_mut(); + let mut scope = self.scope.as_ref().unwrap().inner.borrow_mut(); + scope.hook_idx = 0; + mem::replace(&mut cx_ref.contexts, scope.contexts.clone()) + }; + + self.body = Some(body); + self.body.as_mut().unwrap().build(); + + let mut cx_ref = cx.inner.borrow_mut(); + cx_ref.contexts = parent_contexts; + } + } + + fn rebuild(&mut self, last: &mut dyn Any) { + let last = (*last).downcast_mut::().unwrap(); + let cx = Context::current(); + let mut cx_ref = cx.inner.borrow_mut(); + + let key = last.key.unwrap(); + self.key = Some(key); + self.scope = last.scope.clone(); + + let mut scope = self.scope.as_ref().unwrap().inner.borrow_mut(); + for (name, value) in cx_ref.contexts.iter() { + if !scope.contexts.contains_key(name) { + scope.contexts.insert(*name, value.clone()); + } + } + drop(scope); + + cx_ref.node = Some(key); + cx_ref.scope = Some(self.scope.clone().unwrap()); + drop(cx_ref); + + let view = unsafe { mem::transmute(&self.view) }; + let body = (self.builder)(view); + + let parent_contexts = { + let mut cx_ref = cx.inner.borrow_mut(); + let mut scope = self.scope.as_ref().unwrap().inner.borrow_mut(); + scope.hook_idx = 0; + mem::replace(&mut cx_ref.contexts, scope.contexts.clone()) + }; + + self.body = Some(body); + self.body + .as_mut() + .unwrap() + .rebuild(last.body.as_mut().unwrap()); + + let mut cx_ref = cx.inner.borrow_mut(); + cx_ref.contexts = parent_contexts; + } + + fn remove(&mut self) { + let cx = Context::current(); + let mut cx_ref = cx.inner.borrow_mut(); + let key = self.key.unwrap(); + cx_ref.nodes.remove(key); + drop(cx_ref); + + for dropper in &mut self.scope.as_ref().unwrap().inner.borrow_mut().droppers { + dropper() + } + + self.body.as_mut().unwrap().remove(); + } +} diff --git a/crates/concoct/src/view/child.rs b/crates/concoct/src/view/child.rs new file mode 100644 index 00000000..db8101fa --- /dev/null +++ b/crates/concoct/src/view/child.rs @@ -0,0 +1,28 @@ +use crate::{Tree, View}; +use std::{cell::RefCell, rc::Rc}; + +pub struct Child { + cell: Rc>>, +} + +impl Child { + pub fn new(body: B) -> Self { + Self { + cell: Rc::new(RefCell::new(Some(body))), + } + } +} + +impl Clone for Child { + fn clone(&self) -> Self { + Self { + cell: self.cell.clone(), + } + } +} + +impl View for Child { + fn into_tree(self) -> impl Tree { + self.cell.take().unwrap().into_tree() + } +} diff --git a/crates/concoct/src/view/empty.rs b/crates/concoct/src/view/empty.rs new file mode 100644 index 00000000..e7bc0429 --- /dev/null +++ b/crates/concoct/src/view/empty.rs @@ -0,0 +1,9 @@ +use crate::{Tree, View}; + +pub struct Empty; + +impl View for Empty { + fn into_tree(self) -> impl Tree { + self + } +} diff --git a/crates/concoct/src/view/mod.rs b/crates/concoct/src/view/mod.rs new file mode 100644 index 00000000..7496133a --- /dev/null +++ b/crates/concoct/src/view/mod.rs @@ -0,0 +1,62 @@ +use crate::{Node, Tree, ViewBuilder}; +use std::hash::Hash; + +mod child; +pub use child::Child; + +mod empty; +pub use empty::Empty; + +mod one_of; +pub use one_of::*; + +pub trait View: 'static { + fn into_tree(self) -> impl Tree; +} + +impl View for Option { + fn into_tree(self) -> impl Tree { + self.map(|me| me.into_tree()) + } +} + +impl View for Vec<(K, B)> { + fn into_tree(self) -> impl Tree { + self.into_iter() + .map(|(key, body)| (key, body.into_tree())) + .collect::>() + } +} + +impl View for V { + fn into_tree(self) -> impl Tree { + Node { + view: self, + body: None, + builder: |me: &'static V| me.build().into_tree(), + scope: None, + key: None, + } + } +} + +macro_rules! impl_view_for_tuple { + ($($t:tt : $idx:tt),*) => { + impl<$($t: View),*> View for ($($t),*) { + fn into_tree(self) -> impl Tree { + ($( self.$idx.into_tree() ),*) + + } + } + }; +} + +impl_view_for_tuple!(V1: 0, V2: 1); +impl_view_for_tuple!(V1: 0, V2: 1, V3: 2); +impl_view_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3); +impl_view_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3, V5: 4); +impl_view_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3, V5: 4, V6: 5); +impl_view_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3, V5: 4, V6: 5, V7: 6); +impl_view_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3, V5: 4, V6: 5, V7: 6, V8: 7); +impl_view_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3, V5: 4, V6: 5, V7: 6, V8: 7, V9: 8); +impl_view_for_tuple!(V1: 0, V2: 1, V3: 2, V4: 3, V5: 4, V6: 5, V7: 6, V8: 7, V9: 8, V10: 9); diff --git a/crates/concoct/src/view/one_of.rs b/crates/concoct/src/view/one_of.rs new file mode 100644 index 00000000..f4f7ef9d --- /dev/null +++ b/crates/concoct/src/view/one_of.rs @@ -0,0 +1,62 @@ +use crate::{Tree, View}; +use std::any::Any; + +macro_rules! one_of { + ($name:tt, $($t:tt),*) => { + pub enum $name<$($t),*> { + $($t($t)),* + } + + impl<$($t: View),*> View for $name<$($t),*> { + fn into_tree(self) -> impl Tree { + match self { + $( + $name::$t(body) => $name::$t(body.into_tree()), + )* + } + } + } + + impl<$($t: Tree),*> Tree for $name<$($t),*> { + fn build(&mut self) { + match self { + $( + $name::$t(tree) => tree.build(), + )* + } + } + + fn rebuild(&mut self, last: &mut dyn Any) { + let last = last.downcast_mut::().unwrap(); + match (self, last) { + $( + ($name::$t(tree), $name::$t(last_tree)) => { + tree.rebuild(last_tree) + } + ),* + (me, last) => { + last.remove(); + me.build(); + } + } + + } + + fn remove(&mut self) { + match self { + $( + $name::$t(tree) => tree.remove(), + )* + } + } + } + }; +} + +one_of!(OneOf2, A, B); +one_of!(OneOf3, A, B, C); +one_of!(OneOf4, A, B, C, D); +one_of!(OneOf5, A, B, C, D, E); +one_of!(OneOf6, A, B, C, D, E, F); +one_of!(OneOf7, A, B, C, D, E, F, G); +one_of!(OneOf8, A, B, C, D, E, F, G, H); diff --git a/crates/concoct/src/view.rs b/crates/concoct/src/view_builder.rs similarity index 52% rename from crates/concoct/src/view.rs rename to crates/concoct/src/view_builder.rs index cebef0a1..cfccc9ac 100644 --- a/crates/concoct/src/view.rs +++ b/crates/concoct/src/view_builder.rs @@ -1,26 +1,26 @@ -use crate::{body::Empty, Body}; +use crate::{view::Empty, View}; use std::rc::Rc; -pub trait View: 'static { - fn body(&self) -> impl Body; +pub trait ViewBuilder: 'static { + fn build(&self) -> impl View; } -impl View for () { - fn body(&self) -> impl Body { +impl ViewBuilder for () { + fn build(&self) -> impl View { Empty } } -impl View for Rc { - fn body(&self) -> impl Body { - (&**self).body() +impl ViewBuilder for Rc { + fn build(&self) -> impl View { + (&**self).build() } } macro_rules! impl_string_view { ($t:ty) => { - impl View for $t { - fn body(&self) -> impl Body { + impl ViewBuilder for $t { + fn build(&self) -> impl View { let cx = crate::hook::use_context::().unwrap(); let mut view = cx.view.borrow_mut(); view(self.clone().into()) diff --git a/web_examples/counter/src/main.rs b/web_examples/counter/src/main.rs index ba703089..b2b5be8b 100644 --- a/web_examples/counter/src/main.rs +++ b/web_examples/counter/src/main.rs @@ -1,11 +1,11 @@ -use concoct::{hook::use_state, Body, View}; +use concoct::{hook::use_state, View, ViewBuilder}; use concoct_web::html; use wasm_bindgen_futures::spawn_local; struct App; -impl View for App { - fn body(&self) -> impl Body { +impl ViewBuilder for App { + fn build(&self) -> impl View { let (count, set_high) = use_state(|| 0); let set_low = set_high.clone();