Skip to main content

simple_form/
simple_form.rs

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}