#dsl #bevy #gamedev

bevy-ui-dsl

简化在bevy_ui中创建小部件的领域特定语言库

11个版本 (有破坏性)

0.9.0 2024年7月9日
0.8.0 2024年2月29日
0.7.1 2023年11月4日
0.6.1 2023年7月22日
0.3.0 2023年3月7日

#135 in 游戏开发

MIT/Apache

59KB
680

Bevy UI DSL

一个微小、无插件、无宏的“领域特定语言”,旨在使在Bevy中构建UI更愉快。此DSL使用与bevy_ui相同的成分,因此那些已经熟悉bevy_ui的人应该能够轻松学习它。

UI示例

Example Image

use bevy_ui_dsl::*;

fn startup(mut commands: Commands, assets: Res<AssetServer>, mut scale: ResMut<UiScale>) {

    // Obligatory camera
    commands.spawn(Camera2dBundle::default());
    scale.0 = 2.0;

    // Spawns ui and gathers entity ids
    let mut hiya = None;
    let mut howdy = None;
    root(c_root, &assets, &mut commands, |p| {                                  // Spawns the root NodeBundle. AssetServer gets propagated.
        node((c_half, c_green), p, |p| {                                        // Spawns the left pane as a NodeBundle.
            text("This is the left pane!", c_text, c_pixel, p);                 // Spawns a TextBundle.
            text("Do you like it?", c_text, c_pixel, p);
            text_button("Hiya", c_button_left, c_pixel, p).set(&mut hiya);      // Spawns a ButtonBundle with a TextBundle child in the middle. Convenience widget.
            grid(6, 6, c_grid, p, |p, _row, _col| {                             // Spawns a NodeBundle container with a NodeBundle for each cell (6x6).
                image(c_inv_slot, p);
            });
            text("Le grid", c_text, c_pixel, p);
        });
        node((c_half, c_blue), p, |p| {
            text("This is the right pane!", c_text, c_pixel, p);
            text("Indeed, I do!", c_text, c_pixel, p);
            text_button("Howdy", c_button_right, c_pixel, p).set(&mut howdy);
        });
    });

    // Inserts marker components into the gathered entities.
    // Useful when you need to interact with specific entities in the UI.
    commands
        .entity(hiya.unwrap())
        .insert(UiId::HiyaButton);
    commands
        .entity(howdy.unwrap())
        .insert(UiId::HowdyButton);
}

此系统使用类似root、node、text、text_button等小部件创建UI。您甚至可以创建自己的小部件!它们只是函数!回调方法主要受egui的启发。

在此示例中,root是一个接受名为c_root的类的函数。c_root函数仅操作NodeBundle,默认情况下为NodeBundle::default()。最终,相关NodeBundle被生成。

与root类似,node也接受一个类(或类元组的组合)并生成一个NodeBundle。当提供一个类元组时,回调函数按从左到右的顺序应用。

小部件函数返回生成的实体。通过扩展方法,这些实体ID可以“逃脱”,以便以后插入组件和包。这对于将UI创建代码与包插入代码分开非常有用。有两种逃脱方法

impl EntityWriter for Entity {
    /// Copies this entity into an Option.
    fn set(self, entity: &mut Option<Entity>) {
        *entity = Some(self);
    }
    /// Pushes a copy of this Entity into a Vec.
    fn push(self, entities: &mut Vec<Entity>) {
        entities.push(self);
    }
}

然而,通常在即时插入标记组件比稍后插入它更容易。因此,每个内置小部件都有一个内联变体。这些变体接受一个额外的包作为参数。

base inline
root rooti
blank blanki
node nodei
text texti
button buttoni
simple_button simple_buttoni
text_button text_buttoni
image imagei
image_pane image_panei
grid gridi

因此,而不是

text_button("Hiya", c_button_left, c_pixel, p).set(&mut hiya)
/// ... then later ...
commands.entity(hiya.unwrap()).insert(UiId::HiyaButton);

可以写

text_buttoni("Hiya", c_button_left, c_pixel, UiId::HiyaButton);

自由地比较和对比example.rsexample_inline.rs。选择哪种样式取决于个人喜好和具体情况。当插入逻辑复杂且易于分离时,“set”和“push”样式更佳。对于标记组件以及从插入逻辑中分离UI逻辑不方便的情况,“inline”样式更佳。

类示例

fn c_root(b: &mut NodeBundle) {
    b.style.width = Val::Percent(100.);
    b.style.height = Val::Percent(100.)
}

