cuicui_layout 0.9.0

A layout algorithm for bevy and made to be understood by humans
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
[![Bevy tracking](https://img.shields.io/badge/Bevy%20tracking-released%20version-lightblue)](https://github.com/bevyengine/bevy/blob/main/docs/plugins_guidelines.md#main-branch-tracking)
[![Latest version](https://img.shields.io/crates/v/cuicui_layout.svg)](https://crates.io/crates/cuicui_layout)
[![MIT/Apache 2.0](https://img.shields.io/badge/license-MIT%2FApache-blue.svg)](./LICENSE)
[![Documentation](https://docs.rs/cuicui_layout/badge.svg)](https://docs.rs/cuicui_layout/)

# 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][vote]** |

[vote]: https://github.com/nicopap/cuicui_layout/discussions/72

A dumb layout algorithm you can rely on, built for and with bevy.

<details><summary><h2>The Cyberpunk 2077 showcase</h2></summary>

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

```rust
use bevy::prelude::*;
use cuicui_layout_bevy_ui::UiDsl as Dsl;
use cuicui_layout::{LayoutRootCamera, dsl, dsl_functions::{px, pct}};

fn setup(mut cmds: Commands, serv: Res<AssetServer>) {

cmds.spawn((Camera2dBundle::default(), LayoutRootCamera));
let menu_buttons = [
    "CONTINUE",
    "NEW GAME",
    "LOAD GAME",
    "SETTINGS",
    "ADDITIONAL CONTENT",
    "CREDITS",
    "QUIT GAME",
];
let title_card = serv.load::<Image, _>("logo.png");
let bg = serv.load("background.png");
let board = serv.load("board.png");
let button = serv.load("button.png");

dsl! {
    &mut cmds,
    row(screen_root, "root", main_margin 100., distrib_start, align_start, image &bg) {
        column("menu", width px(310), height pct(100), main_margin 40., image &board) {
            spawn(image &title_card, "Title card", width pct(100));
            spawn(ui title_card, "Title card 2", width pct(50));
            code(let cmds) {
                for n in &menu_buttons {
                    let name = format!("{n} button");
                    dsl!(cmds, spawn(ui *n, named name, image &button, height px(33)););
                }
            }
        }
    }
};

}
```

</details>

## 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](./examples/chirpunk/) 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:

```sh
cargo run --bin sprite_debug -p sprite_debug --features cuicui_layout/debug
```

## 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 of `bevy_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`:

```toml
[dependencies]
cuicui_layout_bevy_ui = "0.9.0"
cuicui_layout = "0.9.0"
```

Then, use `cuicui_layout` in your crate with the [`dsl!`] macro:

```rust,no_run
use bevy::prelude::*;
use cuicui_layout::{dsl, LayoutRootCamera, dsl_functions::*};
use cuicui_layout_bevy_ui::UiDsl as Dsl;

fn main() {
    // Do not forget to add cuicui_layout_bevy_{ui,sprite}::Plugin
    App::new().add_plugins((DefaultPlugins, cuicui_layout_bevy_ui::Plugin))
        .add_systems(Startup, setup)
        .run();
}
fn setup(mut commands: Commands) {
    // Use LayoutRootCamera to mark a camera as the screen boundaries.
    commands.spawn((Camera2dBundle::default(), LayoutRootCamera));
    dsl! { &mut commands,
        // Use screen_root to follow the screen's boundaries
        row(screen_root) {
            row(margin 9., border(5, Color::CYAN), bg Color::NAVY) {
                spawn(ui "Hello world!");
            }
        }
    };
}
```

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:

```rust,no_run
use bevy::prelude::*;
use cuicui_layout_bevy_ui::UiDsl as Dsl;

// ...

fn setup(mut commands: Commands) {
    use cuicui_dsl::{IntoEntityCommands, DslBundle};
    let mut x = <Dsl>::default();
    x.screen_root();
    x.node(&mut commands.to_cmds(), |cmds| {
      let mut x = <Dsl>::default();
      x.margin(9.);
      x.border(5, Color::CYAN);
      x.bg(Color::NAVY);
      x.node(&mut cmds.to_cmds(), |cmds| {
        let mut x = <Dsl>::default();
        let mut cmds = cmds.to_cmds();
        x.ui("Hello world!");
        x.insert(&mut cmds);
      });
    });
}
```

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](./chirp).

#### How do I use `.chirp` files?

Reproducing the previous example with `.chirp` files:

First, write the chirp file:

```ron
// 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:

```rust,no_run
use bevy::prelude::*;
use cuicui_layout::{dsl, LayoutRootCamera, dsl_functions::*};
use cuicui_layout_bevy_ui::UiDsl;
use cuicui_chirp::Chirp;

fn main() {
    // Do not forget to add cuicui_layout_bevy_{ui,sprite}::Plugin
    // and cuicui_chirp::loader::Plugin with the wanted DSL as type parameter
    App::new().add_plugins((
        DefaultPlugins,
        cuicui_layout_bevy_ui::Plugin,
        cuicui_chirp::loader::Plugin::new::<UiDsl>(),
    ))
        .add_systems(Startup, setup)
        .run();
}
fn setup(mut commands: Commands, assets: Res<AssetServer>) {
    commands.spawn((Camera2dBundle::default(), LayoutRootCamera));
    // Spawn the chirp scene as is. Yeah that's it.
    commands.spawn(assets.load::<Chirp, _>("scene.chirp"));
}
```

[`cuicui_layout_bevy_sprite`]: https://lib.rs/crates/cuicui_layout_bevy_sprite
[`cuicui_layout_bevy_ui`]: https://lib.rs/crates/cuicui_layout_bevy_ui
[`cuicui_layout`]: https://lib.rs/crates/cuicui_layout
[`cuicui_dsl`]: https://lib.rs/crates/cuicui_dsl
[`cuicui_chirp`]: https://lib.rs/crates/cuicui_chirp
[`LayoutDsl`]: https://docs.rs/cuicui_layout/latest/cuicui_layout/dsl/struct.LayoutDsl.html
[`ReflectDsl`]: https://docs.rs/cuicui_chirp/latest/cuicui_chirp/reflect/struct.ReflectDsl.html
[`dsl!`]: https://docs.rs/cuicui_dsl/latest/cuicui_dsl/macro.dsl.html

## `cuicui_layout` crates

This repository contains several crates:

- `cuicui_dsl` ([dsl]dsl): The `dsl!` macro and `DslBundle`.
- `cuicui_chirp` ([chirp]chirp): A parser for files that follow the `dsl!`
  syntax. It creates a scene identical to what the same text passed to the
  `dsl!` macro would produce.
  \
  It also includes a bevy plugin to load `.chirp` files defined in this format.
- `cuicui_layout` ([layout]layout): The base algorithm and components, does not make any assumption
  about how it is used, beside the requirement that layout nodes be bevy `Entitiy` and
  uses `bevy_hierarchy`. Exports a `LayoutDsl` to use with the `dsl!` macro.
- `cuicui_layout_bevy_ui` ([ui]ui): Integration with `bevy_ui`, including extension to `UiDsl`
  for `UiImage`, `Text`, background images and background colors.
- `cuicui_layout_bevy_sprite` ([sprite]sprite): `bevy_sprite` integration, supports
  `Mesh2dHandle`, `Sprite` and `Text2d`. This isn't as good as the `bevy_ui`-based integration
  when it comes to content-driven sizes, but otherwise should work very much like the `bevy_ui`
  integration.

## Cargo features

- `cuicui_chirp/macros` (on by default): Define and export the `parse_dsl_impl`
  attribute macro. This allows deriving `ParseDsl` automatically from an `impl`
  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 the `ChirpLoader` bevy asset loader, and the
  `Chirp::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): Derive `bevy_reflect` traits for cuicui_layout
  types & register them.
- `cuicui_layout_bevy_sprite/sprite_text` (on by default): implement content-sized layout nodes
  for `Text2dBundle`.

[debug.md]: https://docs.rs/cuicui_layout/latest/cuicui_layout/debug/index.html

## 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`, not `Transform`, you need to add a system that sets
  `Transform` based on `LayoutRect`.
- 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 in `O(n)` where `n` 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 a `Direction` 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 be `Alignment` 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.

[`bevy-inspector-egui`]: https://lib.rs/crates/bevy-inspector-egui

### Flexbox FAQ

**Q**: Where is `padding`?
<br>**A**: `padding` is equivalent to `margin` in cuicui_layout. `margin` and `border`
doesn't make conceptual sense.

**Q**: Why not call it `padding` then?
<br>**A**: Look at the dictionary definition of "margin" and "padding".

**Q**: How do I center a node?
<br>**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`?
<br>**A**: use `row` and `column`

**Q**: What are the equivalents of `column-reverse` and `row-reverse`?
<br>**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`?
<br>**A**: None, do you really need it?

**Q**: What is the equivalent of `align_item`, `align_self`, `align_content`, `justify_content`?
<br>**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`?
<br>**A**: Do you even know what they do?

**Q**: Why can't child container overflow their parents?
<br>**A**: It's likely you didn't expect this, so we report it as an error.

**Q**: How do I make a grid?
<br>**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:

```text
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]licenses/LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
 * MIT license ([LICENSE-MIT]licenses/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.