https://github.com/databasedav/haalka
ergonomic reactive Bevy UI library powered by FRP signals
https://github.com/databasedav/haalka
bevy framework frp reactive rust signals ui
Last synced: 29 days ago
JSON representation
ergonomic reactive Bevy UI library powered by FRP signals
- Host: GitHub
- URL: https://github.com/databasedav/haalka
- Owner: databasedav
- License: apache-2.0
- Created: 2023-12-26T09:40:39.000Z (over 1 year ago)
- Default Branch: main
- Last Pushed: 2025-04-04T04:41:20.000Z (about 1 month ago)
- Last Synced: 2025-04-04T08:09:03.281Z (about 1 month ago)
- Topics: bevy, framework, frp, reactive, rust, signals, ui
- Language: Rust
- Homepage:
- Size: 1.01 GB
- Stars: 135
- Watchers: 3
- Forks: 4
- Open Issues: 4
-
Metadata Files:
- Readme: README.md
- Changelog: CHANGELOG.md
- License: LICENSE-APACHE
Awesome Lists containing this project
- awesome-bevy - `haalka`
- awesome-bevy - `haalka`
README
# haalka [হালকা](https://translate.google.com/?sl=bn&tl=en&text=%E0%A6%B9%E0%A6%BE%E0%A6%B2%E0%A6%95%E0%A6%BE&op=translate)
[](https://crates.io/crates/haalka)
[](https://docs.rs/haalka)
[](https://bevyengine.org/learn/quick-start/plugin-development/#main-branch-tracking)```text
in bengali, haalka means "light" (e.g. not heavy) and can also be used to mean "easy"
```[haalka](https://github.com/databasedav/haalka) is an ergonomic reactive [Bevy](https://github.com/bevyengine/bevy) UI library powered by the incredible [FRP](https://en.wikipedia.org/wiki/Functional_reactive_programming) signals of [futures-signals](https://github.com/Pauan/rust-signals) and the convenient async ECS of [bevy-async-ecs](https://github.com/dlom/bevy-async-ecs) with API ported from web UI libraries [MoonZoon](https://github.com/MoonZoon/MoonZoon) and [Dominator](https://github.com/Pauan/rust-dominator).
While haalka is primarily targeted at UI and provides high level UI abstractions as such, its [core abstraction](https://docs.rs/haalka/latest/haalka/struct.RawHaalkaEl.html) can be used to manage signals-powered reactivity for any entity, not just [`bevy_ui` nodes](https://github.com/bevyengine/bevy/blob/main/crates/bevy_ui/src/node_bundles.rs).
## considerations
- Reactive updates done by haalka are [**eventually consistent**](https://en.wikipedia.org/wiki/Eventual_consistency), that is, once some ECS world state has been updated, any downstream reactions should not be expected to run in the same frame. This is due to the indirection involved with using an async signals library, which dispatches Bevy commands after polling by the async runtime. The resulting "lag" should not be noticeable in most popular cases, e.g. reacting to hover/click state or synchronizing UI (one can run the examples to evaluate this themselves), but in cases where frame perfect responsiveness is critical, one should simply use Bevy-native systems directly.
- If one is using the `text_input` feature (enabled by default) and using multiple cameras in the same world, they must enable the `multicam` feature AND add the `bevy_cosmic_edit::CosmicPrimaryCamera` marker component to the primary camera.
## [feature flags](https://docs.rs/haalka/latest/haalka/#feature-flags-1)
## examples
![]()
```rust no_run
use bevy::prelude::*;
use haalka::prelude::*;fn main() {
App::new()
.add_plugins((DefaultPlugins, HaalkaPlugin))
.add_systems(
Startup,
(
|world: &mut World| {
ui_root().spawn(world);
},
camera,
),
)
.run();
}#[derive(Component)]
struct Counter(Mutable);fn ui_root() -> impl Element {
let counter = Mutable::new(0);
El::::new()
.height(Val::Percent(100.))
.width(Val::Percent(100.))
.cursor(CursorIcon::default())
.align_content(Align::center())
.child(
Row::::new()
.with_node(|mut node| node.column_gap = Val::Px(15.0))
.item(counter_button(counter.clone(), "-", -1))
.item(
El::::new()
.text_font(TextFont::from_font_size(25.))
.text_signal(counter.signal_ref(ToString::to_string).map(Text)),
)
.item(counter_button(counter.clone(), "+", 1))
.update_raw_el(move |raw_el| raw_el.insert(Counter(counter))),
)
}fn counter_button(counter: Mutable, label: &str, step: i32) -> impl Element {
let hovered = Mutable::new(false);
El::::new()
.width(Val::Px(45.0))
.align_content(Align::center())
.border_radius(BorderRadius::MAX)
.cursor(CursorIcon::System(SystemCursorIcon::Pointer))
.background_color_signal(
hovered
.signal()
.map_bool(|| Color::hsl(300., 0.75, 0.85), || Color::hsl(300., 0.75, 0.75))
.map(BackgroundColor),
)
.hovered_sync(hovered)
.on_click(move || *counter.lock_mut() += step)
.child(
El::::new()
.text_font(TextFont::from_font_size(25.))
.text(Text::new(label)),
)
}fn camera(mut commands: Commands) {
commands.spawn(Camera2d);
}
```### on the web
All examples are compiled to wasm for both webgl2 and webgpu (check [compatibility]()) and deployed to github pages.
- [**`counter`**](https://github.com/databasedav/haalka/blob/main/examples/counter.rs) [webgl2](https://databasedav.github.io/haalka/examples/webgl2/counter/) [webgpu](https://databasedav.github.io/haalka/examples/webgpu/counter/)
the example above, a simple counter
- [**`button`**](https://github.com/databasedav/haalka/blob/main/examples/button.rs) [webgl2](https://databasedav.github.io/haalka/examples/webgl2/button/) [webgpu](https://databasedav.github.io/haalka/examples/webgpu/button/)
a basic button, port of
- [**`align`**](https://github.com/databasedav/haalka/blob/main/examples/align.rs) [webgl2](https://databasedav.github.io/haalka/examples/webgl2/align/) [webgpu](https://databasedav.github.io/haalka/examples/webgpu/align/)
alignment API demo, port of and
- [**`scroll`**](https://github.com/databasedav/haalka/blob/main/examples/scroll.rs) [webgl2](https://databasedav.github.io/haalka/examples/webgl2/scroll/) [webgpu](https://databasedav.github.io/haalka/examples/webgpu/scroll/)
scrollability API demo, inspired by
- [**`scroll_grid`**](https://github.com/databasedav/haalka/blob/main/examples/scroll_grid.rs) [webgl2](https://databasedav.github.io/haalka/examples/webgl2/scroll_grid/) [webgpu](https://databasedav.github.io/haalka/examples/webgpu/scroll_grid/)
i can't believe it's not scrolling!
- [**`snake`**](https://github.com/databasedav/haalka/blob/main/examples/snake.rs) [webgl2](https://databasedav.github.io/haalka/examples/webgl2/snake/) [webgpu](https://databasedav.github.io/haalka/examples/webgpu/snake/)
the classic, with adjustable grid size and tick rate
- [**`dot_counter`**](https://github.com/databasedav/haalka/blob/main/examples/dot_counter.rs) [webgl2](https://databasedav.github.io/haalka/examples/webgl2/dot_counter/) [webgpu](https://databasedav.github.io/haalka/examples/webgpu/dot_counter/)
forward ecs changes to the ui, throttled button presses
- [**`key_values_sorted`**](https://github.com/databasedav/haalka/blob/main/examples/key_values_sorted.rs) [webgl2](https://databasedav.github.io/haalka/examples/webgl2/key_values_sorted/) [webgpu](https://databasedav.github.io/haalka/examples/webgpu/key_values_sorted/)
text inputs, scrolling/viewport control, and reactive lists; promises made promises kept! (yes I take requests)
- [**`calculator`**](https://github.com/databasedav/haalka/blob/main/examples/calculator.rs) [webgl2](https://databasedav.github.io/haalka/examples/webgl2/calculator/) [webgpu](https://databasedav.github.io/haalka/examples/webgpu/calculator/)
simple calculator, spurred by
- [**`nested_lists`**](https://github.com/databasedav/haalka/blob/main/examples/nested_lists.rs) [webgl2](https://databasedav.github.io/haalka/examples/webgl2/nested_lists/) [webgpu](https://databasedav.github.io/haalka/examples/webgpu/nested_lists/)
nested dynamic lists, arbitrarily deeply nested retained reactivity, spurred by
- [**`main_menu`**](https://github.com/databasedav/haalka/blob/main/examples/main_menu.rs) [webgl2](https://databasedav.github.io/haalka/examples/webgl2/main_menu/) [webgpu](https://databasedav.github.io/haalka/examples/webgpu/main_menu/)
sub menus, sliders, dropdowns, reusable composable widgets, gamepad navigation
- [**`inventory`**](https://github.com/databasedav/haalka/blob/main/examples/inventory.rs) [webgl2](https://databasedav.github.io/haalka/examples/webgl2/inventory/) [webgpu](https://databasedav.github.io/haalka/examples/webgpu/inventory/)
grid, icons, drag and drop, tooltips
- [**`healthbar`**](https://github.com/databasedav/haalka/blob/main/examples/healthbar.rs) [webgl2](https://databasedav.github.io/haalka/examples/webgl2/healthbar/) [webgpu](https://databasedav.github.io/haalka/examples/webgpu/healthbar/)
3D character anchor, customizable widgets
- [**`responsive_menu`**](https://github.com/databasedav/haalka/blob/main/examples/responsive_menu.rs) [webgl2](https://databasedav.github.io/haalka/examples/webgl2/responsive_menu/) [webgpu](https://databasedav.github.io/haalka/examples/webgpu/responsive_menu/)
nine-patch buttons, screen size reactivity
- [**`character_editor`**](https://github.com/databasedav/haalka/blob/main/examples/character_editor.rs) [webgl2](https://databasedav.github.io/haalka/examples/webgl2/character_editor/) [webgpu](https://databasedav.github.io/haalka/examples/webgpu/character_editor/)
scrollable buttons, mutable viewport, text input reactivity
Or run them locally with `cargo`.
```bash
cargo run --example counter
cargo run --example button
cargo run --example align
cargo run --example scroll
cargo run --example scroll_grid
cargo run --example snake
cargo run --example dot_counter
cargo run --example key_values_sorted
cargo run --example calculator
cargo run --example nested_lists# ui challenges from https://github.com/bevyengine/bevy/discussions/11100
cargo run --example main_menu
cargo run --example inventory
cargo run --example healthbar
cargo run --example responsive_menu
cargo run --example character_editor
```
Or with [`just`](https://github.com/casey/just), e.g. `just example snake -r`.## Bevy compatibility
|bevy|haalka|
|-|-|
|`0.15`|`0.4`|
|`0.14`|`0.2`|
|`0.13`|`0.1`|## development
- include submodules when fetching the repo
```bash
git clone --recurse-submodules https://github.com/databasedav/haalka.git
```
- install [just](https://github.com/casey/just?tab=readme-ov-file#installation)
- install [nickel](https://github.com/tweag/nickel?tab=readme-ov-file#run) for modifying CI configuration (`nickel` must be in your PATH)
- install [File Watcher](https://marketplace.visualstudio.com/items?itemName=appulate.filewatcher) for automatically syncing nickels## license
All code in this repository is dual-licensed under either:- MIT License ([LICENSE-MIT](https://github.com/databasedav/haalka/blob/main/LICENSE-MIT) or )
- Apache License, Version 2.0 ([LICENSE-APACHE](https://github.com/databasedav/haalka/blob/main/LICENSE-APACHE) or )at your option.
Assets used in examples may be licensed under different terms, see the [`examples` README](https://github.com/databasedav/haalka/blob/main/examples/README.md).
### your contributions
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.