1use bevy::prelude::*;
4use std::collections::HashMap;
5use crate::styles::{colors, dimensions};
6use super::types::{
7 FormField, FieldType, ValidationRule, FormLayout,
8 FormRoot, FormSubmitButton
9};
10use super::field::spawn_form_field;
11
12pub struct FormBuilder {
14 id: String,
15 title: Option<String>,
16 fields: Vec<FormField>,
17 submit_text: String,
18 cancel_text: Option<String>,
19 layout: FormLayout,
20 width: Val,
21}
22
23impl FormBuilder {
24 pub fn new(id: impl Into<String>) -> Self {
26 Self {
27 id: id.into(),
28 title: None,
29 fields: Vec::new(),
30 submit_text: "Submit".to_string(),
31 cancel_text: None,
32 layout: FormLayout::Vertical,
33 width: Val::Px(400.0),
34 }
35 }
36
37 pub fn title(mut self, title: impl Into<String>) -> Self {
39 self.title = Some(title.into());
40 self
41 }
42
43 pub fn text_field(mut self, name: impl Into<String>, label: impl Into<String>) -> Self {
45 self.fields.push(FormField {
46 name: name.into(),
47 label: label.into(),
48 field_type: FieldType::Text,
49 validations: Vec::new(),
50 placeholder: None,
51 help_text: None,
52 disabled: false,
53 default_value: None,
54 });
55 self
56 }
57
58 pub fn password_field(mut self, name: impl Into<String>, label: impl Into<String>) -> Self {
60 self.fields.push(FormField {
61 name: name.into(),
62 label: label.into(),
63 field_type: FieldType::Password,
64 validations: Vec::new(),
65 placeholder: None,
66 help_text: None,
67 disabled: false,
68 default_value: None,
69 });
70 self
71 }
72
73 pub fn email_field(mut self, name: impl Into<String>, label: impl Into<String>) -> Self {
75 let field = FormField {
76 name: name.into(),
77 label: label.into(),
78 field_type: FieldType::Email,
79 validations: vec![ValidationRule::Email],
80 placeholder: Some("email@example.com".to_string()),
81 help_text: None,
82 disabled: false,
83 default_value: None,
84 };
85 self.fields.push(field);
86 self
87 }
88
89 pub fn number_field(
91 mut self,
92 name: impl Into<String>,
93 label: impl Into<String>,
94 min: Option<f32>,
95 max: Option<f32>,
96 ) -> Self {
97 self.fields.push(FormField {
98 name: name.into(),
99 label: label.into(),
100 field_type: FieldType::Number { min, max },
101 validations: Vec::new(),
102 placeholder: None,
103 help_text: None,
104 disabled: false,
105 default_value: None,
106 });
107 self
108 }
109
110 pub fn slider_field(
112 mut self,
113 name: impl Into<String>,
114 label: impl Into<String>,
115 min: f32,
116 max: f32,
117 ) -> Self {
118 self.fields.push(FormField {
119 name: name.into(),
120 label: label.into(),
121 field_type: FieldType::Slider { min, max, step: None },
122 validations: Vec::new(),
123 placeholder: None,
124 help_text: None,
125 disabled: false,
126 default_value: Some(min.to_string()),
127 });
128 self
129 }
130
131 pub fn dropdown_field(
133 mut self,
134 name: impl Into<String>,
135 label: impl Into<String>,
136 options: Vec<String>,
137 ) -> Self {
138 self.fields.push(FormField {
139 name: name.into(),
140 label: label.into(),
141 field_type: FieldType::Dropdown { options },
142 validations: Vec::new(),
143 placeholder: Some("Select an option".to_string()),
144 help_text: None,
145 disabled: false,
146 default_value: None,
147 });
148 self
149 }
150
151 pub fn checkbox_field(mut self, name: impl Into<String>, label: impl Into<String>) -> Self {
153 self.fields.push(FormField {
154 name: name.into(),
155 label: label.into(),
156 field_type: FieldType::Checkbox,
157 validations: Vec::new(),
158 placeholder: None,
159 help_text: None,
160 disabled: false,
161 default_value: Some("false".to_string()),
162 });
163 self
164 }
165
166 pub fn required(mut self) -> Self {
168 if let Some(field) = self.fields.last_mut() {
169 field.validations.push(ValidationRule::Required);
170 }
171 self
172 }
173
174 pub fn validate(mut self, rule: ValidationRule) -> Self {
176 if let Some(field) = self.fields.last_mut() {
177 field.validations.push(rule);
178 }
179 self
180 }
181
182 pub fn placeholder(mut self, text: impl Into<String>) -> Self {
184 if let Some(field) = self.fields.last_mut() {
185 field.placeholder = Some(text.into());
186 }
187 self
188 }
189
190 pub fn help_text(mut self, text: impl Into<String>) -> Self {
192 if let Some(field) = self.fields.last_mut() {
193 field.help_text = Some(text.into());
194 }
195 self
196 }
197
198 pub fn submit_text(mut self, text: impl Into<String>) -> Self {
200 self.submit_text = text.into();
201 self
202 }
203
204 pub fn cancel_text(mut self, text: impl Into<String>) -> Self {
206 self.cancel_text = Some(text.into());
207 self
208 }
209
210 pub fn layout(mut self, layout: FormLayout) -> Self {
212 self.layout = layout;
213 self
214 }
215
216 pub fn width(mut self, width: Val) -> Self {
218 self.width = width;
219 self
220 }
221
222 pub fn build(self, parent: &mut ChildSpawnerCommands) -> Entity {
224 let form_entity = parent
225 .spawn((
226 FormRoot {
227 id: self.id.clone(),
228 fields: self.fields.clone(),
229 is_valid: false,
230 values: HashMap::new(),
231 },
232 Node {
233 flex_direction: FlexDirection::Column,
234 width: self.width,
235 row_gap: Val::Px(dimensions::SPACING_MEDIUM),
236 padding: UiRect::all(Val::Px(dimensions::PADDING_LARGE)),
237 ..default()
238 },
239 BackgroundColor(colors::BACKGROUND_SECONDARY),
240 BorderRadius::all(Val::Px(dimensions::BORDER_RADIUS_MEDIUM)),
241 ))
242 .id();
243
244 let form_entity_copy = form_entity;
245
246 parent.commands().entity(form_entity).with_children(|form| {
247 if let Some(title) = self.title {
249 form.spawn((
250 Text::new(title),
251 TextFont {
252 font_size: dimensions::FONT_SIZE_HEADING,
253 ..default()
254 },
255 TextColor(colors::TEXT_PRIMARY),
256 Node {
257 margin: UiRect::bottom(Val::Px(dimensions::SPACING_LARGE)),
258 ..default()
259 },
260 ));
261 }
262
263 for field in &self.fields {
265 spawn_form_field(form, field);
266 }
267
268 form.spawn((
270 Node {
271 flex_direction: FlexDirection::Row,
272 justify_content: JustifyContent::End,
273 column_gap: Val::Px(dimensions::SPACING_MEDIUM),
274 margin: UiRect::top(Val::Px(dimensions::SPACING_LARGE)),
275 ..default()
276 },
277 BackgroundColor(Color::NONE),
278 ))
279 .with_children(|buttons| {
280 if let Some(cancel_text) = self.cancel_text {
282 crate::button::secondary_button(cancel_text).build(buttons);
283 }
284
285 let submit_button = crate::button::primary_button(self.submit_text)
287 .build(buttons);
288
289 buttons.commands()
290 .entity(submit_button)
291 .insert(FormSubmitButton { form_entity: form_entity_copy });
292 });
293 });
294
295 form_entity
296 }
297}