waterui_core/view.rs
1//! # View Module
2//!
3//! This module provides the core abstractions for building user interfaces.
4//!
5//! The primary types include:
6//! - `View`: The fundamental trait for UI components
7//! - `IntoView`: A trait for converting values into views
8//! - `TupleViews`: A trait for working with collections of views
9//! - `ConfigurableView`: A trait for views that can be configured
10//! - `Modifier`: A type for modifying configurable views
11//!
12//! These abstractions support a declarative and composable approach to UI building, allowing
13//! for flexible combinations of views and transformations.
14
15use crate::{AnyView, Environment, components::Metadata, layout::StretchAxis};
16use alloc::{boxed::Box, vec::Vec};
17
18/// View represents a part of the user interface.
19///
20/// You can create your custom view by implementing this trait. You just need to implement fit.
21///
22/// Users can also create a View using a function that returns another View. This allows for more
23/// flexible and composable UI designs.
24///
25/// # Example
26///
27/// ```rust
28/// use waterui_core::View;
29///
30/// fn greeting() -> impl View {
31/// "Hello, World!" // &'static str implements View
32/// }
33///
34#[must_use]
35pub trait View: 'static {
36 /// Build this view and return the content.
37 ///
38 /// WARNING: This method should not be called directly by user.
39 fn body(self, _env: &Environment) -> impl View;
40
41 #[doc(hidden)]
42 /// Returns the stretch axis for this view.
43 fn stretch_axis(&self) -> StretchAxis {
44 panic!("View::stretch_axis() only implemented for NativeView");
45 }
46}
47
48impl<F: 'static + FnOnce() -> V, V: View> View for F {
49 fn body(self, _env: &Environment) -> impl View {
50 self()
51 }
52}
53
54impl<V: View, E: View> View for Result<V, E> {
55 fn body(self, _env: &Environment) -> impl View {
56 match self {
57 Ok(view) => AnyView::new(view),
58 Err(view) => AnyView::new(view),
59 }
60 }
61}
62
63impl<V: View> View for Option<V> {
64 fn body(self, _env: &Environment) -> impl View {
65 self.map_or_else(|| AnyView::new(()), |view| AnyView::new(view))
66 }
67}
68
69/// A trait for converting values into views.
70///
71/// This trait allows different types to be converted into View implementations,
72/// enabling more flexible composition of UI elements.
73pub trait IntoView {
74 /// The resulting View type after conversion.
75 type Output: View;
76
77 /// Converts the implementing type into a View.
78 ///
79 /// # Arguments
80 ///
81 /// * `env` - The environment containing context for the view conversion.
82 ///
83 /// # Returns
84 ///
85 /// A View implementation that can be used in the UI.
86 fn into_view(self, env: &Environment) -> Self::Output;
87}
88
89impl<V: View> IntoView for V {
90 type Output = V;
91 fn into_view(self, _env: &Environment) -> Self::Output {
92 self
93 }
94}
95
96/// A trait for converting collections and tuples of views into a vector of `AnyView`s.
97///
98/// This trait provides a uniform way to handle multiple views, allowing them
99/// to be converted into a homogeneous collection that can be processed consistently.
100pub trait TupleViews {
101 /// Converts the implementing type into a vector of `AnyView` objects.
102 ///
103 /// # Returns
104 ///
105 /// A `Vec<AnyView>` containing each view from the original collection.
106 fn into_views(self) -> Vec<AnyView>;
107}
108
109impl<V: View> TupleViews for Vec<V> {
110 fn into_views(self) -> Vec<AnyView> {
111 self.into_iter()
112 .map(|content| AnyView::new(content))
113 .collect()
114 }
115}
116
117impl<V: View, const N: usize> TupleViews for [V; N] {
118 fn into_views(self) -> Vec<AnyView> {
119 self.into_iter()
120 .map(|content| AnyView::new(content))
121 .collect()
122 }
123}
124
125/// A trait for views that can be configured with additional parameters.
126///
127/// This trait extends the basic `View` trait to support views that can be
128/// customized with a configuration object, allowing for more flexible and
129/// reusable UI components.
130pub trait ConfigurableView: View {
131 /// The configuration type associated with this view.
132 ///
133 /// This type defines the structure of configuration data that can be
134 /// applied to the view.
135 type Config: ViewConfiguration;
136
137 /// Returns the configuration for this view.
138 ///
139 /// This method extracts the configuration data from the view, which can
140 /// then be modified and applied to create customized versions of the view.
141 ///
142 /// # Returns
143 ///
144 /// The configuration object for this view.
145 fn config(self) -> Self::Config;
146}
147
148/// A trait for types that can be used to configure views.
149///
150/// View configurations are used by hooks to modify how views are rendered.
151pub trait ViewConfiguration: 'static {
152 // Note: the result would ignore any hook in the environment, to avoid infinite recursion.
153 /// The view type that this configuration produces.
154 type View: View;
155 /// Renders this configuration into a view.
156 fn render(self) -> Self::View;
157}
158
159// Note: Hook could change the behavior of the view dynamically based on the environment
160// only view implemented `ViewConfiguration` can be hooked.
161// A struct implemented `View` can be not concrete, but `ViewConfiguration` providing
162// `config()` method, which would return a concrete type.
163// By add `Hook<Config>` into `Environment`, a
164/// A function type for view hooks.
165type HookFn<C> = Box<dyn Fn(&Environment, C) -> AnyView>;
166
167/// A hook that can intercept and modify view configurations.
168///
169/// Hooks are used to apply global transformations to views based on their configuration.
170pub struct Hook<C>(HookFn<C>);
171
172impl<C> core::fmt::Debug for Hook<C> {
173 fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
174 write!(f, "Modifier<{}>(..)", core::any::type_name::<C>())
175 }
176}
177
178impl<V, C, F> From<F> for Hook<C>
179where
180 C: ViewConfiguration,
181 V: View,
182 F: Fn(&Environment, C) -> V + 'static,
183{
184 fn from(value: F) -> Self {
185 Self(Box::new(move |env, config| {
186 let mut env = env.clone();
187 env.remove::<Self>(); // avoid infinite recursion
188 AnyView::new(Metadata::new(value(&env, config), env))
189 }))
190 }
191}
192
193impl<C> Hook<C>
194where
195 C: ViewConfiguration,
196{
197 /// Creates a new hook from a function.
198 ///
199 /// The function will be called with the environment and configuration
200 /// whenever a matching view configuration is encountered.
201 pub fn new<V, F>(f: F) -> Self
202 where
203 V: View,
204 F: Fn(&Environment, C) -> V + 'static,
205 {
206 Self::from(f)
207 }
208
209 /// Applies this hook to a configuration, producing a view.
210 pub fn apply(&self, env: &Environment, config: C) -> AnyView {
211 (self.0)(env, config)
212 }
213}
214
215impl<C: ViewConfiguration> Hook<C> {}
216
217macro_rules! impl_tuple_views {
218 ($($ty:ident),*) => {
219 #[allow(non_snake_case)]
220 #[allow(unused_variables)]
221 #[allow(unused_parens)]
222 impl <$($ty:View,)*>TupleViews for ($($ty,)*){
223 fn into_views(self) -> Vec<AnyView> {
224 let ($($ty),*)=self;
225 alloc::vec![$(AnyView::new($ty)),*]
226 }
227 }
228 };
229}
230
231tuples!(impl_tuple_views);
232
233raw_view!(());
234
235impl<V: View> View for (V,) {
236 fn body(self, _env: &Environment) -> impl View {
237 self.0
238 }
239}
240
241#[cfg(feature = "nightly")]
242impl View for ! {
243 fn body(self, _env: &Environment) -> impl View {}
244}