Skip to main content

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!(
136    i8, i16, i32, i64, i128, isize, u8, u16, u32, u64, u128, usize, f32, f64
137);
138
139impl<T: ViewNode, F: FnMut() -> U + 'static, U: Into<View<T>> + 'static> From<F> for View<T> {
140    fn from(f: F) -> Self {
141        T::create_dynamic_view(f)
142    }
143}
144// Implement `From` for all tuples of types that implement `Into<View<U>>`.
145macro_rules! impl_from_tuple {
146    ($($name:ident),*) => {
147        paste::paste! {
148            impl<U, $($name),*> From<($($name,)*)> for View<U>
149            where
150                $($name: Into<View<U>>),*
151            {
152                fn from(t: ($($name,)*)) -> Self {
153                    let ($([<$name:lower>]),*) = t;
154                    #[allow(unused_mut)]
155                    let mut nodes = SmallVec::new();
156                    $(
157                        nodes.extend([<$name:lower>].into().nodes);
158                    )*
159                    View { nodes }
160                }
161            }
162        }
163    };
164}
165
166impl_from_tuple!();
167impl_from_tuple!(A, B);
168impl_from_tuple!(A, B, C);
169impl_from_tuple!(A, B, C, D);
170impl_from_tuple!(A, B, C, D, E);
171impl_from_tuple!(A, B, C, D, E, F);
172impl_from_tuple!(A, B, C, D, E, F, G);
173impl_from_tuple!(A, B, C, D, E, F, G, H);
174impl_from_tuple!(A, B, C, D, E, F, G, H, I);
175impl_from_tuple!(A, B, C, D, E, F, G, H, I, J);
176
177/// A trait that should be implemented for anything that represents a node in the view tree (UI
178/// tree).
179///
180/// Examples include `DomNode` and `SsrNode` which are used to render views to the browser DOM and
181/// to a string respectively. This trait can be implemented for other types to create custom render
182/// backends.
183pub trait ViewNode: Into<View<Self>> + Sized + 'static {
184    /// Appends a child to the node. Panics if the node is not an element or other node that can
185    /// have children (e.g. text node).
186    fn append_child(&mut self, child: Self);
187
188    /// Append a view to this node. Since a view is just a list of nodes, this essentially appends
189    /// every node in the view to this node.
190    fn append_view(&mut self, view: View<Self>) {
191        for node in view.nodes {
192            self.append_child(node);
193        }
194    }
195
196    /// Create a dynamic view from a function that returns a view.
197    ///
198    /// The returned view will no longer be a function and can be treated as a normal view and,
199    /// e.g., appended as a child to another node.
200    ///
201    /// Some render backends may not support dynamic views (e.g. `SsrNode`). In that case, the
202    /// default behavior is to simply evaluate the function as a static view.
203    fn create_dynamic_view<U: Into<View<Self>> + 'static>(
204        mut f: impl FnMut() -> U + 'static,
205    ) -> View<Self> {
206        f().into()
207    }
208}