<div align="center">
# Backer

[](https://crates.io/crates/backer)
[](https://crates.io/crates/backer)
[](https://github.com/cyypherus/backer/blob/main/LICENSE)
**A library for straight-forward UI layout.**
</div>
Dependency free & framework-agnostic. Backer can be used in an index-based layout approach or with inline drawing code.
_This library **only** implements layout & would be most useful along with a GUI library that can do GUI things (like [macroquad](https://github.com/not-fl3/macroquad) or [egui](https://github.com/emilk/egui))._
### Features ✨
- Declarative API: The code should look like the structure it defines
- Minimal interface: No confusing overloads or magic, cascading effects
- Intuitive constraints: Backer adheres to & smoothly resolves size constraints with an advanced algorithm
- Performant: Layout can be comfortably recalculated every frame
- Easy integration & compatibility: Backer should work with just about any UI library with a bit of glue - so it isn't really fine-tuned for any specific UI solution.
This project intends to be a flexible layout tool & not much else.
# Quick Start
## 1. Define your views.
```rust
use backer::nodes::*;
use backer::{Area, Layout};
struct MyState;
enum View {
Label { area: Area, text: &'static str },
}
```
## 2. Implement a `draw` node
The `View` in `Layout<'a, View, MyState>` is returned by `draw` nodes as `Vec<View>` and collected by `layout.draw`.
```rust
fn label<'a>(text: &'static str) -> Layout<'a, View, MyState> {
draw(move |area: Area, _: &mut MyState| {
// The `area` parameter is the space alotted for your view after layout is calculated
// The `state` parameter is *your* global mutable state that you pass when you call layout (necessary for things like text caches)
// These returned values will be collected by `layout.draw`
vec![View::Label { area, text }]
})
}
```
## 3. Combine nodes to define & customize your layout
```rust
fn my_layout<'a>() -> Layout<'a, View, MyState> {
row(vec![
label("Hello"),
])
}
let mut layout = my_layout();
```
## 4. Draw your layout
```rust
// UI libraries generally will expose methods to get the available space for layout.
// In a real implementation this would use the real screen size!
let available_area = Area::new(
0.,
0.,
400.,
600.,
);
let mut my_state = MyState;
// Perform layout & collect the values returned by your draw nodes.
let views: Vec<View> = layout.draw(available_area, &mut my_state);
```
## Preview
Check out the [demo site](https://cyypherus.github.io/backer/): a mock page showcasing layout capabilities in a realistic interface. Built with [egui](https://github.com/emilk/egui)!
[<img src="https://github.com/user-attachments/assets/71c2e83c-67e0-46e9-9bb8-d3bc5926c973">](https://cyypherus.github.io/backer/)
Backer relies on simple rules that can compose to create complex, flexible layouts.

<details>
<summary>See some code</summary>
```rust
column_spaced(
10.,
vec![
draw_a(ui),
row_spaced(
10.,
vec![
draw_b(ui).width(180.).align(Align::Leading),
column_spaced(10., vec![draw_a(ui), draw_b(ui), draw_c(ui)]),
],
),
draw_c(ui),
],
)
.pad(10.)
```
</details>
## Status
The crate is currently usable but new! Breaking changes may be relatively frequent as the crate matures.
## Contributing
> [!NOTE]
> This repo uses `cargo insta` to snapshot test the public API.
> If your PR changes the public API, one of the checks will fail by default.
> If the changes to the public API were intentional you can update the snapshot by running:
>
> `INSTA_UPDATE=always cargo test --features test-api`
Contributions are always welcome 🤗