1//! This module contains the [`View`] struct which represents a view tree.
23use std::any::Any;
4use std::fmt;
56use smallvec::{smallvec, SmallVec};
7use sycamore_core::Children;
89use crate::*;
1011/// Represents a view tree.
12///
13/// Internally, this stores a list of nodes. This is the main type that is returned from
14/// components.
15pub struct View<T = HtmlNode> {
16/// The nodes in the view tree.
17pub(crate) nodes: SmallVec<[T; 1]>,
18}
1920impl<T> View<T> {
21/// Create a new blank view.
22pub fn new() -> Self {
23Self {
24 nodes: SmallVec::new(),
25 }
26 }
2728/// Create a new view with a single node.
29pub fn from_node(node: T) -> Self {
30Self {
31 nodes: smallvec![node],
32 }
33 }
3435/// Create a new view with multiple nodes.
36pub fn from_nodes(nodes: Vec<T>) -> Self {
37Self {
38 nodes: nodes.into(),
39 }
40 }
4142/// Create a new view from a function that returns a view. An alias to
43 /// [`ViewNode::create_dynamic_view`].
44pub fn from_dynamic<U: Into<Self> + 'static>(f: impl FnMut() -> U + 'static) -> Self
45where
46T: ViewNode,
47 {
48 T::create_dynamic_view(f)
49 }
5051/// Create a flat list of all the web-sys nodes in the view.
52pub fn as_web_sys(&self) -> Vec<web_sys::Node>
53where
54T: ViewHtmlNode,
55 {
56self.nodes
57 .iter()
58 .map(|node| node.as_web_sys().clone())
59 .collect()
60 }
61}
6263impl<T> Default for View<T> {
64fn default() -> Self {
65Self::new()
66 }
67}
6869impl<T> fmt::Debug for View<T> {
70fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
71 f.debug_struct("View").finish()
72 }
73}
7475impl<T> From<Children<Self>> for View<T> {
76fn from(children: Children<Self>) -> Self {
77 children.call()
78 }
79}
8081impl<T> From<Vec<View<T>>> for View<T> {
82fn from(nodes: Vec<View<T>>) -> Self {
83 View {
84 nodes: nodes.into_iter().flat_map(|v| v.nodes).collect(),
85 }
86 }
87}
8889impl<T> From<Option<View<T>>> for View<T> {
90fn from(node: Option<View<T>>) -> Self {
91 node.unwrap_or_default()
92 }
93}
9495macro_rules! impl_view_from {
96 ($($ty:ty),*) => {
97 $(
98impl<T: ViewHtmlNode> From<$ty> for View<T> {
99fn from(t: $ty) -> Self {
100 View::from_node(T::create_text_node(t.into()))
101 }
102 }
103 )*
104 }
105}
106107macro_rules! impl_view_from_to_string {
108 ($($ty:ty),*) => {
109 $(
110impl<T: ViewHtmlNode> From<$ty> for View<T> {
111fn from(t: $ty) -> Self {
112 View::from_node(T::create_text_node(t.to_string().into()))
113 }
114 }
115 )*
116 }
117}
118119impl_view_from!(&'static str, String, Cow<'static, str>);
120impl_view_from_to_string!(i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64);
121122impl<T: ViewNode, F: FnMut() -> U + 'static, U: Into<View<T>> + Any + 'static> From<F> for View<T> {
123fn from(f: F) -> Self {
124 T::create_dynamic_view(f)
125 }
126}
127// Implement `From` for all tuples of types that implement `Into<View<U>>`.
128macro_rules! impl_from_tuple {
129 ($($name:ident),*) => {
130paste::paste! {
131impl<U, $($name),*> From<($($name,)*)> for View<U>
132where
133$($name: Into<View<U>>),*
134 {
135fn from(t: ($($name,)*)) -> Self {
136let ($([<$name:lower>]),*) = t;
137#[allow(unused_mut)]
138let mut nodes = SmallVec::new();
139 $(
140 nodes.extend([<$name:lower>].into().nodes);
141 )*
142 View { nodes }
143 }
144 }
145 }
146 };
147}
148149impl_from_tuple!();
150impl_from_tuple!(A, B);
151impl_from_tuple!(A, B, C);
152impl_from_tuple!(A, B, C, D);
153impl_from_tuple!(A, B, C, D, E);
154impl_from_tuple!(A, B, C, D, E, F);
155impl_from_tuple!(A, B, C, D, E, F, G);
156impl_from_tuple!(A, B, C, D, E, F, G, H);
157impl_from_tuple!(A, B, C, D, E, F, G, H, I);
158impl_from_tuple!(A, B, C, D, E, F, G, H, I, J);
159160/// A trait that should be implemented for anything that represents a node in the view tree (UI
161/// tree).
162///
163/// Examples include `DomNode` and `SsrNode` which are used to render views to the browser DOM and
164/// to a string respectively. This trait can be implemented for other types to create custom render
165/// backends.
166pub trait ViewNode: Into<View<Self>> + Sized + 'static {
167/// Appends a child to the node. Panics if the node is not an element or other node that can
168 /// have children (e.g. text node).
169fn append_child(&mut self, child: Self);
170171/// Append a view to this node. Since a view is just a list of nodes, this essentially appends
172 /// every node in the view to this node.
173fn append_view(&mut self, view: View<Self>) {
174for node in view.nodes {
175self.append_child(node);
176 }
177 }
178179/// Create a dynamic view from a function that returns a view.
180 ///
181 /// The returned view will no longer be a function and can be treated as a normal view and,
182 /// e.g., appended as a child to another node.
183 ///
184 /// Some render backends may not support dynamic views (e.g. `SsrNode`). In that case, the
185 /// default behavior is to simply evaluate the function as a static view.
186fn create_dynamic_view<U: Into<View<Self>> + 'static>(
187mut f: impl FnMut() -> U + 'static,
188 ) -> View<Self> {
189 f().into()
190 }
191}