#标记语言 #UI元素 #bevy UI #组件 # #UI框架 #节点

bevy_bsml

一个使用简单标记语言组合UI元素的UI库,灵感来自svelte和tailwindcss

16个版本

0.14.8 2024年8月19日
0.14.7 2024年8月13日
0.14.0 2024年7月17日
0.0.8 2024年6月22日
0.0.7 2024年1月21日

#66游戏开发

Download history 179/week @ 2024-06-22 5/week @ 2024-06-29 2/week @ 2024-07-06 101/week @ 2024-07-13 14/week @ 2024-07-20 109/week @ 2024-07-27 689/week @ 2024-08-03 173/week @ 2024-08-10 152/week @ 2024-08-17

1,123 每月下载量

MIT/Apache

150KB
2.5K SLoC

bevy_bsml

github crates.io docs.rs

BSML代表Bevy的Simple Markup Language,或简称BS Markup Language。

bevy_bsml允许您使用简单标记语言组合UI元素,灵感来自svelte和tailwindcss。

它建立在官方bevy_ui库之上,因此您仍然可以使用bevy_ui手动与UI节点或样式交互。

要查看bevy_bsml的基本用法,请查看示例

有关重大更改和新功能,请参阅CHANGELOG.md

目录

为什么不使用HTML或XML?

因为尖括号标记语言与Rust宏不兼容,而(...){...}可以自然地工作。

支持的Bevy版本

从版本0.14开始,bevy_bsml将与Bevy保持相同的次要版本,而补丁版本将有所不同。

Bevy bevy_bsml
0.14 0.14
0.13 0.0.8
0.12 0.0.7

设置

导入预览模块,并添加BsmlPlugin

use bevy_bsml::prelude::*;

fn main() {
    App::build()
        .add_plugins(DefaultPlugins)
        .add_plugin(BsmlPlugin)
        .run();
}

有关更详细的用法,请参阅示例

BSML基础知识

在BSML中,(...) 包含元素的名称和属性,而 {...} 包含元素的内容,例如嵌套元素。

以下是一个基本的BSML示例

(node) {
    (text) { "hello world" }
}

它等同于以下HTML

<div>
  hello world
</div>

如果一个元素没有任何内容,你可以省略大括号

(node)

这是一个有效的bsml;它将生成一个没有内容的默认样式的NodeBundle。

你还可以在括号内嵌套元素

(node) {
    (node)
    (text) { "hello world" }
    (node) {
        (text) { "nested text" }
    }
}

等于

<div>
  <div />
  hello world
  <div>
    nested text
  </div>
</div>

BSML中的元素类型

node

(node) 类似于HTML中的 div;你可以在其中嵌套元素,或者不嵌套元素仅用于样式。

可用的属性是 标签

示例:

没有嵌套元素的100x100px蓝色方块:

(node class=[w(100.0), h(100.0), BG_BLUE_400])

居中节点:

(node class=[W_FULL, H_FULL, JUSTIFY_CENTER, ITEMS_CENTER]) {
    (node class=[h(100.0), w(100.0), BG_BLUE_400])
}

蓝色框中的文本:

(node class=[w(100.0), h(100.0), BG_BLUE_400]) {
    (text class=[TEXT_WHITE]}) { "hello world" }
}

带有标签:

#[derive(Component)]
pub struct MyNode { i: u8 }
(node labels=[MyNode { i: 0 }] class=[w(100.0), h(100.0), BG_BLUE_400])

for

(for) 是一个遍历给定迭代器的节点,并为每个项目重复嵌套元素。

语法是 (for {<item> in <iterator>}) { <nested elements> }

<item> 是一个变量名,而 <iterator> 可以是任何实现 IntoIterator 的表达式,就像在 for ... in ... 循环中。

可选地,你还可以像这样获取项目的索引

(for {<index>, <item> in <iterator>}) { <nested elements> }

可用的属性是 标签

示例:

简单的菜单屏幕:

(for
    {name in ["Continue", "Setting", "Exit"]}
    class=[W_FULL, H_FULL, JUSTIFY_CENTER, ITEMS_CENTER, BG_WHITE]
) {
    (node class=[w(100.0), h(100.0), BG_BLUE_400]) {
        (text class=[TEXT_BASE]) { "{}", name }
    }
}

带有索引的简单菜单屏幕:

(for
    {i, name in ["Continue", "Setting", "Exit"]}
    class=[W_FULL, H_FULL, JUSTIFY_CENTER, ITEMS_CENTER, BG_WHITE]
) {
    (node class=[w(100.0), h(100.0), BG_BLUE_400]) {
        (text class=[TEXT_BASE]) { "{}: {}", i, name }
    }
}

slot

(slot) 是一个特殊元素;它用于 可复用组件 定义,用于公开其子元素的空间。

当它包含括号内的嵌套元素时,它将用作插槽的默认内容,当使用插槽时将被替换。如果你不需要默认内容,可以省略括号。

