bevy_immediate 0.7.0

A simple, fast, and modular immediate mode UI library for Bevy
Documentation
use bevy::color::palettes::tailwind::{GRAY_100, GRAY_300, GRAY_500, GRAY_700};
use bevy::ecs::system::EntityCommand;
use bevy::ecs::{
    change_detection::DetectChanges,
    component::Component,
    system::{Local, SystemParam},
};
use bevy::text::{EditableText, TextCursorStyle, TextLayout};
use bevy::ui::{BackgroundColor, FlexDirection, Node, px};
use bevy::utils::default;
use bevy_immediate::{
    CapSet, Imm, ImmCapAccessRequests, ImmCapability, ImmEntity, ImplCap,
    attach::{BevyImmediateAttachPlugin, ImmediateAttach},
    imm_id,
    ui::text::ImmUiText,
    utils::ImmLocalHashMemoryHelper,
};
use std::marker::PhantomData;

use crate::extension_use::CapsMyUi;

pub struct TextEditExamplePlugin;

impl bevy::app::Plugin for TextEditExamplePlugin {
    fn build(&self, app: &mut bevy::app::App) {
        app.add_plugins(BevyImmediateAttachPlugin::<CapsMyUi, TextEditExampleRoot>::new());
    }
}

#[derive(Component)]
pub struct TextEditExampleRoot;

#[derive(SystemParam)]
pub struct Params<'w, 's> {
    text: Local<'s, String>,
    _ph: PhantomData<(&'w (), &'s ())>,
}

impl ImmediateAttach<CapsMyUi> for TextEditExampleRoot {
    type Params = Params<'static, 'static>;

    fn construct(ui: &mut Imm<CapsMyUi>, params: &mut Params) {
        ui.ch()
            .on_spawn_insert(|| Node {
                flex_direction: FlexDirection::Column,
                row_gap: px(4.),
                ..default()
            })
            .add(|ui| {
                for idx in 0..10 {
                    ui.ch_id(("text", idx))
                        .on_spawn_insert(|| {
                            (
                                Node {
                                    width: px(500.),
                                    height: px(30.),
                                    ..default()
                                },
                                BackgroundColor(GRAY_700.into()),
                                EditableText {
                                    max_characters: Some(100),
                                    ..default()
                                },
                                TextCursorStyle {
                                    color: GRAY_100.into(),
                                    selection_color: GRAY_300.into(),
                                    unfocused_selection_color: GRAY_500.into(),
                                    selected_text_color: None,
                                },
                                TextLayout::no_wrap(),
                            )
                        })
                        .input_text(&mut params.text);
                }

                ui.ch().text(params.text.as_str());
            });
    }
}

/// Capability that allows to write, read ui input text in immediate mode
pub struct CapabilityUiTextInput;

impl ImmCapability for CapabilityUiTextInput {
    fn build<Cap: CapSet>(app: &mut bevy::app::App, cap_req: &mut ImmCapAccessRequests<Cap>) {
        cap_req.request_component_write::<EditableText>(app.world_mut());
    }
}

pub trait ImmUiTextInput {
    fn input_text(self, text: &mut String) -> Self;
}

impl<Cap> ImmUiTextInput for ImmEntity<'_, '_, '_, Cap>
where
    Cap: ImplCap<CapabilityUiTextInput>,
{
    fn input_text(mut self, text: &mut String) -> Self {
        let text_hash = imm_id(&text);
        let mut helper =
            ImmLocalHashMemoryHelper::new(&mut self, "__bevy_ui_text_input", &text_hash);

        'exist: {
            let Ok(mut entity) = self.cap_get_entity_mut() else {
                break 'exist;
            };

            let Some(mut input_buffer) = entity.get_mut::<EditableText>() else {
                break 'exist;
            };

            if !helper.is_stored(&text_hash) {
                helper.store(&text_hash);

                // External value updated
                input_buffer.editor.set_text(text);
            } else {
                let not_equal = input_buffer.is_changed() // Fast check
                    && input_buffer.editor.text() != text;

                if not_equal {
                    *text = input_buffer.editor.text().to_string();

                    helper.store(&imm_id(&text));
                }
            }

            helper.finalize(&mut self);
            return self;
        }

        helper.finalize(&mut self);
        if self.will_be_spawned() {
            self.entity_commands()
                .queue_silenced(SetText { text: text.clone() });
        }

        self
    }
}

struct SetText {
    text: String,
}

impl EntityCommand for SetText {
    type Out = ();

    fn apply(self, mut entity: bevy::ecs::world::EntityWorldMut) -> Self::Out {
        if let Some(mut textedit) = entity.get_mut::<EditableText>() {
            textedit.editor.set_text(&self.text);
        }
    }
}