1use crate::items::{Button, Dropdown, RadioGroup, Slider, TextBox, Toggle, UiItem};
11use crate::loader::{
12 DropdownAction, PressAction, RadioAction, SliderAction, TextBoxAction, ToggleAction,
13};
14use crate::styles::StyleId;
15use crate::widgets::{
16 ButtonState, DropdownState, ItemState, RadioGroupState, SliderState, TextBoxState,
17};
18
19pub trait WidgetBuilder {
21 fn build(self, default_style: Option<StyleId>) -> (UiItem, ItemState);
22}
23
24pub struct ButtonBuilder {
27 id: String,
28 x: f32,
29 y: f32,
30 w: f32,
31 h: f32,
32 text: String,
33 style: Option<StyleId>,
34 tooltip: Option<String>,
35 action: PressAction,
36 disabled: bool,
37}
38
39impl ButtonBuilder {
40 #[must_use]
41 pub fn new(id: impl Into<String>) -> Self {
42 Self {
43 id: id.into(),
44 x: 0.0,
45 y: 0.0,
46 w: 100.0,
47 h: 40.0,
48 text: String::new(),
49 style: None,
50 tooltip: None,
51 action: PressAction::Custom(String::new()),
52 disabled: false,
53 }
54 }
55 #[must_use]
56 pub const fn pos(mut self, x: f32, y: f32) -> Self {
57 self.x = x;
58 self.y = y;
59 self
60 }
61 #[must_use]
62 pub const fn size(mut self, w: f32, h: f32) -> Self {
63 self.w = w;
64 self.h = h;
65 self
66 }
67 #[must_use]
68 pub fn text(mut self, t: impl Into<String>) -> Self {
69 self.text = t.into();
70 self
71 }
72 #[must_use]
73 pub const fn style(mut self, s: StyleId) -> Self {
74 self.style = Some(s);
75 self
76 }
77 #[must_use]
78 pub fn tooltip(mut self, t: impl Into<String>) -> Self {
79 self.tooltip = Some(t.into());
80 self
81 }
82 #[must_use]
83 pub fn on_press(mut self, tag: impl Into<String>) -> Self {
84 self.action = PressAction::Custom(tag.into());
85 self
86 }
87 #[must_use]
88 pub const fn disabled(mut self, d: bool) -> Self {
89 self.disabled = d;
90 self
91 }
92}
93
94impl WidgetBuilder for ButtonBuilder {
95 fn build(self, default_style: Option<StyleId>) -> (UiItem, ItemState) {
96 let style = self.style.or(default_style).unwrap_or(StyleId::new(0));
97 let item = Button {
98 id: self.id,
99 x: self.x,
100 y: self.y,
101 width: self.w,
102 height: self.h,
103 text: self.text,
104 style,
105 tooltip: self.tooltip,
106 action: self.action,
107 disabled: self.disabled,
108 nav_default: false,
109 };
110 (UiItem::Button(item), ItemState::Button(ButtonState::new()))
111 }
112}
113
114pub struct ToggleBuilder {
117 id: String,
118 x: f32,
119 y: f32,
120 w: f32,
121 h: f32,
122 text: String,
123 style_off: Option<StyleId>,
124 style_on: Option<StyleId>,
125 tooltip: Option<String>,
126 action: ToggleAction,
127 default_checked: bool,
128 disabled: bool,
129}
130
131impl ToggleBuilder {
132 #[must_use]
133 pub fn new(id: impl Into<String>) -> Self {
134 Self {
135 id: id.into(),
136 x: 0.0,
137 y: 0.0,
138 w: 100.0,
139 h: 40.0,
140 text: String::new(),
141 style_off: None,
142 style_on: None,
143 tooltip: None,
144 action: ToggleAction::Custom(String::new()),
145 default_checked: false,
146 disabled: false,
147 }
148 }
149 #[must_use]
150 pub const fn pos(mut self, x: f32, y: f32) -> Self {
151 self.x = x;
152 self.y = y;
153 self
154 }
155 #[must_use]
156 pub const fn size(mut self, w: f32, h: f32) -> Self {
157 self.w = w;
158 self.h = h;
159 self
160 }
161 #[must_use]
162 pub fn text(mut self, t: impl Into<String>) -> Self {
163 self.text = t.into();
164 self
165 }
166 #[must_use]
167 pub const fn style_off(mut self, s: StyleId) -> Self {
168 self.style_off = Some(s);
169 self
170 }
171 #[must_use]
172 pub const fn style_on(mut self, s: StyleId) -> Self {
173 self.style_on = Some(s);
174 self
175 }
176 #[must_use]
177 pub const fn checked(mut self, c: bool) -> Self {
178 self.default_checked = c;
179 self
180 }
181 #[must_use]
182 pub fn on_change(mut self, tag: impl Into<String>) -> Self {
183 self.action = ToggleAction::Custom(tag.into());
184 self
185 }
186}
187
188impl WidgetBuilder for ToggleBuilder {
189 fn build(self, default_style: Option<StyleId>) -> (UiItem, ItemState) {
190 let fb = default_style.unwrap_or(StyleId::new(0));
191 let item = Toggle {
192 id: self.id,
193 x: self.x,
194 y: self.y,
195 width: self.w,
196 height: self.h,
197 text: self.text,
198 tooltip: self.tooltip,
199 default_checked: self.default_checked,
200 disabled: self.disabled,
201 style_off: self.style_off.unwrap_or(fb),
202 style_on: self.style_on.unwrap_or(fb),
203 action: self.action,
204 };
205 let state = ButtonState::for_toggle(&item);
206 (UiItem::Toggle(item), ItemState::Toggle(state))
207 }
208}
209
210pub struct SliderBuilder {
213 id: String,
214 x: f32,
215 y: f32,
216 w: f32,
217 h: f32,
218 min: f32,
219 max: f32,
220 default_value: f32,
221 step: Option<f32>,
222 style_track: Option<StyleId>,
223 style_thumb: Option<StyleId>,
224 tooltip: Option<String>,
225 action: SliderAction,
226}
227
228impl SliderBuilder {
229 #[must_use]
230 pub fn new(id: impl Into<String>) -> Self {
231 Self {
232 id: id.into(),
233 x: 0.0,
234 y: 0.0,
235 w: 200.0,
236 h: 40.0,
237 min: 0.0,
238 max: 1.0,
239 default_value: 0.0,
240 step: None,
241 style_track: None,
242 style_thumb: None,
243 tooltip: None,
244 action: SliderAction::Custom(String::new()),
245 }
246 }
247 #[must_use]
248 pub const fn pos(mut self, x: f32, y: f32) -> Self {
249 self.x = x;
250 self.y = y;
251 self
252 }
253 #[must_use]
254 pub const fn size(mut self, w: f32, h: f32) -> Self {
255 self.w = w;
256 self.h = h;
257 self
258 }
259 #[must_use]
260 pub const fn range(mut self, min: f32, max: f32) -> Self {
261 self.min = min;
262 self.max = max;
263 self
264 }
265 #[must_use]
266 pub const fn value(mut self, v: f32) -> Self {
267 self.default_value = v;
268 self
269 }
270 #[must_use]
271 pub const fn step(mut self, s: f32) -> Self {
272 self.step = Some(s);
273 self
274 }
275 #[must_use]
276 pub const fn style_track(mut self, s: StyleId) -> Self {
277 self.style_track = Some(s);
278 self
279 }
280 #[must_use]
281 pub const fn style_thumb(mut self, s: StyleId) -> Self {
282 self.style_thumb = Some(s);
283 self
284 }
285 #[must_use]
286 pub fn on_change(mut self, tag: impl Into<String>) -> Self {
287 self.action = SliderAction::Custom(tag.into());
288 self
289 }
290}
291
292impl WidgetBuilder for SliderBuilder {
293 fn build(self, default_style: Option<StyleId>) -> (UiItem, ItemState) {
294 let fb = default_style.unwrap_or(StyleId::new(0));
295 let item = Slider {
296 id: self.id,
297 x: self.x,
298 y: self.y,
299 width: self.w,
300 height: self.h,
301 min: self.min,
302 max: self.max,
303 default_value: self.default_value.clamp(self.min, self.max),
304 step: self.step,
305 tooltip: self.tooltip,
306 style_track: self.style_track.unwrap_or(fb),
307 style_thumb: self.style_thumb.unwrap_or(fb),
308 action: self.action,
309 };
310 let state = SliderState::for_slider(&item);
311 (UiItem::Slider(item), ItemState::Slider(state))
312 }
313}
314
315pub struct TextBoxBuilder {
318 id: String,
319 x: f32,
320 y: f32,
321 w: f32,
322 h: f32,
323 default_text: String,
324 placeholder: String,
325 max_len: Option<usize>,
326 style_idle: Option<StyleId>,
327 style_focus: Option<StyleId>,
328 tooltip: Option<String>,
329 on_change: TextBoxAction,
330 on_submit: TextBoxAction,
331 password: bool,
332 multiline: bool,
333 font_size: Option<f32>,
334}
335
336impl TextBoxBuilder {
337 #[must_use]
338 pub fn new(id: impl Into<String>) -> Self {
339 Self {
340 id: id.into(),
341 x: 0.0,
342 y: 0.0,
343 w: 200.0,
344 h: 40.0,
345 default_text: String::new(),
346 placeholder: String::new(),
347 max_len: None,
348 style_idle: None,
349 style_focus: None,
350 tooltip: None,
351 on_change: TextBoxAction::Custom(String::new()),
352 on_submit: TextBoxAction::Custom(String::new()),
353 password: false,
354 multiline: false,
355 font_size: None,
356 }
357 }
358 #[must_use]
359 pub const fn pos(mut self, x: f32, y: f32) -> Self {
360 self.x = x;
361 self.y = y;
362 self
363 }
364 #[must_use]
365 pub const fn size(mut self, w: f32, h: f32) -> Self {
366 self.w = w;
367 self.h = h;
368 self
369 }
370 #[must_use]
371 pub fn text(mut self, t: impl Into<String>) -> Self {
372 self.default_text = t.into();
373 self
374 }
375 #[must_use]
376 pub fn placeholder(mut self, t: impl Into<String>) -> Self {
377 self.placeholder = t.into();
378 self
379 }
380 #[must_use]
381 pub const fn max_len(mut self, n: usize) -> Self {
382 self.max_len = Some(n);
383 self
384 }
385 #[must_use]
386 pub const fn style(mut self, s: StyleId) -> Self {
387 self.style_idle = Some(s);
388 self
389 }
390 #[must_use]
391 pub const fn style_focus(mut self, s: StyleId) -> Self {
392 self.style_focus = Some(s);
393 self
394 }
395 #[must_use]
396 pub const fn password(mut self, p: bool) -> Self {
397 self.password = p;
398 self
399 }
400 #[must_use]
401 pub const fn multiline(mut self, m: bool) -> Self {
402 self.multiline = m;
403 self
404 }
405 #[must_use]
406 pub fn on_change(mut self, tag: impl Into<String>) -> Self {
407 self.on_change = TextBoxAction::Custom(tag.into());
408 self
409 }
410 #[must_use]
411 pub fn on_submit(mut self, tag: impl Into<String>) -> Self {
412 self.on_submit = TextBoxAction::Custom(tag.into());
413 self
414 }
415}
416
417impl WidgetBuilder for TextBoxBuilder {
418 fn build(self, default_style: Option<StyleId>) -> (UiItem, ItemState) {
419 let fb = default_style.unwrap_or(StyleId::new(0));
420 let style_idle = self.style_idle.unwrap_or(fb);
421 let item = TextBox {
422 id: self.id,
423 x: self.x,
424 y: self.y,
425 width: self.w,
426 height: self.h,
427 default_text: self.default_text,
428 placeholder: self.placeholder,
429 max_len: self.max_len,
430 tooltip: self.tooltip,
431 style_idle,
432 style_focus: self.style_focus.unwrap_or(style_idle),
433 on_change: self.on_change,
434 on_submit: self.on_submit,
435 password: self.password,
436 multiline: self.multiline,
437 font_size: self.font_size,
438 };
439 let state = TextBoxState::for_textbox(&item);
440 (UiItem::TextBox(item), ItemState::TextBox(state))
441 }
442}
443
444pub struct DropdownBuilder {
447 id: String,
448 x: f32,
449 y: f32,
450 w: f32,
451 h: f32,
452 options: Vec<String>,
453 default_selected: usize,
454 style: Option<StyleId>,
455 style_list: Option<StyleId>,
456 style_item: Option<StyleId>,
457 action: DropdownAction,
458}
459
460impl DropdownBuilder {
461 #[must_use]
462 pub fn new(id: impl Into<String>) -> Self {
463 Self {
464 id: id.into(),
465 x: 0.0,
466 y: 0.0,
467 w: 200.0,
468 h: 40.0,
469 options: Vec::new(),
470 default_selected: 0,
471 style: None,
472 style_list: None,
473 style_item: None,
474 action: DropdownAction::Custom(String::new()),
475 }
476 }
477 #[must_use]
478 pub const fn pos(mut self, x: f32, y: f32) -> Self {
479 self.x = x;
480 self.y = y;
481 self
482 }
483 #[must_use]
484 pub const fn size(mut self, w: f32, h: f32) -> Self {
485 self.w = w;
486 self.h = h;
487 self
488 }
489 #[must_use]
490 pub fn options<I, S>(mut self, opts: I) -> Self
491 where
492 I: IntoIterator<Item = S>,
493 S: Into<String>,
494 {
495 self.options = opts.into_iter().map(Into::into).collect();
496 self
497 }
498 #[must_use]
499 pub const fn selected(mut self, i: usize) -> Self {
500 self.default_selected = i;
501 self
502 }
503 #[must_use]
504 pub const fn style(mut self, s: StyleId) -> Self {
505 self.style = Some(s);
506 self
507 }
508 #[must_use]
509 pub fn on_change(mut self, tag: impl Into<String>) -> Self {
510 self.action = DropdownAction::Custom(tag.into());
511 self
512 }
513}
514
515impl WidgetBuilder for DropdownBuilder {
516 fn build(self, default_style: Option<StyleId>) -> (UiItem, ItemState) {
517 let fb = default_style.unwrap_or(StyleId::new(0));
518 let header_style = self.style.unwrap_or(fb);
519 let item_style = self.style_item.unwrap_or(header_style);
520 let n = self.options.len();
521 let opt_h = self.h;
522 let items: Vec<Button> = self
523 .options
524 .iter()
525 .enumerate()
526 .map(|(i, label)| Button {
527 id: format!("{}__opt_{}", self.id, i),
528 x: 0.0,
529 y: (i as f32 + 1.0) * opt_h,
530 width: self.w,
531 height: opt_h,
532 text: label.clone(),
533 style: item_style,
534 tooltip: None,
535 action: PressAction::DropdownSelect {
536 dropdown_id: self.id.clone(),
537 index: i,
538 },
539 disabled: false,
540 nav_default: false,
541 })
542 .collect();
543 let item = Dropdown {
544 id: self.id,
545 x: self.x,
546 y: self.y,
547 width: self.w,
548 height: self.h,
549 default_selected: self.default_selected.min(n.saturating_sub(1)),
550 options: self.options,
551 style: header_style,
552 style_list: self.style_list,
553 action: self.action,
554 items,
555 };
556 let state = DropdownState::for_dropdown(&item);
557 (UiItem::Dropdown(item), ItemState::Dropdown(state))
558 }
559}
560
561pub struct RadioGroupBuilder {
564 id: String,
565 x: f32,
566 y: f32,
567 w: f32,
568 h: f32,
569 options: Vec<String>,
570 default_selected: usize,
571 style_idle: Option<StyleId>,
572 style_selected: Option<StyleId>,
573 gap: f32,
574 action: RadioAction,
575}
576
577impl RadioGroupBuilder {
578 #[must_use]
579 pub fn new(id: impl Into<String>) -> Self {
580 Self {
581 id: id.into(),
582 x: 0.0,
583 y: 0.0,
584 w: 200.0,
585 h: 120.0,
586 options: Vec::new(),
587 default_selected: 0,
588 style_idle: None,
589 style_selected: None,
590 gap: 4.0,
591 action: RadioAction::Custom(String::new()),
592 }
593 }
594 #[must_use]
595 pub const fn pos(mut self, x: f32, y: f32) -> Self {
596 self.x = x;
597 self.y = y;
598 self
599 }
600 #[must_use]
601 pub const fn size(mut self, w: f32, h: f32) -> Self {
602 self.w = w;
603 self.h = h;
604 self
605 }
606 #[must_use]
607 pub fn options<I, S>(mut self, opts: I) -> Self
608 where
609 I: IntoIterator<Item = S>,
610 S: Into<String>,
611 {
612 self.options = opts.into_iter().map(Into::into).collect();
613 self
614 }
615 #[must_use]
616 pub const fn selected(mut self, i: usize) -> Self {
617 self.default_selected = i;
618 self
619 }
620 #[must_use]
621 pub const fn style_idle(mut self, s: StyleId) -> Self {
622 self.style_idle = Some(s);
623 self
624 }
625 #[must_use]
626 pub const fn style_selected(mut self, s: StyleId) -> Self {
627 self.style_selected = Some(s);
628 self
629 }
630 #[must_use]
631 pub const fn gap(mut self, g: f32) -> Self {
632 self.gap = g;
633 self
634 }
635 #[must_use]
636 pub fn on_change(mut self, tag: impl Into<String>) -> Self {
637 self.action = RadioAction::Custom(tag.into());
638 self
639 }
640}
641
642impl WidgetBuilder for RadioGroupBuilder {
643 fn build(self, default_style: Option<StyleId>) -> (UiItem, ItemState) {
644 let fb = default_style.unwrap_or(StyleId::new(0));
645 let style_idle = self.style_idle.unwrap_or(fb);
646 let style_selected = self.style_selected.unwrap_or(style_idle);
647 let n = self.options.len();
648 let total_gap = self.gap * n.saturating_sub(1) as f32;
649 let opt_h = if n > 0 {
650 (self.h - total_gap) / n as f32
651 } else {
652 self.h
653 };
654 let selected = self.default_selected.min(n.saturating_sub(1));
655 let items: Vec<Button> = self
656 .options
657 .iter()
658 .enumerate()
659 .map(|(i, label)| Button {
660 id: format!("{}__opt_{}", self.id, i),
661 x: 0.0,
662 y: i as f32 * (opt_h + self.gap),
663 width: self.w,
664 height: opt_h,
665 text: label.clone(),
666 style: if i == selected {
667 style_selected
668 } else {
669 style_idle
670 },
671 tooltip: None,
672 action: PressAction::RadioSelect {
673 group_id: self.id.clone(),
674 index: i,
675 },
676 disabled: false,
677 nav_default: i == selected,
678 })
679 .collect();
680 let item = RadioGroup {
681 id: self.id,
682 default_selected: selected,
683 options: self.options,
684 style_idle,
685 style_selected,
686 action: self.action,
687 items,
688 x: self.x,
689 y: self.y,
690 };
691 let state = RadioGroupState::for_radio(&item);
692 (UiItem::RadioGroup(item), ItemState::RadioGroup(state))
693 }
694}