bevy_immediate

Crates.iobevy_immediate
lib.rsbevy_immediate
version0.5.0
created_at2025-09-22 08:21:19.525075+00
updated_at2026-01-14 14:32:08.228238+00
descriptionA simple, fast, and modular immediate mode UI library for Bevy
homepagehttps://github.com/PPakalns/bevy_immediate/
repositoryhttps://github.com/PPakalns/bevy_immediate/
max_upload_size
id1849660
size251,259
Pēteris Pakalns (PPakalns)

documentation

README

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_immediate bevy MSRV
0.5 0.18 1.89
0.4 0.17 1.88
0.3 0.17 1.88
0.2 0.17 1.88
0.1 0.16 1.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)
Commit count: 152

cargo fmt