Cuicui Layout
β A syntax overall is expected in 0.10.0!!! Beware β |
---|
Want to have an impact on how it will look like? VOTE FOR YOUR FAVORITE HERE |
A dumb layout algorithm you can rely on, built for and with bevy.
For some reasons, the Cyberpunk main menu has become the 7GUI of bevy, so here
is the Cyberpunk main menu using cuicui_layout_bevy_ui
.
https://github.com/nicopap/cuicui_layout/assets/26321040/8a51f9a9-ffa7-4b60-a2ad-3947ff718e27.mp4
β Hot reloading disclaimer β |
---|
Chirp hot reloading with bevy_ui
components (ie: using cuicui_layout_bevy_ui
)
is broken due to https://github.com/bevyengine/bevy/pull/9621.
You may want to work on a local patched version of bevy.
A workaround will probably be provided in cuicui 0.10.
Code
use *;
use UiDsl as Dsl;
use ;
Running examples
Use the cargo run --bin
command to list possible examples, and run them.
We do this because it allows us to have different dependencies between examples.
Specific example docs
chirpunk
A clone of the cyberpunk 2077 main menu and settings menu.
Demonstrates full end-to-end usage of .chirp
, including common patterns for
managining complexity.
This example requires additional steps to work properly.
Check the example's README for more details.
simple_menu
A single menu made using cuicui_dsl
.
dsl_and_chirp
Demonstrates the equivalence between the dsl!
macro and the .chirp
file
format. Also used as a test to make sure it is trully equivalent.
sprite_debug
Demonstrates usage of cuicui_layout_bevy_sprite
. Due to a quirk in the way
cargo resolves workspace features, the debug overlay is specifically broken for
this. You need to use the following command line to run it with the layout debug
overlay:
Stability
This crate is in expansion, use at your own risk, it is extremely likely that a lot of things are going to break a lot.
Using cuicui_layout
First, chose which crate you want to use:
- Interested in a ready-made UI library on top of
bevy_ui
?cuicui_layout_bevy_ui
is for you. - Want more flexibility? Using
cuicui_layout
on top ofbevy_sprite
will let you integrate your UI with a lot of 3rd party crates that only work with sprites.cuicui_layout_bevy_sprite
is for you. - Using a custom renderer or want your UI to be part of the 3D environment?
Build on top of
cuicui_layout
itself then. - Are you making a complex menu requiring a lot of iterations? Consider using
cuicui_chirp
and the.chirp
file format!
Secondly, add your chosen integration crate to your Cargo.toml
:
[]
= "0.9.0"
= "0.9.0"
Then, use cuicui_layout
in your crate with the dsl!
macro:
use *;
use ;
use UiDsl as Dsl;
That's it! You are now using cuicui_layout
, congratulations!
Make sure to check the LayoutDsl
docs to learn the current capabilities of
cuicui_layout
.
What's that dsl!
macro?
The previous snippet can be translated to:
use *;
use UiDsl as Dsl;
// ...
In short, the identifiers between parenthesis are just methods. Check the
documentation on the relevant FoobarDsl
struct to learn which methods you
can use!
Also check the cuicui_dsl
crate documentation for details on the
DslBundle
trait (the trait providing the node
and insert
methods)
and the IntoEntityCommands
trait (for the to_cmds
method).
What's that .chirp
file format?
See the cuicui_chirp
crate README.
How do I use .chirp
files?
Reproducing the previous example with .chirp
files:
First, write the chirp file:
// file: <scene.chirp>
// Use screen_root to follow the screen's boundaries
row(screen_root) {
row(margin 9, border(5, cyan), bg navy) {
spawn(text "Hello world!");
}
}
Second, add the plugin and load the chirp file:
use *;
use ;
use UiDsl;
use Chirp;
cuicui_layout
crates
This repository contains several crates:
cuicui_dsl
(dsl): Thedsl!
macro andDslBundle
.cuicui_chirp
(chirp): A parser for files that follow thedsl!
syntax. It creates a scene identical to what the same text passed to thedsl!
macro would produce.
It also includes a bevy plugin to load.chirp
files defined in this format.cuicui_layout
(layout): The base algorithm and components, does not make any assumption about how it is used, beside the requirement that layout nodes be bevyEntitiy
and usesbevy_hierarchy
. Exports aLayoutDsl
to use with thedsl!
macro.cuicui_layout_bevy_ui
(ui): Integration withbevy_ui
, including extension toUiDsl
forUiImage
,Text
, background images and background colors.cuicui_layout_bevy_sprite
(sprite):bevy_sprite
integration, supportsMesh2dHandle
,Sprite
andText2d
. This isn't as good as thebevy_ui
-based integration when it comes to content-driven sizes, but otherwise should work very much like thebevy_ui
integration.
Cargo features
cuicui_chirp/macros
(on by default): Define and export theparse_dsl_impl
attribute macro. This allows derivingParseDsl
automatically from animpl
block.cuicui_chirp/fancy_errors
(on by default): Display error message with source code context and actionable messages when failing to load a.chirp
file. Note that this is only used in theChirpLoader
bevy asset loader, and theChirp::interpret_logging
. If disabled, a more rudimentary message is shown instead.cuicui_layout/debug
(off by default): An overlay showing layout outlines & the rule type used by nodes. See debug.md for a detailed feature list.cuicui_layout/reflect
(on by default): Derivebevy_reflect
traits for cuicui_layout types & register them.cuicui_layout_bevy_sprite/sprite_text
(on by default): implement content-sized layout nodes forText2dBundle
.
Why cuicui layout
- Friendly algo with less things to keep in your head and good defaults.
- Uses and takes full advantage of the bevy ECS.
- Only controls
LayoutRect
, notTransform
, you need to add a system that setsTransform
based onLayoutRect
. - Fully flexible and extensible, can be used with
bevy_ui
,bevy_sprite
, your own stuff. - Fantatstically easy to extend, like really.
- Helpful and fully detailed error messages when things are incoherent or broken. As opposed to FlexBox, which goes "this is fine π₯πΆπ₯" and leaves you to guess why things do not turn out as expected.
cuicui_layout
's algo runs inO(n)
wheren
is how many nodes you have.- An extensive debugging overlay.
- Working hot reloading.
Why not Flexbox
You are writing text to get 2d visual results on screen.
The translation from text to screen should be trivial, easy to do in your head.
Otherwise you need visual feedback to get what you want.
Bevy, even with hot reloading or bevy-inspector-egui
will always have extremely slow visual feedback.
Flexbox has too many parameters and depends on implicit properties of UI elements, it is not possible to emulate it in your head.
cuicui's layout, in contrast to Flexbox is easy to fit in your head. In fact, I will forecefully push cuicui's layout algorithm in your head in two short bullet points.
- A node can be a
Node::Container
and distribute its children along aDirection
either by evenly spacing them (Distribution::FillMain
) or putting them directly one after another (Distribution::Start
). - A
Container
's size can be expressed as a static value, a fraction of the size of what contains it, or a multiple of what it contains. - The content of a
Container
can beAlignment
to the start, end or center of its parent (by default it's centered).
That's it. There are some edge cases, but cuicui will yell at you
tell you nicely when you hit them and tell you how to handle them properly.
Flexbox FAQ
Q: Where is padding
?
A: padding
is equivalent to margin
in cuicui_layout. margin
and border
doesn't make conceptual sense.
Q: Why not call it padding
then?
A: Look at the dictionary definition of "margin" and "padding".
Q: How do I center a node? A: nodes are centered by default, make sure the parent's container size has the expected size.
Q: What is the equivalent of flex_direction
?
A: use row
and column
Q: What are the equivalents of column-reverse
and row-reverse
?
A: None. Use Alignment::End
and swap your elements! Note that the *-reverse
flows in flexbox are very useful for internationalization. However,
when making a game, it is not enough to just swap the elements! Artistic control is
paramount and internationalization needs to be taken as a whole in the context of the UI.
Q: What is the equivalent of flex_wrap
?
A: None, do you really need it?
Q: What is the equivalent of align_item
, align_self
, align_content
, justify_content
?
A: After 5 years of working with CSS, I still have no clue which one does what,
and whether they really do anything, so I wont' adventure an asnwer.
Q: What is the equivalent of flex_grow
, flex_shrink
, flex_basis
, gap
?
A: Do you even know what they do?
Q: Why can't child container overflow their parents? A: It's likely you didn't expect this, so we report it as an error.
Q: How do I make a grid?
A: cuicui_layout
is currently not capable of managing a grid of nodes.
This might be added in the future.
Why not add <Flexbox feature> to cuicui_layout
?
Each flexbox feature is useful taken in isolation, but when combined, they make for a very difficult to grasp whole. It's the combinatorial explosion of interactions between features that makes Flexbox impossible to emulate in your head. In fact, I'm not so sure anything short of a Flexbox implementation can predict what the final output of your CSS will look like.
With this settled, it is natural that I aim to make cuicui_layout
as featureless
as possible. Ideally, there is exactly one way to do anything, even if it requires
a bit of head scratching to get there. Code with less feature is paradoxically
smarter. A narrow set of functionalities allow easier inference on the user's
expectations, enabling better error messages and suggestions.
Of course, as a library, cuicui_layout
must at least have some features.
Here is what I look in a new feature:
- The feature is inherent to layouting, ie: this isn't the job of a 3rd party integration plugin.
- The feature can only interact in meaningful and predictible ways with other existing features.
- The feature introduces only meaningful abstractions/concepts that are fully orthogonal with other features.
- The feature is not too complex to implement
Here is an example: margin
. At first, I didn't even want margins.
After all, I can nest a container within another one with padding empty nodes.
Right? Well no.
Say you have the following layout:
dsl! {
row(rules(pct(100), pct(100))) {
row(margins 10, rules(pct(100), pct(100))) {
some_element();
}
}
}
We can't use padding nodes here. Because the inner row
depends on the size
of the parent. Adding nodes would make the inner row
always overflow the
outer row, because it's size will still be 100% that of its parent, in addition
to the two 10 pixel empty padding nodes.
The solution to this is to have a distinction between "outer" and "inner" sizes:
- outer size is the size as seen by the parent
- inner size is the size seen by the children
- inner size = outer size - margin * 2.
The margin
now allows specifying the inner row
as 100% the "size" of the
outer row. In fact it's specifying the size relative to the outer row's size
minus the given margin
.
Now why limit margin
to pixel specification, eschewing percent-based rules?
Paradoxically, it's for usability: It is not clear what the percent is a percentage of. Is it the inner size? The parent's size? children size? The full size after the application of the margin?
We don't know, different people will have different expectations. This avoids any confusion. In any case, this is a situation where empty nodes can be used, since you'll be able to compute the relative size of each node yourself.
Change log
See the [./CHANGELOG.md] file.
Version matrix
bevy | latest supporting version |
---|---|
0.11 | 0.9.0 |
0.10 | 0.3.0 |
License
Licensed under either of
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
- MIT license (LICENSE-MIT or http://opensource.org/licenses/MIT) at your option.
Contribution
Unless you explicitly state otherwise, any contribution intentionally submitted for inclusion in the work by you, as defined in the Apache-2.0 license, shall be dual licensed as above, without any additional terms or conditions.