haalka

repository·main·Indexed 41 days ago

https://github.com/databasedav/haalka

An ergonomic, reactive UI library for the Bevy game engine powered by Functional Reactive Programming (FRP) via jonmo signals. It provides a web-like developer experience with high-level alignment semantics, pointer event handling, and signal-integrated text input, drawing inspiration from MoonZoon and Dominator. Version 0.7.1 is compatible with Bevy 0.18.

Tokens
13.3K
Snippets
57
Records
69
Agent score
44%

What's inside haalka

  1. License information

    main

    All code in the haalka repository is dual-licensed under either the MIT License or the Apache License, Version 2.0, at the user's option.

    Note: Assets used in examples may be subject to different licensing terms; refer to the examples/README.md for details.

  2. Core features of haalka

    main

    haalka provides several high-level UI capabilities:

    • Alignment & Layout: Simple high-level alignment semantics and a grid layout model (ported from MoonZoon).
    • Pointer Events:
      • Hover detection (including Enter and Leave events).
      • Click handling (on_click) and on-click-outside.
      • Pressed methods with throttling support.
    • Cursor Management: cursor-on-hover management.
    • Input: Signals-integrated text input (built on bevy_ui_text_input).
    • Navigation & Viewport: Mouse wheel scroll handling and viewport mutation handling.
    • Macros: Macro rules for adding signal helper methods to custom element structs.
  3. How Pull Request previews are deployed

    main

    Pull request previews are triggered via a maintainer review.

    1. A maintainer must submit a /deploy comment review from the pull request's Files changed → Review changes dialog.
    2. This works for both fork and same-repository pull requests.
    3. The deployment is bound to the exact reviewed commit. If the PR changes, a new /deploy review is required.
    4. Submitting a new /deploy review cancels any previous preview build or deployment for that PR.
    5. New commits to a PR will automatically cancel older in-progress preview workflows for that same PR.
    6. New pushes to main will cancel older in-progress example deployments.
  4. Run haalka examples locally

    main

    You can run the provided examples using cargo run with the --example flag.

    Example commands:

    cargo run --example counter
    cargo run --example button
    cargo run --example align
    cargo run --example scroll
    cargo run --example snake

    If you have just installed, you can also use it to run examples:

    just example snake -r
    cargo run --example counter
  5. Set up the development environment

    main

    To set up a local development environment for haalka, follow these steps:

    1. Clone the repository: Use the following command to avoid the gh-pages branch and ensure all submodules are included:
      git clone --single-branch --branch main --recurse-submodules https://github.com/databasedav/haalka.git
    2. Install just: A command runner used for project tasks.
    3. Install nickel: Required for modifying CI configuration. Ensure nickel is in your PATH.
    4. Install File Watcher: (VS Code extension) Recommended for automatically syncing nickels.
    git clone --single-branch --branch main --recurse-submodules https://github.com/databasedav/haalka.git
  6. Setup haalka in a Bevy App

    main

    To use haalka, add HaalkaPlugin to your Bevy App. You typically spawn your UI root using the ui_root() helper within a Startup system. Note that ui_root() returns an impl Element which can be spawned into the Bevy World.

    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();
    }
    
    // Example ui_root implementation
    fn ui_root() -> impl Element {
        El::<Node>::new()
            .child(El::<Text>::new().text("Hello Haalka"))
    }
    
    fn camera(mut commands: Commands) {
        commands.spawn(Camera2d);
    }
  7. Integrate kaaj into Nickel workflows

    main

    To use kaaj as a Nickel package for GitHub Actions output targets, add it to your Nickel-pkg.ncl dependencies. You can then import specific targets (like build_example, ci, release, etc.) into your .ncl file and configure them with your repository URL and deployment targets.

    # In Nickel-pkg.ncl
    dependencies = {
      kaaj = 'Git { url = "https://github.com/databasedav/haalka", ref = 'Tag "v0.1" },
    }
    
    # In your .ncl file
    let { build_example, ci, cleanup_pr_previews, deploy_reviewed_pr_preview, examples_on_main, pr_previews, release, reviewed_pr_preview, .. } = import kaaj in
    let REPO = "https://github.com/databasedav/haalka" in
    let GITHUB_PAGES_URL = "https://databasedav.github.io/haalka" in
    {
        build_example_ = build_example "webgpu",
        ci_ = ci,
        cleanup_pr_previews_ = cleanup_pr_previews REPO,
        pr_previews_ = pr_previews,
        reviewed_pr_preview_ = reviewed_pr_preview,
        deploy_reviewed_pr_preview_ = deploy_reviewed_pr_preview REPO GITHUB_PAGES_URL,
        examples_on_main_ = examples_on_main REPO,
        release_ = release,
    }
  8. Set up kaaj in a Justfile

    main

    To use the kaaj Justfile for Bevy-related CI tasks (like building or serving examples), you must fetch the kaaj.just file using curl and import it into your local justfile. You can configure exclude_examples and export_nickels to control which examples are processed and which Nickel targets are exported to GitHub Actions workflows.

    KAAJ_HAALKA_COMMIT := "5091123d38b0a0eafd3b7d6b46350f6072748f0b"
    
    fetch_kaaj_justfile:
      curl https://raw.githubusercontent.com/databasedav/haalka/{{ KAAJ_HAALKA_COMMIT }}/kaaj/justfile > kaaj.just
    
    import? 'kaaj.just'
    
    exclude_examples := '"example1", "example2"'
    
    export_nickels := "ci build_example pr_previews reviewed_pr_preview deploy_reviewed_pr_preview examples_on_main cleanup_pr_previews release"
  9. What is an Element in haalka?

    main

    An Element is the fundamental UI building block in haalka. It is a type that wraps a jonmo::Builder and provides high-level UI capabilities.

    Elements are categorized into two types:

    1. Primitive Elements: Types like El<Node> or Column<Node> that directly hold a jonmo::Builder. These implement the Element trait directly.
    2. Widgets: Custom UI components that wrap other elements. These implement the ElementWrapper trait, which automatically provides BuilderWrapper and Element implementations.

    All Elements can be aligned using haalka's alignment semantics and can be granted additional abilities like pointer event awareness, viewport mutability, or scrollability.

    use bevy::prelude::*;
    use haalka::prelude::*;
    
    // Example of a custom Widget using ElementWrapper
    struct MyWidget {
        el: El<Node>,
        data: usize,
    }
    
    impl ElementWrapper for MyWidget {
        type EL = El<Node>;
        fn element_mut(&mut self) -> &mut Self::EL {
            &mut self.el
        }
    }
    
    // Widgets can then implement UI abilities
    impl GlobalEventAware for MyWidget {}
    impl PointerEventAware for MyWidget {}
    impl ViewportMutable for MyWidget {}
    impl MouseWheelScrollable for MyWidget {}
  10. Create custom widgets using ElementWrapper

    main

    The ElementWrapper trait is the foundation for building complex, reusable "widgets". A widget is a struct that wraps an Element (or another ElementWrapper) and can implement additional UI traits to extend its functionality.

    To create a widget, implement ElementWrapper by defining the type of element it wraps and providing a mutable reference to it via element_mut().

    use bevy::prelude easily;
    use haalka::futures_signals::prelude::*;
    
    struct MyWidget {
        el: El<Node>,
        data: Mutable<usize>,
    }
    
    impl ElementWrapper for MyWidget {
        type EL = El<Node>;
        fn element_mut(&mut self) -> &mut Self::EL {
            &mut self.el
        }
    }
    
    // You can now grant the widget additional abilities
    impl GlobalEventAware for MyWidget {}
    impl PointerEventAware for MyWidget {}
    impl ViewportMutable for MyWidget {}
    impl MouseWheelScrollable for MyWidget {}