Crate bevy_immediate

Crate bevy_immediate 

Source
Expand description

§bevy_immediate: Immediate Mode UI for Bevy

bevy_version Latest version Documentation License

A simple, fast, and modular UI library for Bevy, combining immediate mode ergonomics with Bevy ECS-powered retained UI.

  • Develop complex UI as simple Rust code.
  • UI visuals, styling is fully customizable.
  • Extend immediate mode with custom extensions / capabilities.

§👉 Web Demo 👈

Demo screenshot

§Features

  • Immediate mode entity hierarchy management
    Build interactive entity hierarchies with a clean API.
  • Fully compatible with Bevy
    Heavy lifting is done by Bevy ECS and bevy_ui retained mode UI.
  • Custom extension support
    Add custom capabilities like .clicked(), .selected(true), .hovered(). Extension use integrated with rust type system for autocompletion and compile time check support.
  • Inbuilt support for UI use case
    Contains extensions that implement necessary logic for constructing UI.
  • Reusable widgets
    Implement widgets using functional or bevy native style.
  • Hot-patching support
  • Fast
    Only visits each entity once per tick and does minimal amount of changes. Heavy lifting is done by Bevy’s retained UI.
  • Parallelizable
    Minimal data access requirements allow systems to run in parallel with other systems without exclusive world access.
  • Simple
    Define UI in straightforward functions, free from macro/observer/trigger boilerplate.
  • Modular
    Develop your UI by writing UI in small composable parts. Extend functionality with modular extensions.
  • Integration-friendly
    Works with other libraries (e.g., reloadable CSS style with bevy_flair).

⚠️ Note: This library is under active development. Expect some breaking changes, but they will be minimized.

§Version compatibility

bevy_immediatebevyMSRV
0.30.171.88
0.20.171.88
0.10.161.85

To use add bevy_immediate to your project dependencies in Cargo.toml file.

See CHANGELOG for changes between versions.

§Examples

Examples can be viewed: (cargo run --example demo).

Examples are located in ./examples/

§Interactive UI example

Using bevy_feathers and bevy_ui_widgets.

// Checkbox
ui.ch()
    .on_spawn_insert(|| checkbox((), Text("Checkbox")))
    .checked(&mut checkbox_value);

// Toggle switch
ui.ch()
    .on_spawn_insert(|| toggle_switch(()))
    .interactions_disabled(state.disabled) // Control whether interactions are enabled
    .checked(&mut toggle_value);

// Button that counts clicks
let mut button = ui.ch().on_spawn_insert(|| controls::button(
        ButtonProps {
            variant: ButtonVariant::Normal,
            corners: RoundedCorners::All,
        },(),()
    ))
    .add(|ui| {
        ui.ch().text(format!("Clicked: {}", count));
    });

if button.activated() {
    count += 1;
}

§Power user example

Here’s a more advanced example where user has added their own API.

pub struct PowerUserExamplePlugin;

impl bevy_app::Plugin for PowerUserExamplePlugin {
    fn build(&self, app: &mut bevy_app::App) {
        // Initialize plugin with your widget root component
        app.add_plugins(BevyImmediateAttachPlugin::<CapsUi, PowerUserExampleRoot>::new());
        app.insert_resource(ShowHidden { show: false });
    }
}

#[derive(Resource)]
struct ShowHidden {
    show: bool,
}

#[derive(Component)]
pub struct PowerUserExampleRoot;

#[derive(SystemParam)]
pub struct Params<'w> {
    show_hidden: ResMut<'w, ShowHidden>,
}

impl ImmediateAttach<CapsUi> for PowerUserExampleRoot {
    type Params = Params<'static>;

    fn construct(ui: &mut Imm<CapsUi>, params: &mut Params) {
        ui.ch().my_title("Bevy power user example");

        ui.ch()
            .my_subtitle("Use helper functions to simplify and reuse code!");

        ui.ch().my_subtitle("Show collapsible element");

        ui.ch().my_row_container().add(|ui| {
            for (text, state) in [("No", false), ("Yes", true)] {
                let mut button = ui
                    .ch_id(("choice", state))
                    .my_button()
                    .selected(params.show_hidden.show == state)
                    .add(|ui| {
                        ui.ch().my_text(text);
                    });
                if button.clicked() {
                    params.show_hidden.show = state;
                }
            }
        });

        if params.show_hidden.show {
            ui.ch_id("yes_no").my_container_with_background().add(|ui| {
                ui.ch().my_text("Lorem Ipsum!");
            });
        }

        ui.ch().my_text("It is really simple!");
    }
}

§Extend functionality by implementing new capability

You can add new capabilities with just a few lines of code. Here’s how .selected(...) is implemented.


/// Implements capability to mark entities as selectable.
pub struct CapabilityUiSelectable;

