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]
37pub trait View: 'static {
38    /// Build this view and return the content.
39    ///
40    /// WARNING: This method should not be called directly by user.
41    fn body(self, _env: &Environment) -> impl View;
42
43    #[doc(hidden)]
44    /// Returns the stretch axis for this view.
45    fn stretch_axis(&self) -> StretchAxis {
46        StretchAxis::None
47    }
48}
49
50impl<F: 'static + FnOnce() -> V, V: View> View for F {
51    fn body(self, _env: &Environment) -> impl View {
52        self()
53    }
54}
55
56impl<V: View, E: View> View for Result<V, E> {
57    fn body(self, _env: &Environment) -> impl View {
58        match self {
59            Ok(view) => AnyView::new(view),
60            Err(view) => AnyView::new(view),
61        }
62    }
63}
64
65impl<V: View> View for Option<V> {
66    fn body(self, _env: &Environment) -> impl View {
67        self.map_or_else(|| AnyView::new(()), AnyView::new)
68    }
69}
70
71/// A trait for converting values into views.
72///
73/// This trait allows different types to be converted into View implementations,
74/// enabling more flexible composition of UI elements.
75pub trait IntoView {
76    /// The resulting View type after conversion.
77    type Output: View;
78
79    /// Converts the implementing type into a View.
80    ///
81    /// # Arguments
82    ///
83    /// * `env` - The environment containing context for the view conversion.
84    ///
85    /// # Returns
86    ///
87    /// A View implementation that can be used in the UI.
88    fn into_view(self, env: &Environment) -> Self::Output;
89}
90
91impl<V: View> IntoView for V {
92    type Output = V;
93    fn into_view(self, _env: &Environment) -> Self::Output {
94        self
95    }
96}
97
98/// A trait for converting collections and tuples of views into a vector of `AnyView`s.
99///
100/// This trait provides a uniform way to handle multiple views, allowing them
101/// to be converted into a homogeneous collection that can be processed consistently.
102pub trait TupleViews {
103    /// Converts the implementing type into a vector of `AnyView` objects.
104    ///
105    /// # Returns
106    ///
107    /// A `Vec<AnyView>` containing each view from the original collection.
108    fn into_views(self) -> Vec<AnyView>;
109}
110
111impl<V: View> TupleViews for Vec<V> {
112    fn into_views(self) -> Vec<AnyView> {
113        self.into_iter()
114            .map(|content| AnyView::new(content))
115            .collect()
116    }
117}
118
119impl<V: View, const N: usize> TupleViews for [V; N] {
120    fn into_views(self) -> Vec<AnyView> {
121        self.into_iter()
122            .map(|content| AnyView::new(content))
123            .collect()
124    }
125}
126
127/// A trait for views that can be configured with additional parameters.
128///
129/// This trait extends the basic `View` trait to support views that can be
130/// customized with a configuration object, allowing for more flexible and
131/// reusable UI components.
132pub trait ConfigurableView: View {
133    /// The configuration type associated with this view.
134    ///
135    /// This type defines the structure of configuration data that can be
136    /// applied to the view.
137    type Config: ViewConfiguration;
138
139    /// Returns the configuration for this view.
140    ///
141    /// This method extracts the configuration data from the view, which can
142    /// then be modified and applied to create customized versions of the view.
143    ///
144    /// # Returns
145    ///
146    /// The configuration object for this view.
147    fn config(self) -> Self::Config;
148}
149
150/// A trait for types that can be used to configure views.
151///
152/// View configurations are used by hooks to modify how views are rendered.
153pub trait ViewConfiguration: 'static {
154    // Note: the result would ignore any hook in the environment, to avoid infinite recursion.
155    /// The view type that this configuration produces.
156    type View: View;
157    /// Renders this configuration into a view.
158    fn render(self) -> Self::View;
159}
160
161// Note: Hook could change the behavior of the view dynamically based on the environment
162// only view implemented `ViewConfiguration` can be hooked.
163// A struct implemented `View` can be not concrete, but `ViewConfiguration` providing
164// `config()` method, which would return a concrete type.
165// By add `Hook<Config>` into `Environment`, a
166/// A function type for view hooks.
167type HookFn<C> = Box<dyn Fn(&Environment, C) -> AnyView>;
168
169/// A hook that can intercept and modify view configurations.
170///
171/// Hooks are used to apply global transformations to views based on their configuration.
172pub struct Hook<C>(HookFn<C>);
173
174impl<C> fmt::Debug for Hook<C> {
175    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
176        write!(f, "Modifier<{}>(..)", type_name::<C>())
177    }
178}
179
180impl<V, C, F> From<F> for Hook<C>
181where
182    C: ViewConfiguration,
183    V: View,
184    F: Fn(&Environment, C) -> V + 'static,
185{
186    fn from(value: F) -> Self {
187        Self(Box::new(move |env, config| {
188            let mut env = env.clone();
189            env.remove::<Self>(); // avoid infinite recursion
190            AnyView::new(Metadata::new(value(&env, config), env))
191        }))
192    }
193}
194
195impl<C> Hook<C>
196where
197    C: ViewConfiguration,
198{
199    /// Creates a new hook from a function.
200    ///
201    /// The function will be called with the environment and configuration
202    /// whenever a matching view configuration is encountered.
203    pub fn new<V, F>(f: F) -> Self
204    where
205        V: View,
206        F: Fn(&Environment, C) -> V + 'static,
207    {
208        Self::from(f)
209    }
210
211    /// Applies this hook to a configuration, producing a view.
212    pub fn apply(&self, env: &Environment, config: C) -> AnyView {
213        (self.0)(env, config)
214    }
215}
216
217impl<C: ViewConfiguration> Hook<C> {}
218
219macro_rules! impl_tuple_views {
220    ($($ty:ident),*) => {
221        #[allow(non_snake_case)]
222        #[allow(unused_variables)]
223        #[allow(unused_parens)]
224        impl <$($ty:View,)*>TupleViews for ($($ty,)*){
225            fn into_views(self) -> Vec<AnyView> {
226                // The trailing comma matters: `let (T) = self` is a parenthesized
227                // binding, not a one-element tuple pattern, so without it a
228                // single-child container erased the tuple itself instead of the
229                // view inside it — and the tuple answers every view question with
230                // its default, losing whatever the child had declared.
231                let ($($ty,)*)=self;
232                alloc::vec![$(AnyView::new($ty)),*]
233            }
234        }
235    };
236}
237
238tuples!(impl_tuple_views);
239
240raw_view!(());
241
242impl<V: View> View for (V,) {
243    fn body(self, _env: &Environment) -> impl View {
244        self.0
245    }
246}
247
248#[cfg(feature = "nightly")]
249impl View for ! {
250    fn body(self, _env: &Environment) -> impl View {}
251}