| Crates.io | cuicui_chirp |
| lib.rs | cuicui_chirp |
| version | 0.12.0 |
| created_at | 2023-08-30 16:01:42.025638+00 |
| updated_at | 2023-11-10 14:42:36.928698+00 |
| description | A file format based on cuicui_dsl to describe bevy UIs |
| homepage | |
| repository | https://github.com/nicopap/cuicui_layout |
| max_upload_size | |
| id | 959086 |
| size | 202,803 |
cuicui_chirpcuicui_chirp defines a file format for text-based bevy scene description.
It is used in cuicui for UI, but can describe any kind of scene.
It includes:
loader::Plugin.ParseDsl) to use your own type's methods as chirp methodsparse_dsl_impl)The syntax is very close to that of cuicui_dsl's dsl! macro,
with some additions.
cuicui_chirp?cmds.spawn(…).insert(…).with_children(…) dance.Be aware that cuicui_chirp, by its nature, is not a small dependency.
Consider using cuicui_dsl if dependency size matters to you.
Also, as of 0.10, cuicui_chirp doesn't support WASM for image and font assets.
cuicui_chirp?fancy_errors (default): Print parsing error messages in a nicely formatted way.macros (default): Define and export the parse_dsl_impl macroload_font (default): load Handle<Font> as method argumentload_image (default): load Handle<Image> as method argumentmore_unsafe: Convert some runtime checks into unsafe assumptions.
In theory, this is sound, but cuicui_chirp is not tested enough to my taste
for making those assumptions by default.cuicui_chirp reads files ending with the .chirp extension. To load a .chirp
file, use ChirpBundle as follow:
# #[cfg(feature = "doc_and_test")] mod test {
# use cuicui_chirp::__doc_helpers::*; // ignore this line pls
use bevy::prelude::*;
use cuicui_chirp::ChirpBundle;
fn setup(mut cmds: Commands, assets: Res<AssetServer>) {
cmds.spawn((Camera2dBundle::default(), LayoutRootCamera));
cmds.spawn(ChirpBundle::from(assets.load("my_scene.chirp")));
}
# }
You need however to add the loader pluging (loader::Plugin) for this to work.
The plugin is parametrized over the DSL type.
The DSL type needs to implement the ParseDsl trait.
Here is an example using cuicui_layout_bevy_ui's DSL:
# #[cfg(feature = "doc_and_test")] mod test {
# use cuicui_chirp::__doc_helpers::*; // ignore this line pls
# fn setup() {}
use bevy::prelude::*;
use cuicui_layout_bevy_ui::UiDsl;
fn main() {
App::new()
.add_plugins((
DefaultPlugins,
cuicui_chirp::loader::Plugin::new::<UiDsl>(),
))
.add_systems(Startup, setup)
.run();
}
# }
Documentation
Methods available in
chirpfiles are the methods available in the choosen DSL type (in this case, it would be theUiDslmethods). Check the documentation page for the corresponding type you are using as DSL. All methods that accept an&mut selfare candidate.
The identifiers within parenthesis are methods on the ParseDsl.
Since the chirp format is a wrapper over a ParseDsl, refer to the methods
on the ParseDsl impl you added as loader::Plugin.
DslBundle compatible with cuicui_chirpLet's re-use the example in cuicui_dsl and extend it to work with cuicui_chirp.
We had a MyDsl that implements DslBundle, now we need to also implement ParseDsl
for it. So that methods are accessible in ParseDsl, use the parse_dsl_impl
attribute macro, and add it to the impl block where all the DSL's methods are
defined:
font_size: f32,
}
+#[cuicui_chirp::parse_dsl_impl]
impl MyDsl {
pub fn style(&mut self, style: Style) {
self.style = style;
Yep, for the simple case that's it. Just avoid panicking inside methods if you want to take advantage of hot reloading.
.chirp file formatThe basic syntax is similar to the cuicui_dsl dsl! macro.
One major difference is that code blocks are replaced with a function registry.
You can register a function using the WorldHandles resource. Registered
functions are global to all chirp files loaded using cuicui_chirp.
The other differences are the addition of import statements (use),
template definitions (fn), and template calls (template!()).
They are currently not implemented, so please proceed to the next section.
Note Imports ARE NOT IMPLEMENTED
In cuicui_chirp you are not limited to a single file. You can import other
chirp files.
To do so, use an import statement. Import statements are the first statements
in the file; They start with the use keyword, are followed by the source
path of the file to import and an optional "as imported_name", this is the
name with which the import will be refered in this file.
use different/file
// ...
You have two ways to use imports:
.template_name.Similarly to rust, you can combine imports, but only templates from the same file, so the following is valid:
use different/file.template
use different/file.{template1, template2}
// ...
Wild card imports are not supported.
However, to be able to import templates, you need to mark them as pub in the
source template. Just prefix the fn with pub and that's it.
chirp files admit a series of fn definitions at the very beginning of the
file. A fn definition looks very similar to rust function definitions.
It has a name and zero or several parameters. Their body is a single statement:
// file: <scene.chirp>
// template name
// ↓
// vvvvvv
fn spacer() {
Spacer(height(10px) width(10%) bg(coral))
}
// parameter
// template name ↓
// ↓ ↓
// vvvvvv vvvvvvvvvvv
fn button(button_text) {
Entity(named(button_text) width(95%) height(200px) bg(purple) row) {
ButtonText(text(button_text) rules(0.5*, 0.5*))
}
}
You can call a template like you would call a rust macro, by writing the template
name followed by ! and parenthesis:
# fn sys(cmds: &mut cuicui_dsl::EntityCommands) { cuicui_dsl::dsl!(cmds,
// file: <scene.chirp> (following)
Menu(screen_root row bg(darkgrey)) {
TestSpacer(width(30%) height(100px) bg(pink))
spacer!()
button!("Hello world")
}
# )}
When a template is called, it will be replaced by the single root statement
defined as body of the fn definition for that template.
Template calls can be followed by template extras.
# fn sys(cmds: &mut cuicui_dsl::EntityCommands) { cuicui_dsl::dsl!(cmds,
// file: <scene.chirp> (following)
Menu(screen_root row bg(darkgrey)) {
TestSpacer(width(30%) height(100px) bg(pink))
// Additional method list after the template arguments list
// vvvvvvvvvvvvvvvvvvvvvv
spacer!()(width(50%) bg(coral))
// Both additional methods and additional children added after the argument list
// vvvvvvvvvv
button!("Hello world")(column) {
MoreChildren(text("Hello"))
MoreChildren(text("World"))
}
}
# )}
The additional methods will be added at the end of template's root statement method list. While the additional children statements will be added as children of the template's root statement.
Take for example this chirp file:
fn deep_trailing2(line, color) {
Trailing2Parent {
Trailing2 (text(line) bg(color) width(1*))
}
}
fn deep_trailing1(line2, line1) {
deep_trailing2!(line1, red) {
Trailing1 (text(line2) bg(green) width(2*))
}
}
deep_trailing1!("Second line", "First line") (column bg(beige) rules(1.1*, 2*) margin(20)) {
Trailing0 (text("Third line") bg(blue))
}
It is equivalent to:
# fn sys(cmds: &mut cuicui_dsl::EntityCommands) { cuicui_dsl::dsl!(cmds,
Trailing2Parent(column bg(beige) rules(1.1*, 2*) margin(20)) {
Trailing2 (text("First line") bg(red) width(1*))
Trailing1 (text("Second line") bg(green) width(2*))
Trailing0 (text("Third line") bg(blue))
}
# )}
Note "argument" here may refer to two things: (1) the value passed as argument to a template, in
template!(foo_bar),foo_baris an argument. (2) arguments passed to methods, inEntity(text(method_argument)),method_argumentis a method argument.The name declared between parenthesis in the
fnname is a parameter. Infn button(button_text),button_textis a template parameter.
When a template is called, the body of the fn is inserted where the call
is made, arguments passed to the template are inlined within the statement
of the template body.
Please pay close attention to how parameters are inlined:
| ❗ Compatibility Notice ❗ |
|---|
In the future, parameters will be allowed in more contexts:
|
| To avoid painfull breaking changes, avoid naming parameters the same as DSL methods or templates. |
# fn sys(cmds: &mut EntityCommands) { dsl!(cmds,
fn button(button_text) {
// Will spawn an entity without name, with tooltip set to whatever
// was passed to `button!`.
Entity(tooltip(button_text) width(95%) height(200px) bg(purple) row) {
// Will spawn an entity named "button_text" with text "button_text"
button_text(text("button_text") rules(0.5*, 0.5*))
// Current limitation:
// `gizmo` method will be called with `GizmoBuilder(button_text)` as first
// argument and whatever was passed to `button!` as second argument
Gizmo(gizmo(GizmoBuilder(button_text), button_text) rules(0.5*, 0.5*))
}
}
# )}
See the dedicated documentation page for all available
configuration options on parse_dsl_impl.
Consider explicitly depending on the log and tracing crates, and enable the
"release_max_level_debug" features of those crates, so that log messages are
elided from release builds.
cuicui_chirp contains a lot of trace logging in very hot loops.
"release_max_level_debug" will remove trace logging at compile time and
not only make the code faster (code that would otherwise read a lock atomic),
but it enables more optimization, inlining and loop unrolling.
First, find the version of the log and tracing crates in your dependency tree with:
cargo tree -p log -p tracing
Then, add them to your Cargo.toml and enable a max_level feature. Note
that they are already in your dependency tree, so there is no downside to doing so:
log = { version = "<version found with `cargo tree`>", features = ["release_max_level_debug"] }
tracing = { version = "<version found with `cargo tree`>", features = ["release_max_level_debug"] }
# Note: I recommend `release_max_level_warn` instead.
# `debug` is specific for performance regarding `cuicui_chirp`
Next time you compile your game, you'll probably have to recompile the whole
dependency tree, since tracing and log are usually fairly deep.
Remember the inheritance trick from cuicui_dsl? parse_dsl_impl is
compatible with it. Use the delegate argument to specify the field to which
to delegate methods not found on the MyDsl impl.
// pub struct MyDsl<D = ()> {
// #[deref]
// inner: D,
// }
#[parse_dsl_impl(delegate = inner)]
impl<D: DslBundle> MyDsl<D> {
// ...
}
ReflectDslUnlike cuicui_dsl, it is possible to use Reflect to define DSLs. See the
ReflectDsl docs for details.
Since .chirp files are in text format, we need to convert text into method
arguments. parse_dsl_impl parses differently method arguments depending on
their type.
See parse_dsl_impl::type_parsers for details.
cuicui_dsl and cuicui_chirp?cuicui_dsl is a macro (dsl!), while cuicui_chirp is a scene file format,
parser and bevy loader. cuicui_chirp builds on top of cuicui_dsl, and has
different features than cuicui_dsl. Here is a feature matrix:
| features | cuicui_dsl |
cuicui_chirp |
|---|---|---|
| statements & methods | ✅ | ✅ |
code blocks with in-line rust code |
✅ | |
code calling registered functions |
✅ | |
fn templates |
rust | ✅ |
| import from other files | rust | |
| hot-reloading | ✅ | |
| reflection-based methods | ✅ | |
| special syntax for colors, rules | ✅ | |
| lightweight | ✅ | |
Allows for non-Reflect components |
✅ |
You may use cuicui_dsl in combination with cuicui_chirp, both crates fill
different niches.