pub fn stepper_bound(
label: &str,
state: &BoundState<f64>,
lower: f64,
upper: f64,
step: f64,
) -> ViewExpand description
Create a Stepper wired to Rust state — the +/− buttons write back.
Examples found in repository?
examples/settings.rs (line 66)
43fn body() -> View {
44 let s = settings();
45
46 form![
47 section![header: text("Account");
48 hstack![
49 text("Name"),
50 spacer(),
51 textfield_bound("Your name", &s.name).text_field_style(
52 views::TextFieldStyle::RoundedBorderTextFieldStyle,
53 ),
54 ],
55 toggle_bound("Notifications", &s.notify),
56 ],
57 section![header: text("Playback");
58 vstack_aligned(&[
59 hstack![
60 text("Volume"),
61 spacer(),
62 text!("{:.0}%", s.volume.get() * 100.0).fg_system(SystemColor::Gray),
63 ],
64 slider_bound(&s.volume, 0.0, 1.0),
65 ], HorizontalAlignment::Leading),
66 stepper_bound("Refresh every", &s.refresh, 1.0, 60.0, 1.0),
67 ],
68 section![header: text("Appearance");
69 picker_bound!(
70 "Theme",
71 &s.theme,
72 text(THEMES[0]).tag_int(0),
73 text(THEMES[1]).tag_int(1),
74 text(THEMES[2]).tag_int(2),
75 )
76 .picker_style(PickerStyle::InlinePickerStyle),
77 ],
78 section![
79 button("About", show_about)
80 .a11y_hint("Opens a panel describing this app"),
81 ],
82 ]
83 .form_style(FormStyle::GroupedFormStyle)
84 .sheet_bound(&s.about, about_panel())
85}More examples
examples/common/spec.rs (lines 592-598)
487fn node_view(node: &Node) -> Result<View, String> {
488 let spacing = node.f64_attr("spacing").unwrap_or(8.0);
489 let base = match node.name.as_str() {
490 "vstack" => {
491 let kids = children(node)?;
492 match node.attr("align") {
493 Some(a) => vstack_aligned_spacing(&kids, h_align(a, node.line)?, Some(spacing)),
494 None => vstack_spacing(&kids, spacing),
495 }
496 }
497 "hstack" => {
498 let kids = children(node)?;
499 match node.attr("align") {
500 Some(a) => hstack_aligned_spacing(&kids, v_align(a, node.line)?, Some(spacing)),
501 None => hstack_spacing(&kids, spacing),
502 }
503 }
504 "zstack" => zstack_aligned(&children(node)?, align(node.attr("align").unwrap_or("center"), node.line)?),
505 "text" => {
506 let s = node.value.as_deref().unwrap_or("");
507 let mut v = text(s);
508 if let Some(style) = node.attr("style") {
509 v = v.text_style(text_style(style, node.line)?);
510 }
511 if let Some(size) = node.f64_attr("size") {
512 v = v.font_size(size);
513 }
514 if node.flag("bold") {
515 v = v.bold();
516 }
517 if node.flag("italic") {
518 v = v.italic();
519 }
520 if let Some(c) = node.attr("color") {
521 v = match tint(c, node.line)? {
522 Tint::System(sc) => v.fg_system(sc),
523 Tint::Literal(lc) => v.fg(lc),
524 };
525 }
526 v
527 }
528 "spacer" => spacer(),
529 "divider" => divider(),
530 // The action is a no-op on purpose: a spec read from a file has nowhere
531 // to put behaviour, and pretending otherwise would be worse than a
532 // button that visibly does nothing. `hot_reload.rs` is where a spec
533 // gains behaviour, by being reloaded.
534 "button" => button(node.value.as_deref().unwrap_or("Button"), || {}),
535 "color" => color(parse_color(node.value.as_deref().unwrap_or("#808080"), node.line)?),
536 "image" => system_image(node.value.as_deref().unwrap_or("star")).into_view(),
537 "rect" => match node.attr("color") {
538 Some(c) => match tint(c, node.line)? {
539 Tint::System(sc) => rectangle_shape().fill_system(sc),
540 Tint::Literal(lc) => rectangle_shape().fill(lc),
541 },
542 None => rectangle_shape().into_view(),
543 },
544 "circle" => match node.attr("color") {
545 Some(c) => match tint(c, node.line)? {
546 Tint::System(sc) => circle_shape().fill_system(sc),
547 Tint::Literal(lc) => circle_shape().fill(lc),
548 },
549 None => circle_shape().into_view(),
550 },
551 "capsule" => match node.attr("color") {
552 Some(c) => match tint(c, node.line)? {
553 Tint::System(sc) => capsule_shape().fill_system(sc),
554 Tint::Literal(lc) => capsule_shape().fill(lc),
555 },
556 None => capsule_shape().into_view(),
557 },
558 "progress" => progress(
559 node.value.as_deref().and_then(|v| v.parse().ok()).unwrap_or(0.5),
560 node.f64_attr("total").unwrap_or(1.0),
561 ),
562 "gauge" => gauge(
563 node.value.as_deref().and_then(|v| v.parse().ok()).unwrap_or(0.5),
564 node.f64_attr("min").unwrap_or(0.0),
565 node.f64_attr("max").unwrap_or(1.0),
566 text(node.attr("label").unwrap_or("")),
567 ),
568 "group_box" => group_box(stack_children(node)?),
569 "list" => list(stack_children(node)?),
570 "scroll" => stack_children(node)?.scroll(),
571 "form" => form(stack_children(node)?),
572 // Every bound control needs an explicit `@name`.
573 //
574 // Inventing one — from the line, or from position in the tree — looks
575 // friendlier and is a trap: adding a line above a `toggle` would
576 // change its derived name, and the control would silently reset on the
577 // next reload. A name the file states is a name that survives the file
578 // being edited, which is the entire point of the slots.
579 "toggle" => toggle_bound(
580 node.value.as_deref().unwrap_or("Toggle"),
581 &flag_slot(&require_slot(node)?, node.flag("on")),
582 ),
583 "slider" => {
584 let lo = node.f64_attr("min").unwrap_or(0.0);
585 let hi = node.f64_attr("max").unwrap_or(1.0);
586 slider_bound(
587 &number_slot(&require_slot(node)?, node.f64_attr("value").unwrap_or(lo)),
588 lo,
589 hi,
590 )
591 }
592 "stepper" => stepper_bound(
593 node.value.as_deref().unwrap_or("Stepper"),
594 &number_slot(&require_slot(node)?, node.f64_attr("value").unwrap_or(0.0)),
595 node.f64_attr("min").unwrap_or(0.0),
596 node.f64_attr("max").unwrap_or(100.0),
597 node.f64_attr("step").unwrap_or(1.0),
598 ),
599 "field" => textfield_bound(
600 node.value.as_deref().unwrap_or(""),
601 &string_slot(&require_slot(node)?, node.attr("text").unwrap_or("")),
602 ),
603 "label" => label(
604 node.value.as_deref().unwrap_or(""),
605 node.attr("icon").unwrap_or("star"),
606 ),
607 other => {
608 return Err(format!(
609 "line {}: no view called `{other}`; try one of {}",
610 node.line,
611 NAMES.join(", ")
612 ))
613 }
614 };
615 decorate(base, node)
616}