Skip to main content

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}