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}