waterui_form/
lib.rs

1//! # `WaterUI` Form Components
2//!
3//! This crate provides a comprehensive form system for `WaterUI` applications with ergonomic
4//! macros and type-safe form building capabilities.
5
6#![no_std]
7extern crate alloc;
8
9use waterui_color::Color;
10
11/// Picker form component module.
12pub mod picker;
13pub mod valid;
14
15use waterui_core::{AnyView, Binding, Str, View};
16
17/// Trait for types that can be automatically converted to form UI components.
18///
19/// This trait enables ergonomic form creation by mapping Rust data structures
20/// to interactive UI components. It can be implemented manually for custom layouts
21/// or automatically derived using `#[derive(FormBuilder)]`.
22///
23/// # Automatic Implementation via Derive Macro
24///
25/// The most convenient way to use this trait is through the derive macro:
26///
27/// ```text
28/// use waterui_form::FormBuilder;
29///
30/// #[derive(Default, Clone, Debug, FormBuilder)]
31/// pub struct UserProfile {
32///     /// User's display name
33///     pub name: String,
34///     /// Account active status
35///     pub active: bool,
36///     /// User's current level
37///     pub level: i32,
38/// }
39/// ```
40///
41/// This automatically generates appropriate form components for each field type.
42///
43/// # Manual Implementation
44///
45/// For custom layouts or specialized form behavior, implement the trait manually:
46///
47/// ```text
48/// use waterui_form::FormBuilder;
49/// use waterui::prelude::*;
50///
51/// struct CustomForm {
52///     title: String,
53///     enabled: bool,
54/// }
55///
56/// impl FormBuilder for CustomForm {
57///     type View = VStack;
58///
59///     fn view(binding: &Binding<Self>) -> Self::View {
60///         vstack((
61///             TextField::new(&binding.title),
62///             Toggle::new(&binding.enabled),
63///         ))
64///     }
65/// }
66/// ```
67pub trait FormBuilder: Sized {
68    /// The view type that represents this form field.
69    ///
70    /// This associated type determines what UI component will be rendered
71    /// for this form. For derived implementations, this is typically a
72    /// layout containing multiple form fields.
73    type View: View;
74
75    /// Creates a view representation of this form field bound to the given binding.
76    ///
77    /// This method is the core of the form building system. It takes a binding
78    /// to the form data and returns a view that displays interactive form controls.
79    ///
80    /// # Parameters
81    ///
82    /// * `binding` - A reference to a binding that holds the form's data state
83    ///
84    /// # Returns
85    ///
86    /// A view that renders the form's UI components, automatically bound to the data
87    fn view(binding: &Binding<Self>, label: AnyView, placeholder: Str) -> Self::View;
88
89    /// Creates a new binding with the default value for this form.
90    ///
91    /// This convenience method creates a new binding initialized with the default
92    /// values for all form fields. It requires the form type to implement
93    /// [`Default`] and [`Clone`].
94    ///
95    /// # Examples
96    ///
97    /// ```text
98    /// # use waterui_form::FormBuilder;
99    /// # #[derive(Default, Clone, FormBuilder)]
100    /// # struct LoginForm { username: String, password: String }
101    /// let form_binding = LoginForm::binding();
102    /// // form_binding now holds a LoginForm with default values
103    /// ```
104    #[must_use]
105    fn binding() -> Binding<Self>
106    where
107        Self: Default + Clone,
108    {
109        Binding::default()
110    }
111}
112
113// TextField has prompt, so handle it specially
114impl FormBuilder for Str {
115    type View = waterui_controls::TextField;
116    #[allow(unused_variables)]
117    fn view(binding: &Binding<Self>, label: AnyView, placeholder: Str) -> Self::View {
118        let mut field = waterui_controls::TextField::new(binding).label(label);
119        if !placeholder.is_empty() {
120            field = field.prompt(placeholder);
121        }
122        field
123    }
124}
125
126// String also uses TextField with prompt
127impl FormBuilder for alloc::string::String {
128    type View = waterui_controls::TextField;
129
130    fn view(binding: &Binding<Self>, label: AnyView, placeholder: Str) -> Self::View {
131        use alloc::string::ToString;
132        use nami::Binding as NamiBinding;
133        // Map String to Str binding
134        let str_binding = NamiBinding::mapping(binding, Str::from, |binding, str_val: Str| {
135            *binding.get_mut() = str_val.to_string();
136        });
137        let mut field = waterui_controls::TextField::new(&str_binding).label(label);
138        if !placeholder.is_empty() {
139            field = field.prompt(placeholder);
140        }
141        field
142    }
143}
144
145// Other components don't have prompt
146impl FormBuilder for i32 {
147    type View = waterui_controls::Stepper;
148    #[allow(unused_variables)]
149    fn view(binding: &Binding<Self>, label: AnyView, placeholder: Str) -> Self::View {
150        waterui_controls::Stepper::new(binding)
151            .label(label)
152            .range(Self::MIN..=Self::MAX)
153    }
154}
155
156impl FormBuilder for bool {
157    type View = waterui_controls::Toggle;
158    #[allow(unused_variables)]
159    fn view(binding: &Binding<Self>, label: AnyView, placeholder: Str) -> Self::View {
160        waterui_controls::Toggle::new(binding).label(label)
161    }
162}
163
164impl FormBuilder for Color {
165    type View = picker::ColorPicker;
166    #[allow(unused_variables)]
167    fn view(binding: &Binding<Self>, label: AnyView, placeholder: Str) -> Self::View {
168        picker::ColorPicker::new(binding).label(label)
169    }
170}
171
172impl FormBuilder for f64 {
173    type View = waterui_controls::Slider;
174    #[allow(unused_variables)]
175    fn view(binding: &Binding<Self>, label: AnyView, placeholder: Str) -> Self::View {
176        waterui_controls::Slider::new(0.0..=1.0, binding).label(label)
177    }
178}
179
180impl FormBuilder for f32 {
181    type View = waterui_controls::Slider;
182    #[allow(unused_variables)]
183    fn view(binding: &Binding<Self>, label: AnyView, placeholder: Str) -> Self::View {
184        waterui_controls::Slider::new(
185            0.0..=1.0,
186            #[allow(clippy::cast_possible_truncation)]
187            &Binding::mapping(binding, f64::from, |binding, val| binding.set(val as Self)),
188        )
189        .label(label)
190    }
191}
192
193/// Secure form components for handling sensitive data like passwords.
194pub mod secure;
195pub use secure::{SecureField, secure};
196
197/// Creates a form view from a binding to a type that implements [`FormBuilder`].
198///
199/// This is the primary entry point for creating forms using the `FormBuilder` system.
200/// It takes a binding to your form data structure and returns a view that renders
201/// all the appropriate form controls.
202///
203/// # Examples
204///
205/// ## Basic Usage
206///
207/// ```text
208/// use waterui_form::{FormBuilder, form};
209/// use waterui_core::{Binding, View};
210///
211/// #[derive(Default, Clone, Debug, FormBuilder)]
212/// struct ContactForm {
213///     name: String,
214///     email: String,
215///     age: i32,
216///     newsletter: bool,
217/// }
218///
219/// fn contact_form_view() -> impl View {
220///     let form_binding = ContactForm::binding();
221///     form(&form_binding)
222/// }
223/// ```
224///
225/// ## With Custom Initialization
226///
227/// ```text
228/// # use waterui_form::{FormBuilder, form};
229/// # use waterui_core::{Binding, View};
230/// # #[derive(Default, Clone, Debug, FormBuilder)]
231/// # struct ContactForm { name: String, email: String }
232/// fn pre_filled_form() -> impl View {
233///     let initial_data = ContactForm {
234///         name: "John Doe".to_string(),
235///         email: "john@example.com".to_string(),
236///     };
237///     let form_binding = Binding::new(initial_data);
238///     form(&form_binding)
239/// }
240/// ```
241///
242/// ## Accessing Form Data
243///
244/// ```text
245/// # use waterui_form::{FormBuilder, form};
246/// # use waterui_core::{Binding, View};
247/// # use waterui_layout::vstack;
248/// # use waterui_text::text;
249/// # #[derive(Default, Clone, Debug, FormBuilder)]
250/// # struct ContactForm { name: String, email: String }
251/// fn form_with_output() -> impl View {
252///     let form_binding = ContactForm::binding();
253///
254///     vstack((
255///         form(&form_binding),
256///         // Display current form values
257///         text!(format!("Name: {}", form_binding.name.get())),
258///         text!(format!("Email: {}", form_binding.email.get())),
259///     ))
260/// }
261/// ```
262///
263/// # Parameters
264///
265/// * `binding` - A reference to a binding containing the form's data
266///
267/// # Returns
268///
269/// A view that renders interactive form controls for all fields in the bound data structure
270#[must_use]
271pub fn form<T: FormBuilder>(binding: &Binding<T>) -> T::View {
272    T::view(binding, AnyView::default(), Str::default())
273}