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 游戏开发
59KB
680 行
Bevy UI DSL
一个微小、无插件、无宏的“领域特定语言”,旨在使在Bevy中构建UI更愉快。此DSL使用与bevy_ui相同的成分,因此那些已经熟悉bevy_ui的人应该能够轻松学习它。
UI示例
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.rs与example_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