haalka
ergonomic reactive Bevy UI library powered by FRP signals
git clone https://github.com/databasedav/haalka.gitdatabasedav/haalkahaalka হালকা
in bengali, haalka means "light" (e.g. not heavy) and can also be used to mean "easy"
haalka is an ergonomic reactive Bevy UI library powered by the FRP signals of jonmo with API ported from web UI libraries MoonZoon and Dominator.
assorted features
- simple high-level alignment semantics ported from MoonZoon (see align example below)
- pointer event handling methods
- cursor-on-hover management
- global event handling methods
- mouse wheel scroll handling methods
- signals-integrated text input, a thin layer on top of bevy_ui_text_input
- viewport mutation handling methods
- simple grid layout model ported from MoonZoon
- macro rules for adding signal helper methods to custom element structs
feature flags
examples
use bevy::prelude::*;
use haalka::prelude::*;
fn main() {
App::new()
.add_plugins((DefaultPlugins, HaalkaPlugin::new()))
.add_systems(
Startup,
(
|world: &mut World| {
ui_root().spawn(world);
},
camera,
),
)
.run();
}
#[derive(Component, Clone, Deref, DerefMut)]
struct Counter(i32);
fn ui_root() -> impl Element {
let counter_holder = LazyEntity::new();
El::<Node>::new()
.with_node(|mut node| {
node.height = Val::Percent(100.);
node.width = Val::Percent(100.);
})
.insert(Pickable::default())
.cursor(CursorIcon::default())
.align_content(Align::center())
.child(
Row::<Node>::new()
.with_node(|mut node| node.column_gap = Val::Px(15.0))
.insert(Counter(0))
.lazy_entity(counter_holder.clone())
.item(counter_button(counter_holder.clone(), "-", -1))
.item(
El::<Text>::new()
.text_font(TextFont::from_font_size(25.))
.text_signal(
signal::from_component_changed::<Counter>(counter_holder.clone())
.map_in(deref_copied)
.map_in_ref(ToString::to_string)
.map_in(Text)
.map_in(Some),
),
)
.item(counter_button(counter_holder.clone(), "+", 1)),
)
}
fn counter_button(counter_holder: LazyEntity, label: &'static str, step: i32) -> impl Element {
let lazy_entity = LazyEntity::new();
El::<Node>::new()
.with_node(|mut node| {
node.width = Val::Px(45.0);
node.border_radius = BorderRadius::MAX;
})
.insert((Pickable::default(), Hoverable))
.align_content(Align::center())
.cursor(CursorIcon::System(SystemCursorIcon::Pointer))
.lazy_entity(lazy_entity.clone())
.background_color_signal(
signal::from_entity(lazy_entity)
.has_component::<Hovered>()
.dedupe()
.map_bool_in(|| Color::hsl(300., 0.75, 0.85), || Color::hsl(300., 0.75, 0.75))
.map_in(BackgroundColor)
.map_in(Some),
)
.on_click(move |_: In<_>, mut counters: Query<&mut Counter>| {
if let Ok(mut counter) = counters.get_mut(*counter_holder) {
**counter += step;
}
})
.child(
El::<Text>::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.
-
the example above, a simple counter
-
a basic button, port of https://github.com/bevyengine/bevy/blob/b176e1888c04b6c37035b85c115ad3a1266c22dd/examples/ui/button.rs
-
alignment API demo, port of https://github.com/MoonZoon/MoonZoon/tree/19c6cf6b4d07cd27bee7758977ef1ea4d5b9933d/examples/align and https://github.com/MoonZoon/MoonZoon/tree/19c6cf6b4d07cd27bee7758977ef1ea4d5b9933d/examples/align_content
-
scrollability API demo, inspired by https://github.com/mintlu8/bevy-rectray/blob/main/examples/scroll_discrete.rs
-
i can't believe it's not scrolling!
-
the classic, with adjustable grid size and tick rate
-
forward ecs changes to the ui, throttled button presses
-
dragging with z-index stacking
-
key_values_sortedwebgl2 webgputext inputs, scrolling/viewport control, and reactive lists; promises made promises kept! https://discord.com/channels/691052431525675048/1192585689460658348/1193431789465776198 (yes I take requests)
-
simple calculator, spurred by https://discord.com/channels/691052431525675048/885021580353237032/1263661461364932639
-
nested dynamic lists, arbitrarily deeply nested retained reactivity, spurred by https://discord.com/channels/691052431525675048/885021580353237032/1356769984474517617
-
sub menus, sliders, dropdowns, reusable composable widgets, gamepad navigation
-
grid, icons, drag and drop, tooltips
-
3D character anchor, customizable widgets
-
nine-patch buttons, screen size reactivity
-
character_editorwebgl2 webgpuscrollable buttons, mutable viewport, text input reactivity
-
futures_signals_jonmo_compatwebgl2 webgpudemonstrates that both futures-signals and jonmo signals backends can be used together
Or run them locally with cargo.
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 cargo run --example futures_signals_jonmo_compat --features futures_signals_ui
Or with just, e.g. just example snake -r.
Bevy compatibility
| bevy | haalka |
|---|---|
0.18 |
0.7 |
0.17 |
0.6 |
0.16 |
0.5 |
0.15 |
0.4 |
0.14 |
0.2 |
0.13 |
0.1 |
development
- avoid the gh-pages branch and include submodules when fetching the repo
git clone --single-branch --branch main --recurse-submodules https://github.com/databasedav/haalka.git
- install just
- install nickel for modifying CI configuration (
nickelmust be in your PATH) - install File Watcher for automatically syncing nickels
license
All code in this repository is dual-licensed under either:
- MIT License (LICENSE-MIT or http://opensource.org/licenses/MIT)
- Apache License, Version 2.0 (LICENSE-APACHE or http://www.apache.org/licenses/LICENSE-2.0)
at your option.
Assets used in examples may be licensed under different terms, see the examples README.
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.
more like this
TachiSnap
TachiSnap — Pixel Snapper for animation pixel artists. Rust + WebAssembly client-side tool for cleaning up AI-generated…
bit_gossip
Pathfinding library for calculating all node pairs' shortest paths in an unweighted undirected graph.
