UI (User Interface) / Feathers Number Input

Back to examples View in GitHub

This example shows off the various Bevy Feathers widgets.

use std::f32::consts::PI;

use bevy::{
    feathers::{
        controls::*,
        dark_theme::create_dark_theme,
        display::label,
        theme::{ThemeBackgroundColor, UiTheme},
        tokens, FeathersPlugins,
    },
    input_focus::tab_navigation::TabGroup,
    prelude::*,
    ui::InteractionDisabled,
    ui_widgets::ValueChange,
};

fn main() {
    App::new()
        .add_plugins((DefaultPlugins, FeathersPlugins))
        .insert_resource(UiTheme(create_dark_theme()))
        .add_systems(Startup, scene.spawn())
        // .add_systems(Update, update_colors)
        .run();
}

fn scene() -> impl SceneList {
    bsn_list! {
        Camera2d
        --
        @demo_root()
    }
}

fn demo_root() -> impl Scene {
    bsn! {
        Node {
            width: percent(100),
            height: percent(100),
            align_items: AlignItems::Start,
            justify_content: JustifyContent::Start,
            display: Display::Flex,
            flex_direction: FlexDirection::Column,
            flex_wrap: FlexWrap::Wrap,
            padding: px(8),
            row_gap: px(8),
        }
        TabGroup
        ThemeBackgroundColor(tokens::WINDOW_BG)
        Children[
            @demo_field_f32("none (bare)", 1.0, bsn! {})
            --
            @demo_field_f32("soft limit", 2.0, bsn! {
                SoftLimit(NumberInputRange::F32(0.0..=10.0))
            })
            --
            @demo_field_f32("hard limit", 3.0, bsn! {
                HardLimit(NumberInputRange::F32(-100.0..=100.0))
            })
            --
            @demo_field_f32("soft + hard", 4.0, bsn! {
                SoftLimit(NumberInputRange::F32(0.0..=10.0))
                HardLimit(NumberInputRange::F32(-100.0..=100.0))
            })
            --
            @demo_field_f32("precision(0)", 5.0, bsn! {
                NumberInputPrecision(0)
            })
            --
            @demo_field_f32("precision(2)", 6.0, bsn! {
                NumberInputPrecision(2)
            })
            --
            @demo_field_f32("precision(4)", 7.0, bsn! {
                NumberInputPrecision(4)
            })
            --
            @demo_field_f32("step(1.0)", 8.0, bsn! {
                NumberInputStep(1.0f64)
            })
            --
            @demo_field_f64("f64: soft limit", 1.0f64, bsn! {
                SoftLimit(NumberInputRange::F64(0.0f64..=10.0f64))
            })
            --
            @demo_field_f64("f64: soft limit + precision(2)", 1.0f64, bsn! {
                SoftLimit(NumberInputRange::F64(0.0f64..=10.0f64))
                NumberInputPrecision(2)
            })
            --
            @demo_field_i32("i32: bare", 1, bsn! {})
            --
            @demo_field_i32("i32: soft limit", 1, bsn! {
                SoftLimit(NumberInputRange::I32(0..=10))
            })
            --
            @demo_field_f32_with_sigil("precision(2) + sigil", 6.0, bsn! {
                NumberInputPrecision(2)
            })
            --
            @demo_field_f32("soft limit + disabled", 2.0, bsn! {
                InteractionDisabled
                SoftLimit(NumberInputRange::F32(0.0..=10.0))
            })
            --
            @demo_field_f32("hard limit + wrap", 0.0, bsn! {
                HardLimit(NumberInputRange::F32(-180.0..=180.0))
                NumberInputWrap::Wrap
            })
            --
            @demo_field_f32("in meters", 2.0, bsn! {
                NumberInputUnits::new(&LengthMeters)
            })
            --
            @demo_field_f32("in seconds", 2.0, bsn! {
                NumberInputUnits::new(&TimeSeconds)
            })
            --
            @demo_field_f32("in degrees", PI, bsn! {
                NumberInputUnits::new(&AngleDegrees)
            })
        ]
    }
}