可用的属性是 标签

示例:

定义一个按钮组件:

#[derive(Component)]
pub struct MyButton;

// define a button component
bsml!{MyButton;
    (slot class=[w(100.0), h(100.0), BG_BLUE_400, hovered(BG_BLUE_600)]) {
        (text class=[TEXT_WHITE]) { "I am button" } // default content
    }
}

fn spawn_ui_system(mut commands: Commands) {
    // spawn a screen using bsml!
    commands.spawn_bsml(bsml!(
        (node class=[W_FULL, H_FULL, JUSTIFY_CENTER, ITEMS_CENTER, BG_TRANSPARENT]) {
            (MyButton) { (text class=[TEXT_WHITE]) { "button 1" } }    // displays "button 1"
            (MyButton) { (text class=[TEXT_WHITE]) { "button 2" } }    // displays "button 2"
            (MyButton)                              // displays "i am button"
        }
    ));
}

text

(text) 元素与其他元素不同之处在于你无法在其中嵌套元素。

相反,{...} 内的内容与 format!(...) 的参数完全一样。

可用的属性是 标签

示例:

基本:

(text) { "hello world" }

带有参数:

(text) { "{} + {} = {}", 1, 2, 1 + 2 }

带有样式:

(text class=[TEXT_XS]) { "I'm a tiny wittle text" }

带有标签:

#[derive(Component)]
pub struct MyText;
(text labels=[MyText] class=[TEXT_XS]) { "I'm a tiny little text" }

img

(img) 元素用于渲染图像。

此元素与其他元素不同之处在于你无法在其中嵌套元素。

相反,{...} 内的内容可以是任何返回 UiImage 的表达式。

可用的属性是 标签

示例:

基本:

(img) { some_fn_returning_UiImage() }

(img) { UiImage { color: ..., texture: ..., flip_x: false, flip_y: false } }

带有样式:

(img class=[W_FULL]) { UiImage { color: ..., texture: ..., flip_x: false, flip_y: false } }

带有标签:

