waterui_core/ui/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};
17use core::any::type_name;
18use core::fmt;
19
20/// View represents a part of the user interface.
21///
22/// You can create your custom view by implementing this trait. You just need to implement fit.
23///
24/// Users can also create a View using a function that returns another View. This allows for more
25/// flexible and composable UI designs.
26///
27/// # Example
28///
29/// ```rust
30/// use waterui_core::View;
31///
32/// fn greeting() -> impl View {
33/// "Hello, World!" // &'static str implements View
34/// }
35///
36#[must_use]
37#[diagnostic::on_unimplemented(
38 message = "`{Self}` is not a WaterUI view",
39 label = "expected a view",
40 note = "Any `'static` type implementing `View::body` is a view, as is a function returning `impl View` or a bare `&'static str`, `String`, or `Str`. A `ForEach` is a collection of views, not a view — hand it to a container such as `Lazy::for_each` or `List::for_each`, and erase differing view types with `.anyview()`."
41)]
42pub trait View: 'static {
43 /// Build this view and return the content.
44 ///
45 /// WARNING: This method should not be called directly by user.
46 fn body(self, _env: &Environment) -> impl View;
47
48 #[doc(hidden)]
49 /// Returns the stretch axis for this view.
50 ///
51 /// The answer is static: it must describe the leaf this view eventually
52 /// resolves to. Wrappers that only decorate or observe their content
53 /// (metadata, `Option`, `Result`, single-element tuples) forward the
54 /// content's axis. A composite view whose `body` produces a stretching
55 /// leaf (a `GpuSurface`, a scroll container, a stack with stretchy
56 /// children) must declare that leaf's axis here — callers read the axis
57 /// before `body` runs and without an [`Environment`], so it cannot be
58 /// discovered by expansion.
59 fn stretch_axis(&self) -> StretchAxis {
60 StretchAxis::None
61 }
62}
63
64impl<F: 'static + FnOnce() -> V, V: View> View for F {
65 fn body(self, _env: &Environment) -> impl View {
66 self()
67 }
68}
69
70impl<V: View, E: View> View for Result<V, E> {
71 fn body(self, _env: &Environment) -> impl View {
72 match self {
73 Ok(view) => AnyView::new(view),
74 Err(view) => AnyView::new(view),
75 }
76 }
77
78 fn stretch_axis(&self) -> StretchAxis {
79 match self {
80 Ok(view) => view.stretch_axis(),
81 Err(view) => view.stretch_axis(),
82 }
83 }
84}
85
86impl<V: View> View for Option<V> {
87 fn body(self, _env: &Environment) -> impl View {
88 self.map_or_else(|| AnyView::new(()), AnyView::new)
89 }
90
91 fn stretch_axis(&self) -> StretchAxis {
92 self.as_ref().map_or(StretchAxis::None, View::stretch_axis)
93 }
94}
95
96/// A trait for converting values into views.
97///
98/// This trait allows different types to be converted into View implementations,
99/// enabling more flexible composition of UI elements.
100pub trait IntoView {
101 /// The resulting View type after conversion.
102 type Output: View;
103
104 /// Converts the implementing type into a View.
105 ///
106 /// # Arguments
107 ///
108 /// * `env` - The environment containing context for the view conversion.
109 ///
110 /// # Returns
111 ///
112 /// A View implementation that can be used in the UI.
113 fn into_view(self, env: &Environment) -> Self::Output;
114}
115
116impl<V: View> IntoView for V {
117 type Output = V;
118 fn into_view(self, _env: &Environment) -> Self::Output {
119 self
120 }
121}
122
123/// A trait for converting collections and tuples of views into a vector of `AnyView`s.
124///
125/// This trait provides a uniform way to handle multiple views, allowing them
126/// to be converted into a homogeneous collection that can be processed consistently.
127pub trait TupleViews {
128 /// Converts the implementing type into a vector of `AnyView` objects.
129 ///
130 /// # Returns
131 ///
132 /// A `Vec<AnyView>` containing each view from the original collection.
133 fn into_views(self) -> Vec<AnyView>;
134
135 /// Reports each element's declared [`View::stretch_axis`], in order,
136 /// without consuming the collection.
137 ///
138 /// Composite containers answer their own `stretch_axis` from this:
139 /// they resolve to a [`Layout`](crate::layout::Layout)-driven container
140 /// in `body`, and that layout's `stretch_axis` is a function of the
141 /// children's axes — which must be readable before `body` runs.
142 fn stretch_axes(&self) -> Vec<StretchAxis>;
143}
144
145impl<V: View> TupleViews for Vec<V> {
146 fn into_views(self) -> Vec<AnyView> {
147 self.into_iter()
148 .map(|content| AnyView::new(content))
149 .collect()
150 }
151
152 fn stretch_axes(&self) -> Vec<StretchAxis> {
153 self.iter().map(View::stretch_axis).collect()
154 }
155}
156
157impl<V: View, const N: usize> TupleViews for [V; N] {
158 fn into_views(self) -> Vec<AnyView> {
159 self.into_iter()
160 .map(|content| AnyView::new(content))
161 .collect()
162 }
163
164 fn stretch_axes(&self) -> Vec<StretchAxis> {
165 self.iter().map(View::stretch_axis).collect()
166 }
167}
168
169/// A trait for views that can be configured with additional parameters.
170///
171/// This trait extends the basic `View` trait to support views that can be
172/// customized with a configuration object, allowing for more flexible and
173/// reusable UI components.
174pub trait ConfigurableView: View {
175 /// The configuration type associated with this view.
176 ///
177 /// This type defines the structure of configuration data that can be
178 /// applied to the view.
179 type Config: ViewConfiguration;
180
181 /// Returns the configuration for this view.
182 ///
183 /// This method extracts the configuration data from the view, which can
184 /// then be modified and applied to create customized versions of the view.
185 ///
186 /// # Returns
187 ///
188 /// The configuration object for this view.
189 fn config(self) -> Self::Config;
190}
191
192/// A trait for types that can be used to configure views.
193///
194/// View configurations are used by hooks to modify how views are rendered.
195pub trait ViewConfiguration: 'static {
196 // Note: the result would ignore any hook in the environment, to avoid infinite recursion.
197 /// The view type that this configuration produces.
198 type View: View;
199 /// Renders this configuration into a view.
200 fn render(self) -> Self::View;
201}
202
203// Note: Hook could change the behavior of the view dynamically based on the environment
204// only view implemented `ViewConfiguration` can be hooked.
205// A struct implemented `View` can be not concrete, but `ViewConfiguration` providing
206// `config()` method, which would return a concrete type.
207// By add `Hook<Config>` into `Environment`, a
208/// A function type for view hooks.
209type HookFn<C> = Box<dyn Fn(&Environment, C) -> AnyView>;
210
211/// A hook that can intercept and modify view configurations.
212///
213/// Hooks are used to apply global transformations to views based on their configuration.
214pub struct Hook<C>(HookFn<C>);
215
216impl<C> fmt::Debug for Hook<C> {
217 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
218 write!(f, "Modifier<{}>(..)", type_name::<C>())
219 }
220}
221
222impl<V, C, F> From<F> for Hook<C>
223where
224 C: ViewConfiguration,
225 V: View,
226 F: Fn(&Environment, C) -> V + 'static,
227{
228 fn from(value: F) -> Self {
229 Self(Box::new(move |env, config| {
230 let mut env = env.clone();
231 env.remove::<Self>(); // avoid infinite recursion
232 AnyView::new(Metadata::new(value(&env, config), env))
233 }))
234 }
235}
236
237impl<C> Hook<C>
238where
239 C: ViewConfiguration,
240{
241 /// Creates a new hook from a function.
242 ///
243 /// The function will be called with the environment and configuration
244 /// whenever a matching view configuration is encountered.
245 pub fn new<V, F>(f: F) -> Self
246 where
247 V: View,
248 F: Fn(&Environment, C) -> V + 'static,
249 {
250 Self::from(f)
251 }
252
253 /// Applies this hook to a configuration, producing a view.
254 pub fn apply(&self, env: &Environment, config: C) -> AnyView {
255 (self.0)(env, config)
256 }
257}
258
259impl<C: ViewConfiguration> Hook<C> {}
260
261macro_rules! impl_tuple_views {
262 ($($ty:ident),*) => {
263 #[allow(non_snake_case)]
264 #[allow(unused_variables)]
265 #[allow(unused_parens)]
266 impl <$($ty:View,)*>TupleViews for ($($ty,)*){
267 fn into_views(self) -> Vec<AnyView> {
268 // The trailing comma matters: `let (T) = self` is a parenthesized
269 // binding, not a one-element tuple pattern, so without it a
270 // single-child container erased the tuple itself instead of the
271 // view inside it — and the tuple answers every view question with
272 // its default, losing whatever the child had declared.
273 let ($($ty,)*)=self;
274 alloc::vec![$(AnyView::new($ty)),*]
275 }
276
277 fn stretch_axes(&self) -> Vec<StretchAxis> {
278 let ($($ty,)*)=self;
279 alloc::vec![$($ty.stretch_axis()),*]
280 }
281 }
282 };
283}
284
285tuples!(impl_tuple_views);
286
287raw_view!(());
288
289impl<V: View> View for (V,) {
290 fn body(self, _env: &Environment) -> impl View {
291 self.0
292 }
293
294 fn stretch_axis(&self) -> StretchAxis {
295 self.0.stretch_axis()
296 }
297}
298
299#[cfg(feature = "nightly")]
300impl View for ! {
301 fn body(self, _env: &Environment) -> impl View {}
302}