sycamore_web/
view.rs

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