fn demo_field_f32(label_text: &str, value: f32, options: impl Scene) -> impl Scene {
    bsn! {
        Node {
            display: Display::Flex,
            flex_direction: FlexDirection::Column,
            align_items: AlignItems::Start,
            width: px(200),
            row_gap: px(4),
        }
        Children [
            @label(label_text)
            --
            Node {
                display: Display::Flex,
                flex_direction: FlexDirection::Row,
                align_items: AlignItems::Center,
                align_self: AlignSelf::Stretch,
                justify_content: JustifyContent::SpaceBetween,
            }
            Children [
                @FeathersNumberInput
                NumberInputValue::F32(value)
                @{options}
                Node {
                    flex_grow: 1.0,
                    max_width: px(120),
                }
                on(
                    |value_change: On<ValueChange<f32>>, mut commands: Commands| {
                    commands.entity(value_change.event_target())
                        .insert(NumberInputValue::F32(value_change.value));
                })
                --
                #Output
                @label("-")
            ]
        ]
    }
}

fn demo_field_f32_with_sigil(label_text: &str, value: f32, options: impl Scene) -> impl Scene {
    bsn! {
        Node {
            display: Display::Flex,
            flex_direction: FlexDirection::Column,
            align_items: AlignItems::Start,
            width: px(200),
        }
        Children [
            @label(label_text)
            --
            Node {
                display: Display::Flex,
                flex_direction: FlexDirection::Row,
                align_items: AlignItems::Center,
                align_self: AlignSelf::Stretch,
                justify_content: JustifyContent::SpaceBetween,
            }
            Children [
                @FeathersNumberInput {
                    @sigil_color: tokens::TEXT_INPUT_X_AXIS,
                    @label_text: "X",
                }
                NumberInputValue::F32(value)
                @{options}
                Node {
                    flex_grow: 1.0,
                    max_width: px(120),
                }
                on(
                    |value_change: On<ValueChange<f32>>, mut commands: Commands| {
                    commands.entity(value_change.event_target())
                        .insert(NumberInputValue::F32(value_change.value));
                })
                --
                #Output
                @label("-")
            ]
        ]
    }
}

fn demo_field_f64(label_text: &str, value: f64, options: impl Scene) -> impl Scene {
    bsn! {
        Node {
            display: Display::Flex,
            flex_direction: FlexDirection::Column,
            align_items: AlignItems::Start,
            width: px(200),
        }
        Children [
            @label(label_text)
            --
            Node {
                display: Display::Flex,
                flex_direction: FlexDirection::Row,
                align_items: AlignItems::Center,
                align_self: AlignSelf::Stretch,
                justify_content: JustifyContent::SpaceBetween,
            }
            Children [
                @FeathersNumberInput
                NumberInputValue::F64(value)
                @{options}
                Node {
                    flex_grow: 1.0,
                    max_width: px(120),
                }
                on(
                    |value_change: On<ValueChange<f64>>, mut commands: Commands| {
                    commands.entity(value_change.event_target())
                        .insert(NumberInputValue::F64(value_change.value));
                })
                --
                #Output
                @label("-")
            ]
        ]
    }
}

fn demo_field_i32(label_text: &str, value: i32, options: impl Scene) -> impl Scene {
    bsn! {
        Node {
            display: Display::Flex,
            flex_direction: FlexDirection::Column,
            align_items: AlignItems::Start,
            width: px(200),
        }
        Children [
            @label(label_text)
            --
            Node {
                display: Display::Flex,
                flex_direction: FlexDirection::Row,
                align_items: AlignItems::Center,
                align_self: AlignSelf::Stretch,
                justify_content: JustifyContent::SpaceBetween,
            }
            Children [
                @FeathersNumberInput
                NumberInputValue::I32(value)
                @{options}
                Node {
                    flex_grow: 1.0,
                    max_width: px(120),
                }
                on(
                    |value_change: On<ValueChange<i32>>, mut commands: Commands| {
                    commands.entity(value_change.event_target())
                        .insert(NumberInputValue::I32(value_change.value));
                })
                --
                #Output
                @label("-")
            ]
        ]
    }
}