{"id":15210811,"url":"https://github.com/databasedav/haalka","last_synced_at":"2025-05-16T14:04:46.701Z","repository":{"id":214167679,"uuid":"735862111","full_name":"databasedav/haalka","owner":"databasedav","description":"ergonomic reactive Bevy UI library powered by FRP signals","archived":false,"fork":false,"pushed_at":"2025-05-06T04:15:56.000Z","size":2056881,"stargazers_count":143,"open_issues_count":3,"forks_count":4,"subscribers_count":3,"default_branch":"main","last_synced_at":"2025-05-09T22:09:54.298Z","etag":null,"topics":["bevy","framework","frp","reactive","rust","signals","ui"],"latest_commit_sha":null,"homepage":"","language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"apache-2.0","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/databasedav.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE-APACHE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null}},"created_at":"2023-12-26T09:40:39.000Z","updated_at":"2025-05-07T18:10:50.000Z","dependencies_parsed_at":"2024-01-13T23:35:31.744Z","dependency_job_id":"1aa81d2e-4db5-4b00-a4b7-baa9fb267452","html_url":"https://github.com/databasedav/haalka","commit_stats":{"total_commits":70,"total_committers":3,"mean_commits":"23.333333333333332","dds":"0.11428571428571432","last_synced_commit":"105a0a99c81defe5f351e19235519e32fd8a4542"},"previous_names":["databasedav/haalka"],"tags_count":10,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/databasedav%2Fhaalka","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/databasedav%2Fhaalka/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/databasedav%2Fhaalka/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/databasedav%2Fhaalka/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/databasedav","download_url":"https://codeload.github.com/databasedav/haalka/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":254544146,"owners_count":22088807,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["bevy","framework","frp","reactive","rust","signals","ui"],"created_at":"2024-09-28T08:01:32.487Z","updated_at":"2025-05-16T14:04:46.671Z","avatar_url":"https://github.com/databasedav.png","language":"Rust","funding_links":[],"categories":["Rust","UI"],"sub_categories":[],"readme":"# haalka [হালকা](https://translate.google.com/?sl=bn\u0026tl=en\u0026text=%E0%A6%B9%E0%A6%BE%E0%A6%B2%E0%A6%95%E0%A6%BE\u0026op=translate)\n\n[![Crates.io Version](https://img.shields.io/crates/v/haalka?style=for-the-badge)](https://crates.io/crates/haalka)\n[![Docs.rs](https://img.shields.io/docsrs/haalka?style=for-the-badge)](https://docs.rs/haalka)\n[![Following released Bevy versions](https://img.shields.io/badge/Bevy%20tracking-released%20version-lightblue?style=for-the-badge)](https://bevyengine.org/learn/quick-start/plugin-development/#main-branch-tracking)\n\n```text\nin bengali, haalka means \"light\" (e.g. not heavy) and can also be used to mean \"easy\"\n```\n\n[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).\n\nWhile 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).\n\n## considerations\n\n- 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.\n\n- 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.\n\n## [feature flags](https://docs.rs/haalka/latest/haalka/#feature-flags-1)\n\n## examples\n\u003cp align=\"center\"\u003e\n  \u003cimg src=\"https://raw.githubusercontent.com/databasedav/haalka/main/docs/static/counter.gif\"\u003e\n\u003c/p\u003e\n\n```rust no_run\nuse bevy::prelude::*;\nuse haalka::prelude::*;\n\nfn main() {\n    App::new()\n        .add_plugins((DefaultPlugins, HaalkaPlugin))\n        .add_systems(\n            Startup,\n            (\n                |world: \u0026mut World| {\n                    ui_root().spawn(world);\n                },\n                camera,\n            ),\n        )\n        .run();\n}\n\n#[derive(Component)]\nstruct Counter(Mutable\u003ci32\u003e);\n\nfn ui_root() -\u003e impl Element {\n    let counter = Mutable::new(0);\n    El::\u003cNode\u003e::new()\n        .height(Val::Percent(100.))\n        .width(Val::Percent(100.))\n        .cursor(CursorIcon::default())\n        .align_content(Align::center())\n        .child(\n            Row::\u003cNode\u003e::new()\n                .with_node(|mut node| node.column_gap = Val::Px(15.0))\n                .item(counter_button(counter.clone(), \"-\", -1))\n                .item(\n                    El::\u003cText\u003e::new()\n                        .text_font(TextFont::from_font_size(25.))\n                        .text_signal(counter.signal_ref(ToString::to_string).map(Text)),\n                )\n                .item(counter_button(counter.clone(), \"+\", 1))\n                .update_raw_el(move |raw_el| raw_el.insert(Counter(counter))),\n        )\n}\n\nfn counter_button(counter: Mutable\u003ci32\u003e, label: \u0026str, step: i32) -\u003e impl Element {\n    let hovered = Mutable::new(false);\n    El::\u003cNode\u003e::new()\n        .width(Val::Px(45.0))\n        .align_content(Align::center())\n        .border_radius(BorderRadius::MAX)\n        .cursor(CursorIcon::System(SystemCursorIcon::Pointer))\n        .background_color_signal(\n            hovered\n                .signal()\n                .map_bool(|| Color::hsl(300., 0.75, 0.85), || Color::hsl(300., 0.75, 0.75))\n                .map(BackgroundColor),\n        )\n        .hovered_sync(hovered)\n        .on_click(move || *counter.lock_mut() += step)\n        .child(\n            El::\u003cText\u003e::new()\n                .text_font(TextFont::from_font_size(25.))\n                .text(Text::new(label)),\n        )\n}\n\nfn camera(mut commands: Commands) {\n    commands.spawn(Camera2d);\n}\n```\n\n### on the web\n\nAll examples are compiled to wasm for both webgl2 and webgpu (check [compatibility](\u003chttps://github.com/gpuweb/gpuweb/wiki/Implementation-Status#implementation-status\u003e)) and deployed to github pages.\n\n- [**`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/)\n\n    the example above, a simple counter\n\n- [**`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/)\n\n    a basic button, port of \u003chttps://github.com/bevyengine/bevy/blob/main/examples/ui/button.rs\u003e\n\n- [**`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/)\n\n    alignment API demo, port of \u003chttps://github.com/MoonZoon/MoonZoon/tree/main/examples/align\u003e and \u003chttps://github.com/MoonZoon/MoonZoon/tree/main/examples/align_content\u003e\n\n- [**`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/)\n\n    scrollability API demo, inspired by \u003chttps://github.com/mintlu8/bevy-rectray/blob/main/examples/scroll_discrete.rs\u003e\n\n- [**`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/)\n\n    i can't believe it's not scrolling!\n\n- [**`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/)\n\n    the classic, with adjustable grid size and tick rate\n\n- [**`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/)\n\n    forward ecs changes to the ui, throttled button presses\n\n- [**`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/)\n\n    text inputs, scrolling/viewport control, and reactive lists; promises made promises kept! \u003chttps://discord.com/channels/691052431525675048/1192585689460658348/1193431789465776198\u003e (yes I take requests)\n\n- [**`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/)\n\n    simple calculator, spurred by \u003chttps://discord.com/channels/691052431525675048/885021580353237032/1263661461364932639\u003e\n\n- [**`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/)\n\n    nested dynamic lists, arbitrarily deeply nested retained reactivity, spurred by \u003chttps://discord.com/channels/691052431525675048/885021580353237032/1356769984474517617\u003e\n\n- [**`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/)\n\n    sub menus, sliders, dropdowns, reusable composable widgets, gamepad navigation\n\n- [**`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/)\n\n    grid, icons, drag and drop, tooltips\n\n- [**`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/)\n\n    3D character anchor, customizable widgets\n\n- [**`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/)\n\n    nine-patch buttons, screen size reactivity\n\n- [**`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/)\n\n    scrollable buttons, mutable viewport, text input reactivity\n\nOr run them locally with `cargo`.\n```bash\ncargo run --example counter\ncargo run --example button\ncargo run --example align\ncargo run --example scroll\ncargo run --example scroll_grid\ncargo run --example snake\ncargo run --example dot_counter\ncargo run --example key_values_sorted\ncargo run --example calculator\ncargo run --example nested_lists\n\n# ui challenges from https://github.com/bevyengine/bevy/discussions/11100\ncargo run --example main_menu\ncargo run --example inventory\ncargo run --example healthbar\ncargo run --example responsive_menu\ncargo run --example character_editor\n```\nOr with [`just`](https://github.com/casey/just), e.g. `just example snake -r`.\n\n## Bevy compatibility\n|bevy|haalka|\n|-|-|\n|`0.15`|`0.4`|\n|`0.14`|`0.2`|\n|`0.13`|`0.1`|\n\n## development\n- include submodules when fetching the repo\n    ```bash\n    git clone --recurse-submodules https://github.com/databasedav/haalka.git\n    ```\n- install [just](https://github.com/casey/just?tab=readme-ov-file#installation)\n- install [nickel](https://github.com/tweag/nickel?tab=readme-ov-file#run) for modifying CI configuration (`nickel` must be in your PATH)\n- install [File Watcher](https://marketplace.visualstudio.com/items?itemName=appulate.filewatcher) for automatically syncing nickels\n\n## license\nAll code in this repository is dual-licensed under either:\n\n- MIT License ([LICENSE-MIT](https://github.com/databasedav/haalka/blob/main/LICENSE-MIT) or \u003chttp://opensource.org/licenses/MIT\u003e)\n- Apache License, Version 2.0 ([LICENSE-APACHE](https://github.com/databasedav/haalka/blob/main/LICENSE-APACHE) or \u003chttp://www.apache.org/licenses/LICENSE-2.0\u003e)\n\nat your option.\n\nAssets used in examples may be licensed under different terms, see the [`examples` README](https://github.com/databasedav/haalka/blob/main/examples/README.md).\n\n### your contributions\nUnless 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.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdatabasedav%2Fhaalka","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fdatabasedav%2Fhaalka","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fdatabasedav%2Fhaalka/lists"}