impl ImmCapability for CapabilityUiSelectable {
    fn build<Cap: CapSet>(app: &mut bevy_app::App, cap_req: &mut crate::ImmCapAccessRequests<Cap>) {
        cap_req.request_component_write::<Selectable>(app.world_mut());
    }
}

/// Marks component as being selectable
#[derive(bevy_ecs::component::Component)]
pub struct Selectable {
    /// Is selectable component selected
    pub selected: bool,
}

/// Implements methods to set entity selectable
pub trait ImmUiSelectable {
    /// Insert [`Selected`] component with given boolean value
    ///
    /// Useful for styling purposes
    fn selected(self, selected: bool) -> Self;
}

impl<Cap> ImmUiSelectable for ImmEntity<'_, '_, '_, Cap>
where
    Cap: ImplCap<CapabilityUiSelectable>,
{
    fn selected(mut self, selected: bool) -> Self {
        if let Ok(Some(mut comp)) = self.cap_get_component_mut::<Selectable>() {
            if comp.selected != selected {
                comp.selected = selected;
            }
            return self;
        }

        self.entity_commands().insert(Selectable { selected });
        self
    }
}

§New entity creation

New child entities can be created with .ch, .ch_id, .ch_with_manual_id family of functions.

For child entity creation that could appear, disappear, that are created inside loop: unique id must be provided.

Provided id is combined with parent id. Id must be unique between siblings.

Examples:

ui.ch_id("my_id");
ui.ch_id(lid!());
lch!(ui);

for idx in 0..count {
    ui.ch_id(("my_loop", idx));
    ui.ch_id(lid!(idx));
    lch!(ui, idx);
}

ui.ch(); // Has internal counter for id generation, but can not be used
         // for appearing, disappearing entities.
         // Because between frames entities may get misidentified.

for idx in 0..count {
    // In case of many items inside block, you can add additional id to auto id generation
    // In that case you have a new unique scope for which unique id requirements are restored.
    let mut ui = ui.with_local_auto_id_guard(("my_loop", idx));
    ui.ch();
    ui.ch();
    ui.ch();
}

lid, lch helper macros use current column, line numbers to generate auto id. But still inside loops you need to provide additional unique id.

§Hotpatching

Powered by Subsecond

Follow: Instructions & Limitations

Launch examples with: BEVY_ASSET_ROOT="." dx serve --hot-patch --features "bevy_immediate/hotpatching" --features "bevy/hotpatching" --example demo

Make sure that you enable hotpatching feature bevy_immediate and bevy crates so that UI is recreated upon hotpatch.

Try to modify and save ./examples/hot_patching.rs or any other example and see changes in the live demo.

§FAQ

§UI nodes are changing order and not correctly laid out

Make sure that you assign unique id using ch_id for ui nodes that can appear, disappear.

See New entity creation

§How do I avoid collisions with resources or queries in my systems?

  • Queries: Add Without<ImmMarker<Caps>> to your query filter.
  • Resources: Avoid direct conflicts, or use .ctx() / .ctx_mut() APIs to access resources used by capabilities.

§Contributing

Contributions are welcome!

  • Add your improvements to examples
  • Suggest or implement new capabilities useful for UI creation

Publish your own crate that is built using bevy_immediate!

§Inspiration

§Future work

  • Easier definition of new capability sets

    • Tried transitive capability implementation (works only inside one crate)
    • Tried transitive trait implementation (works only inside one crate)
    • Tried TupleList approach (conflicting trait implementations)
    • ???
  • Create reusable logic for:

    • Bevy ui widgets
    • Bevy scroll areas
    • Tooltips
    • Popups
    • Draggable, resizable windows (like egui::Window)

Re-exports§

pub use paste;
pub use immediate::*;

Modules§

attach
Logic to provide immediate mode api to attach entity tree to already existing entity
immediate
Base logic to provide immediate mode api
ui
Extensions to provide ergonomic functionality for working with creating UI (bevy_ui) in immediate mode
utils
Utility types to simplify implementation

Macros§

impl_capability_set
Implements list of capabilities for given type
lch
Helper macro to create child nodes with macro location used as source for id value generation
lid
Helper macro to generate unique id for child nodes with macro location used as source for id value generation

Structs§

ImmCapAccessRequests
Tracks what kind of query accesses capability has requested
ImmCapAccessRequestsResource
Stores requested capabilities for given immediate mode request
ImmCapQueryParam
You can retrieve components that were requested by capabilities. See FilteredEntityMut

Traits§

CapSet
Type implement support for set of ImmCapability
ImmCapability
Marks types that are used to implement immediate mode capabilities
ImplCap
Trait that marks that CapSet implements given capability implementation
ImplCapsEmpty
All capability sets implement that they implement support for empty capability set