-
Notifications
You must be signed in to change notification settings - Fork 9
Guide Recipes Settings Form
MeowLynxSea edited this page Jun 18, 2026
·
3 revisions
A real settings form using all 7 text inputs, Form, FormField, and inline validation.
#[derive(Default)]
pub struct SettingsState {
pub email: String,
pub email_error: Option<String>,
pub password: String,
pub number: f64,
pub search: String,
pub workspace_path: Option<PathBuf>,
pub keybinding: String,
pub notes: String,
}use std::collections::HashMap;
use yororen_ui::headless::file_path_input::file_path_input;
use yororen_ui::headless::form::form;
use yororen_ui::headless::form_field::form_field;
use yororen_ui::headless::keybinding_input::{KeybindingInputMode, keybinding_input};
use yororen_ui::headless::number_input::number_input;
use yororen_ui::headless::password_input::password_input;
use yororen_ui::headless::search_input::search_input;
use yororen_ui::headless::text_area::text_area;
use yororen_ui::headless::text_input::text_input;
impl Render for SettingsPage {
fn render(&mut self, _w: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
let entity = cx.entity();
// Snapshot once
let state = entity.read(cx);
let email = state.email.clone();
let pw = state.password.clone();
let number = state.number;
let email_error = state.email_error.clone();
drop(state);
// Form scaffold
let entity_for_form = entity.clone();
let form_el = form("settings-form", &mut *cx)
.value("email", email.clone())
.value("password", pw.clone())
.value("number", number.to_string())
.error("email", email_error.as_deref())
.submit("Save")
.on_submit(move |vals, _w, cx| {
entity_for_form.update(cx, |s, _| {
if let Some(e) = vals.get("email") {
s.email = e.to_string();
s.email_error = if e.contains('@') { None } else { Some("must contain @".into()) };
}
if let Some(p) = vals.get("password") { s.password = p.to_string(); }
if let Some(n) = vals.get("number") { s.number = n.parse().unwrap_or(0.0); }
});
});
let submit_btn = form_el.submit_button(&mut *cx).unwrap();
// Email field
let entity_for_email = entity.clone();
let email_field = form_field("settings-email", "email", &mut *cx)
.label("Email").required(true)
.help("Used for sign-in.")
.input(text_input("settings-email-input")
.placeholder("you@example.com")
.on_change(move |new: &str, _, cx| {
entity_for_email.update(cx, |s, _| s.email = new.to_string());
})
.render(cx, _w))
.render(cx);
// Password field
let entity_for_pw = entity.clone();
let password_field = form_field("settings-password", "password", &mut *cx)
.label("Password")
.input(password_input("settings-password-input")
.placeholder("••••••••")
.on_change(move |new: &str, _, cx| {
entity_for_pw.update(cx, |s, _| s.password = new.to_string());
})
.render(cx, _w))
.render(cx);
// Number field
let entity_for_num = entity.clone();
let entity_for_inc = entity.clone();
let entity_for_dec = entity.clone();
let number_field = form_field("settings-number", "number", &mut *cx)
.label("Retries")
.input(number_input("settings-number-input")
.min(0.0).max(10.0).step(1.0)
.value(number)
.on_change(move |new: f64, _, cx| entity_for_num.update(cx, |s, _| s.number = new))
.on_increment(move |next: f64, _, cx| entity_for_inc.update(cx, |s, _| s.number = next))
.on_decrement(move |next: f64, _, cx| entity_for_dec.update(cx, |s, _| s.number = next))
.render(cx, _w))
.render(cx);
// … other fields follow the same shape …
div()
.size_full().p_4().flex().flex_col().gap_4()
.id("settings-scroll").overflow_y_scroll()
.child(email_field)
.child(password_field)
.child(number_field)
// …
.child(div().pt_2().child(submit_btn))
}
}-
cx.entity().clone()per closure. Everyon_change/on_clear/on_browse/on_increment/on_decrementclosure needs its own clone. -
All inputs render via
.render(cx, window). The renderer registers an IME handler onwindow.form_field.input(el)accepts anAnyElement, so pass the result directly. -
.value(seed)is set once at construction. Don't echo the current value back every render — the input owns its own caret/selection state and reading back resets both. -
number_inputhas three callbacks:on_change(f64, …)for the typed value,on_increment(f64, …)andon_decrement(f64, …)for the stepper buttons. All three write to the same field. -
keybinding_inputhasmode: Idle | Capturing. Wireon_start_capture/on_cancel_captureto update your status line. -
search_input.on_clearisFn(&mut Window, &mut App)— no&strpayload; the renderer has already cleared the value. -
form_el.submit_button(cx)returns a pre-wired Primary button that fireson_submitwith a snapshot. Place it where it makes sense. -
Validation lives in
on_submit. The renderer's job is layout; your job is validation. Set.error(field, msg)on the props for any field that should display an error. -
text_input::init(cx)must be called at boot. See Quick start §3.
Yororen UI v0.3.0 · repository · Apache-2.0 · This wiki documents Yororen UI v0.3.0.
This wiki documents Yororen UI v0.3.0 — the headless-core, swappable-renderer build.