#[derive(Component)]
pub struct MyImg;
(img labels=[MyImg] class=[W_FULL]) { UiImage { color: ..., texture: ..., flip_x: false, flip_y: false }

material

(material) 元素用于在 ui Node 中渲染材质;内部,它使用 MaterialNodeBundle

此元素与其他元素不同之处在于你无法在其中嵌套元素。

相反,{...} 内的内容可以是任何返回 Handle<M: UiMaterial> 的表达式。

可用的属性是 标签

示例:

基本:

(material) { some_fn_returning_material_handle() }

带有样式:

(material class=[W_FULL]) { some_fn_returning_material_handle() }

带有标签:

#[derive(Component)]
pub struct MyMaterial;
(material labels=[MyMaterial] class=[W_FULL]) { some_fn_returning_material_handle() }

BSML中的属性

class

使用 class 属性来指定元素的样式,例如 tailwindcss。

在类属性中,您可以提供提供的样式类列表,如 W_FULLBG_SLATE_200,或者返回一个: StyleClassBackgroundColorClassBorderColorClass,以及 ZIndex

示例 将节点居中于屏幕中央

(node class=[W_FULL, H_FULL, JUSTIFY_CENTER, ITEMS_CENTER, BG_TRANSPARENT]) {
    (node class=[h(100.0), w(100.0), BG_BLUE_400])
}

外部节点将填充整个屏幕,并居中其内容。内部节点将是 100x100px 大小,背景颜色为蓝色。

在交互中更改样式

您可以指定当节点悬停或按下时应用的样式,如 tailwindcss。

(node class=[h(100.0), w(100.0), BG_BLUE_400, hovered(BG_BLUE_600), pressed(BG_BLUE_800)])

注意:如果您计划使用 hoveredpressed 样式类,您还必须指定基础类,否则它将不会返回到以前的样式。

labels

labels 属性是节点生成的 bevy 组件的列表。

您可以使用这些组件查询节点,或当节点悬停或按下时更改数据。

然后您可以使用标签组件查询节点。

(node labels=[MyComponent])
#[derive(Component)]
pub struct MyComponent;

// get the entity of the node with MyComponent
// when the node is clicked or hovered
fn my_system(query: Query<Entity, (With<MyComponent>, Changed<Interaction>)>) {
    // ...
}

如果您的标签组件有字段,您还可以提供返回初始化组件的表达式。

(node labels=[MyComponent { i: 0, name: "hello".to_owned() }, label2()])
#[derive(Component)]
pub struct MyComponent {
    pub i: u8,
    pub name: String,
}

#[derive(Component)]
pub struct Label2;

fn label2() -> Label2 {
    Label2
}

然后您可以在系统中与这些组件交互

fn my_system(query: Query<&MyComponent, (With<Label2>, Changed<Interaction>)>) {
    for my_component in query.iter() {
        println!("Interacted with node {}: {}", my_component.i, my_component.name);
    }
}

高级:您还可以在 bsml 中使用标签组件的字段

#[derive(Component)]
pub struct Label {
    pub text: &'static str,
    pub width: f32
}
(node labels=[Label { text: "hello world", width: 100.0 }]) {
    (text class=[w(labels.0.width)]) { "{}", labels.0.text }
}

自定义可重用组件

定义可重用组件

您可以使用 bsml 定义自己的可重用组件。

唯一的要求是您必须在结构体上派生 bevy::prelude::Component 特性。

以下是一个可重用组件定义的示例

#[derive(Component)]
pub struct MyComponent {
    pub i: u8,
    pub name: &'static str,
}

bsml! {MyComponent;
    (node class=[h(100.0), w(100.0), BG_BLUE_400]) {
        (text class=[TEXT_WHITE, TEXT_BASE]) { "index: {}, name: {}", self.i, self.name }
    }
}

在宏中,请注意组件结构体后面跟着分号,这不是 bsml 语法的一部分。

这看起来可能有些突兀,但这是故意的,以清楚地表明您正在定义一个可重用组件。

在BSML中使用组件字段

如您所注意到的,您还可以在 bsml 中使用组件的字段。

您可以在类、标签或嵌套元素内容(如文本元素)中使用它,通过像 self.<field_name> 这样的方式引用它们。

#[derive(Component)]
pub struct MyComponent {
    pub i: u8,
    pub name: &'static str,
    pub width: f32,
}

bsml! {MyComponent;
    (node class=[h(100.0), w(self.width), BG_BLUE_400]) {
        (text class=[TEXT_WHITE, TEXT_BASE]) { "index: {}, name: {}", self.i, self.name }
    }
}

注意 这些不是响应式的。它们仅在组件初始化时评估一次。

即使字段值更改,bsml UI 也不会更新。

使用可重用组件

定义了可重用组件后,您可以直接生成它或将其包含在其他 bsml 元素中使用。

直接生成组件

使用可重用组件的最简单方法是直接创建它。

use bevy_bsml::prelude::*;

fn spawn_bsml_ui(mut commands: Commands) {
    commands.spawn_bsml(MyComponent { i: 0, name: "hello" });
}

在其他BSML元素中包含

在其他bsml元素中使用可重用组件时,您可以在括号中使用任何返回初始化组件的表达式。

use bevy_bsml::prelude::*;

#[derive(Component)]
pub struct MyContainer;

bsml! {MyContainer;
    (node class=[W_FULL, H_FULL, JUSTIFY_CENTER, ITEMS_CENTER, BG_TRANSPARENT]) {
        (MyComponent { i: 0, name: "hello" })
    }
}

fn spawn_bsml_ui(mut commands: Commands) {
    commands.spawn_bsml(MyContainer);
}

表达式可以是任何内容。您甚至可以执行类似的操作

use bevy_bsml::prelude::*;

#[derive(Component)]
pub struct MyContainer;

fn component(i: u8, name: &'static str) -> MyComponent {
    MyComponent { i, name }
}

bsml! {MyContainer;
    (node class=[W_FULL, H_FULL, JUSTIFY_CENTER, ITEMS_CENTER, BG_TRANSPARENT]) {
        (component(0, "hello"))
    }
}

fn spawn_bsml_ui(mut commands: Commands) {
    commands.spawn_bsml(MyContainer);
}

使用BSML生成UI元素

使用bsml创建UI元素有两种方法。

生成匿名UI元素

您可以直接在bsml!宏中包含在Commands::spawn_bsml方法中。

use bevy_bsml::prelude::*;

fn spawn_bsml_ui(mut commands: Commands) {
    commands.spawn_bsml(bsml!(
        (node class=[W_FULL, H_FULL, JUSTIFY_CENTER, ITEMS_CENTER, BG_TRANSPARENT]) {
            (node class=[h(100.0), w(100.0), BG_BLUE_400])
        }
    ));
}

生成可重用组件

参见直接创建组件

销毁BSML元素

要销毁bsml元素,可以使用Commands::despawn_bsml方法,并提供组件的实体。

use bevy_bsml::prelude::*;

fn spawn_bsml_ui(mut commands: Commands) {
    // spawn a screen using bsml!
    let entity = commands.spawn_bsml(bsml!(
        (node class=[W_FULL, H_FULL, JUSTIFY_CENTER, ITEMS_CENTER, BG_TRANSPARENT]) {
            (node class=[h(100.0), w(100.0), BG_BLUE_400])
        }
    ))
    .id();

    // despawn the screen entity
    commands.despawn_bsml(entity);
}

与生成的组件的响应性

由于bsml内部创建了bev_ui组件,因此您只需使用bevy::ui::Interaction来检测和响应用户界面交互。

查看示例以了解如何响应用户界面交互

依赖关系

~33–70MB
~1M SLoC