limn
    ↑↓ ChooseEnter OpenEsc Close
    GitHub

    Components/Inputs

    NumberInput

    A field for a number, joined to a button that lowers it and one that raises it. Use it for a quantity, a size or any value that is typed as often as it is nudged.

    Native componentGuide source

    Preview

    CodeSource
    fn view(state: &State, palette: ComponentPalette) -> Element {
        NumberInput::new("quantity", &state.quantity, palette)
            .min(1.0)
            .max(99.0)
            .accessibility_label("Quantity")
            .render()
    }
    
    fn update(state: &mut State, action: &UiAction) {
        match action {
            UiAction::InputChanged { id, value } if id == "quantity" => state.quantity = value.clone(),
            UiAction::Clicked { id } => {
                let next = NumberInput::adjusted_value(id, "quantity", &state.quantity, Some(1.0), Some(99.0), 1.0, 0);
                if let Some(next) = next {
                    state.quantity = next;
                }
            }
            _ => {}
        }
    }

    Examples

    Each one is the library drawing it. Press, type and hover.

    Step and decimals1 / 2
    CodeSource
    row(vec![input(&OPACITY).render()])
    Step and decimals2 / 2
    CodeSource
    row(vec![input(&OFFSET).render()])
    At the ends of the range
    CodeSource
    row(vec![input(&VOLUME).render()])
    Text that is not a number1 / 2
    CodeSource
    row(vec![input(&GUESTS).placeholder("Guests").render()])
    Text that is not a number2 / 2
    CodeSource
    row(vec![input(&TYPED).render()])
    Sizes and width1 / 2
    CodeSource
    row(vec![input(&DENSE).size(ControlSize::Dense).width(120.0).render(), input(&REGULAR).render()])
    Sizes and width2 / 2
    CodeSource
    row(vec![input(&SPACIOUS).size(ControlSize::Spacious).width(260.0).render()])
    Disabled
    CodeSource
    row(vec![NumberInput::new("number-off", "42", palette).accessibility_label("Locked count").disabled().render()])

    NumberInput

    A field for a number, joined to a button that lowers it and one that raises it. Use it for a quantity, a size or any value that is typed as often as it is nudged.

    Import

    use limn::{Component, NumberInput, NumberInputButtonPlacement};

    Usage

    A number input is controlled: pass the text of the field each frame, and store what the field and the buttons report. The field reports every edit under the input’s own ID. A button reports a click, and NumberInput::adjusted_value turns that click into the next text.

    fn view(state: &State, palette: ComponentPalette) -> Element {
        NumberInput::new("quantity", &state.quantity, palette)
            .min(1.0)
            .max(99.0)
            .accessibility_label("Quantity")
            .render()
    }
    
    fn update(state: &mut State, action: &UiAction) {
        match action {
            UiAction::InputChanged { id, value } if id == "quantity" => state.quantity = value.clone(),
            UiAction::Clicked { id } => {
                let next = NumberInput::adjusted_value(id, "quantity", &state.quantity, Some(1.0), Some(99.0), 1.0, 0);
                if let Some(next) = next {
                    state.quantity = next;
                }
            }
            _ => {}
        }
    }

    adjusted_value(action_id, id, current, min, max, step, decimals) is None for an action that is not one of this input’s buttons, so it can be tried on every click. Give it the same range, step and decimals as the input.

    A field keeps the text typed in it, whatever value it is built with again. So after a button changes the value, give the field the new text:

    tree.set_input_value("quantity", &state.quantity);

    The state is text, not a number, so that what is half typed stays as typed. NumberInput::parse(&state.quantity) is the number when there is one.

    Range and step

    NumberInput::new("opacity", &state.opacity, palette).min(0.0).max(1.0).step(0.05).decimals(2).render()
    BuilderSetsUnless set
    min(f64)The least value the buttons go to.No bound
    max(f64)The greatest value the buttons go to.No bound
    step(f64)How far one click moves the value.1
    decimals(u8)The digits after the point a button writes its result with.0

    A bound that is not finite, and a step that is not finite or not above zero, are ignored. adjusted_value treats them the same way: no bound, and a step of 1.

    A button moves the value by one step and stops at the end of the range: a step up from 9.5 with a max of 10 is 10. The result is rounded to decimals, so a step of 0.1 up from 0.2 is 0.3 with decimals(1). With the default of no decimals a step below 1 moves nothing: set decimals whenever the step has a fraction.

    The field takes any text, so a value outside the range can be typed. A button brings it back to the nearest end and never moves it further out: with a max of 10, the button that lowers takes 150 to 10, and the one that raises does nothing.

    At the ends

    A button with nowhere to go is drawn disabled and is passive: the one that lowers at min or below it, the one that raises at max or above it. For such a click adjusted_value is None.

    Text that is not a number

    NumberInput::parse("-1.5")         // Some(-1.5)
    NumberInput::parse("12a")          // None
    NumberInput::is_valid_number(".5") // true

    A number is an optional sign, digits and at most one point, with spaces around it ignored: -1.5, +3, .5, 2.. Anything else is not: an empty text, -, 1e3, 1,5.

    The text is the host’s, a number or not. From an empty field either button writes the starting value: zero, or the end of the range nearest to zero when zero is outside it. Any other text that is not a number is left as it is: adjusted_value is None and both buttons are disabled until the text is a number again.

    Placeholder

    NumberInput::new("guests", &state.guests, palette).placeholder("Guests").render()

    The placeholder shows while the field is empty.

    Size and width

    NumberInput::new("quantity", &state.quantity, palette).size(ControlSize::Spacious).width(240.0).render()

    size takes the height of a control size, as a text field does: 28 for Dense and Compact, 32 for Regular, the default, and 36 for Spacious. width is the field and the buttons together, 160 unless set. The text always keeps 80, so a narrower width is widened to fit. A width that is not finite is ignored.

    The buttons follow the value by default. To put decrease on the left and increase on the right, with the editable value centered between them:

    NumberInput::new("font-size", "13", palette)
        .button_placement(NumberInputButtonPlacement::Split)
        .render()

    button_placement(NumberInputButtonPlacement::End) restores the default. Both placements keep the same total width, actions and bounds. A long value uses the field’s ordinary horizontal scrolling; caret placement and selection use the same origin as the centered text. A disabled value stays centered.

    Disabled

    NumberInput::new("quantity", &state.quantity, palette).disabled().render()
    NumberInput::new("quantity", &state.quantity, palette).enabled(state.can_edit).render()

    A disabled input shows its text and its buttons in muted ink, in the same place. The field and the buttons take no pointer or focus and report nothing.

    Keyboard

    The field and each button that has somewhere to go are tab stops. Enter or Space on a button reports its click. The arrow keys in the field move the caret, as in any field: they do not step the value. In Split, Tab follows the visual order: decrease, field, increase, skipping disabled buttons. The default remains field, decrease, increase.

    Motion

    A button’s hover and press fills change over tokens().motion.hover_ms.

    Actions

    ActionWhen
    UiAction::InputChanged { id, value }The text of the field changes; id is the input’s.
    UiAction::Clicked { id } with {id}:decrement or {id}:incrementA button is pressed.

    Accessibility

    The field is a text input whose value is its text. Nothing the input draws names it, so give it a name; without one it is called by its placeholder.

    NumberInput::new("quantity", &state.quantity, palette).accessibility_label("Quantity").render()

    Each button is named by what it does and the value it goes to, such as “Increase 5”, or by what it does alone when it has nowhere to go. A button drawn disabled is still there, as disabled. The two words are the English “Decrease” and “Increase”: limn::labels() has no words for them, so an application in another language passes its own.

    NumberInput::new("quantity", &state.quantity, palette).step_labels("Menos", "Más").render()

    Limits

    • The field does not refuse a character: letters can be typed, and the host decides what to do with text that parse does not read.
    • A typed value is not brought into the range by itself. A host that wants that does it where it reads the value.
    • Numbers are written with a point and without separators of thousands.

    API reference

    The guide covers composition, options and actions. Generate the complete Rust type and builder reference from the checkout:

    cargo doc -p limn --open

    Component API overview · State and actions

    The example is drawn by crates/limn-gallery/src/pages/number.rs.