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}