1use egui::Ui;
18
19use crate::common::Size;
20use crate::label::{Label, LabelTone};
21
22pub struct Form {
23 field_gap: f32,
24}
25
26impl Default for Form {
27 fn default() -> Self {
28 Self::new()
29 }
30}
31
32impl Form {
33 pub fn new() -> Self {
34 Self { field_gap: 14.0 }
35 }
36 pub fn field_gap(mut self, gap: f32) -> Self {
38 self.field_gap = gap;
39 self
40 }
41
42 pub fn show(self, ui: &mut Ui, build: impl FnOnce(&mut FormUi)) {
43 let mut form = FormUi {
44 ui,
45 field_gap: self.field_gap,
46 first: true,
47 };
48 build(&mut form);
49 }
50}
51
52pub struct FormUi<'a> {
54 ui: &'a mut Ui,
55 field_gap: f32,
56 first: bool,
57}
58
59impl FormUi<'_> {
60 fn header(&mut self, label: &str, required: bool) {
61 if !self.first {
62 self.ui.add_space(self.field_gap);
63 }
64 self.first = false;
65 self.ui.horizontal(|ui| {
66 ui.add(Label::new(label.to_string()).strong().size(Size::Small));
67 if required {
68 ui.add(
69 Label::new("*")
70 .tone(LabelTone::Danger)
71 .size(Size::Small),
72 );
73 }
74 });
75 self.ui.add_space(4.0);
76 }
77
78 pub fn field(&mut self, label: impl AsRef<str>, content: impl FnOnce(&mut Ui)) {
80 self.header(label.as_ref(), false);
81 content(self.ui);
82 }
83
84 pub fn required(&mut self, label: impl AsRef<str>, content: impl FnOnce(&mut Ui)) {
86 self.header(label.as_ref(), true);
87 content(self.ui);
88 }
89
90 pub fn field_with_hint(
92 &mut self,
93 label: impl AsRef<str>,
94 hint: impl Into<String>,
95 content: impl FnOnce(&mut Ui),
96 ) {
97 self.header(label.as_ref(), false);
98 content(self.ui);
99 self.ui.add_space(3.0);
100 self.ui.add(Label::new(hint.into()).muted().size(Size::Small));
101 }
102
103 pub fn raw(&mut self, content: impl FnOnce(&mut Ui)) {
106 if !self.first {
107 self.ui.add_space(self.field_gap);
108 }
109 self.first = false;
110 content(self.ui);
111 }
112
113 pub fn ui(&mut self) -> &mut Ui {
115 self.ui
116 }
117}