Skip to main content

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.50.181.89
0.40.171.88
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 bevy_immediate_core;
pub use bevy_immediate_attach as attach;
pub use bevy_immediate_ui as ui;
pub use bevy_immediate_core::paste;

Modulesยง

capabilities
Exstention support for immediate mode. Logic to implement additional capabilities.
immediate
Base logic to provide immediate mode api
utils
Reexport utils Utility types to simplify implementation

Macrosยง

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

Structsยง

BevyImmediatePlugin
Plugin for immediate mode functionality in bevy
ChangeDetector
Helper structure for immediate mode compatbile state change detection
Imm
Immediate mode in a state where child components can be added
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
ImmCtx
Immediate mode ctx
ImmEntity
Entity during construction in immediate mode approach
ImmId
Unique id for immediate mode entities.
ImmMarker
Component that is added to entities that are managed by immediate mode system
ImmScopeGuard
Helper guard structure to handle scope for child creation without introducing additional nesting level
ImmediateSystemSet
System set for systems that power bevy_immediate immediate mode functionality

Enumsยง

ImmIdBuilder
Can be used to construct unique id for new entity

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

Functionsยง

imm_id
Helper function to easily construct ImmId

Type Aliasesยง

ImmQuery
Helper type to more easily write queries
WithoutImm
Type to use in QueryFilter to avoid query collisions