fn c_half(b: &mut NodeBundle) {
    let s = &mut b.style;
    s.width = Val::Percent(50.);
    s.height = Val::Percent(100.);
    s.flex_direction = FlexDirection::Column;
    s.justify_content = JustifyContent::Center;
    s.align_items = AlignItems::Center;
    s.padding = UiRect::all(Val::Px(10.));
}

fn c_green(b: &mut NodeBundle) {
    b.background_color = Color::rgb_u8(125, 212, 148).into();
}

fn c_blue(b: &mut NodeBundle) {
    b.background_color = Color::rgb_u8(125, 164, 212).into();
}

fn c_text(_a: &AssetServer, b: &mut TextBundle) {
    b.style.margin = UiRect::all(Val::Px(10.));
}

fn c_button_left(assets: &AssetServer, b: &mut ButtonBundle) {
    let s = &mut b.style;
    s.width = Val::Px(64.);
    s.height = Val::Px(24.);
    s.justify_content = JustifyContent::Center;
    s.align_items = AlignItems::Center;
    b.background_color = Color::rgb_u8(66, 135, 245).into();
    b.image = assets.load("button.png").into();
}

fn c_button_right(assets: &AssetServer, b: &mut ButtonBundle) {
    let s = &mut b.style;
    s.width = Val::Px(64.);
    s.height = Val::Px(24.);
    s.justify_content = JustifyContent::Center;
    s.align_items = AlignItems::Center;
    b.background_color = Color::rgb_u8(57, 179, 118).into();
    b.image = assets.load("button.png").into();
}

fn c_grid(b: &mut NodeBundle) {
    b.style.width = Val::Px(200.);
    b.style.height = Val::Px(200.);
    b.style.margin = UiRect::all(Val::Px(10.));
}

fn c_inv_slot(assets: &AssetServer, b: &mut ImageBundle) {
    b.style.width = Val::Px(32.);
    b.style.height = Val::Px(32.);
    b.image = assets.load("item_slot.png").into();
}

fn c_pixel(assets: &AssetServer, s: &mut TextStyle) {
    s.font = assets.load("prstartk.ttf").into();
    s.font_size = 8.;
    s.color = Color::WHITE.into();
}

一些类仅依赖于单个包。其他类依赖于AssetServer来操作各自的类型。建议您只设置您希望覆盖的字段。例如,请注意不要使用..default(),因为这会覆盖您未指定的字段。在使用元组语法组合类时,这非常不好。

类辅助函数

为了使创建类不那么冗长,您可以导入一个名为class_helpers的可选模块。通过在Cargo.toml文件中启用功能标志class_helpers,可以提供此模块。它包含各种辅助函数和常量,以简化您的操作。建议您在使用这些辅助函数时,将类函数放在自己的模块中,以避免与其他UI代码污染命名空间。

use bevy_ui_dsl::*;
use bevy_ui_dsl::class_helpers::*;

fn c_node(b: &mut NodeBundle) {
    let s = &mut b.style;
    s.width = pc(50);
    s.height = pc(50);
    s.flex_direction = COLUMN;
    s.justify_content = JUSTIFY_CENTER;
    s.align_items = ALIGN_CENTER;
    s.padding = all(px(10));
}

小部件示例

创建小部件只需创建一个遵循一定约定的函数即可。没有更多,也没有更少。通常需要一个AssetClass(可以是回调函数,也可以是回调函数的元组),一个父节点(用于生成小部件本身),对于容器小部件,还需要一个用于生成小部件子节点的回调函数。请参阅widgets.rs以获取示例。

/// Spawns a [`NodeBundle`] with children.
pub fn node(
    class: impl Class<NodeBundle>,              // Class (or classes) that manipulate the bundle.
    parent: &mut UiChildBuilder,                // Parent entity to add NodeBundle to.
    children: impl FnOnce(&mut UiChildBuilder)  // Callback function that spawns children of the newly spawned NodeBundle.
) -> Entity {
    let mut bundle = NodeBundle::default();             // Initializes the NodeBundle.
    class.apply(&mut bundle);                           // Applies class (or classes) to that bundle.
    parent.spawn(bundle).with_children(children).id()   // Spawns updated bundle with children!
}

兼容性

bevy bevy-ui-dsl
0.14 0.9
0.13 0.8
0.12 0.7 - 0.7.1
0.11 0.5 - 0.6
0.10 0.3 - 0.4
0.9 0.1 - 0.2

依赖项

~33–70MB
~1M SLoC