1use operad::native::{NativeWindowOptions, NativeWindowResult};
2use operad::widgets::{self, TextInputOptions, TextInputState};
3use operad::{
4 root_style, ColorRgba, LayoutStyle, TextStyle, UiDocument, UiNode, UiNodeId, UiSize, UiVisual,
5 WidgetAction, WidgetActionKind,
6};
7
8fn main() -> NativeWindowResult {
9 operad::native::run_app_with(
10 NativeWindowOptions::new("Simple form").with_min_size(420.0, 300.0),
11 FormApp::default(),
12 FormApp::update,
13 FormApp::view,
14 )
15}
16
17struct FormApp {
18 name: TextInputState,
19 email: TextInputState,
20 submitted: String,
21}
22
23impl Default for FormApp {
24 fn default() -> Self {
25 Self {
26 name: TextInputState::new(""),
27 email: TextInputState::new(""),
28 submitted: String::new(),
29 }
30 }
31}
32
33impl FormApp {
34 fn update(&mut self, action: WidgetAction) {
35 let Some(action_id) = action.binding.action_id().map(|id| id.as_str()) else {
36 return;
37 };
38 match action_id {
39 "form.name.edit" => apply_text_edit(&mut self.name, &action.kind),
40 "form.email.edit" => apply_text_edit(&mut self.email, &action.kind),
41 "form.submit" => {
42 self.submitted = format!("Submitted {} <{}>", self.name.text(), self.email.text());
43 }
44 _ => {}
45 }
46 }
47
48 fn view(&self, viewport: UiSize) -> UiDocument {
49 let mut ui = UiDocument::new(root_style(viewport.width, viewport.height));
50 let panel = app_panel(&mut ui, "form.panel", viewport, 420.0, 260.0);
51 widgets::label(
52 &mut ui,
53 panel,
54 "form.title",
55 "Simple form",
56 heading(),
57 LayoutStyle::new().with_width_percent(1.0).with_height(32.0),
58 );
59 form_field(&mut ui, panel, "Name", "form.name", &self.name);
60 form_field(&mut ui, panel, "Email", "form.email", &self.email);
61 widgets::button(
62 &mut ui,
63 panel,
64 "form.submit",
65 "Submit",
66 widgets::ButtonOptions::default().with_action("form.submit"),
67 );
68 widgets::label(
69 &mut ui,
70 panel,
71 "form.status",
72 if self.submitted.is_empty() {
73 "Enter values and submit."
74 } else {
75 &self.submitted
76 },
77 muted(),
78 LayoutStyle::new().with_width_percent(1.0).with_height(28.0),
79 );
80 ui
81 }
82}
83
84fn form_field(
85 ui: &mut UiDocument,
86 parent: UiNodeId,
87 label: &str,
88 name: &str,
89 state: &TextInputState,
90) {
91 let row = ui.add_child(
92 parent,
93 UiNode::container(
94 format!("{name}.row"),
95 LayoutStyle::row()
96 .with_width_percent(1.0)
97 .with_height(38.0)
98 .with_gap(8.0),
99 ),
100 );
101 widgets::label(
102 ui,
103 row,
104 format!("{name}.label"),
105 label,
106 TextStyle::default(),
107 LayoutStyle::size(76.0, 32.0),
108 );
109 widgets::text_input(
110 ui,
111 row,
112 name,
113 state,
114 TextInputOptions::default()
115 .with_layout(LayoutStyle::new().with_width(260.0).with_height(32.0))
116 .with_edit_action(format!("{name}.edit")),
117 );
118}
119
120fn apply_text_edit(state: &mut TextInputState, kind: &WidgetActionKind) {
121 if let WidgetActionKind::TextEdit(edit) = kind {
122 state.apply_widget_text_edit(edit, &TextInputOptions::default());
123 }
124}
125
126fn app_panel(
127 ui: &mut UiDocument,
128 name: &str,
129 viewport: UiSize,
130 width: f32,
131 height: f32,
132) -> UiNodeId {
133 ui.add_child(
134 ui.root(),
135 UiNode::container(
136 name,
137 LayoutStyle::column()
138 .with_size(width.min(viewport.width.max(1.0)), height)
139 .with_padding(16.0)
140 .with_gap(10.0),
141 )
142 .with_visual(UiVisual::panel(ColorRgba::new(24, 29, 36, 255), None, 6.0)),
143 )
144}
145
146fn heading() -> TextStyle {
147 TextStyle {
148 font_size: 22.0,
149 line_height: 30.0,
150 color: ColorRgba::WHITE,
151 ..TextStyle::default()
152 }
153}
154
155fn muted() -> TextStyle {
156 TextStyle {
157 color: ColorRgba::new(166, 178, 196, 255),
158 ..TextStyle::default()
159 }
160}