{"id":19294361,"url":"https://github.com/robertdodd/bevy_ui_nav","last_synced_at":"2026-06-22T09:32:16.453Z","repository":{"id":230101101,"uuid":"767211349","full_name":"robertdodd/bevy_ui_nav","owner":"robertdodd","description":null,"archived":false,"fork":false,"pushed_at":"2025-06-03T06:18:51.000Z","size":130,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2025-11-17T09:23:57.729Z","etag":null,"topics":[],"latest_commit_sha":null,"homepage":null,"language":"Rust","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/robertdodd.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"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,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2024-03-04T22:24:47.000Z","updated_at":"2025-05-15T21:44:25.000Z","dependencies_parsed_at":"2024-03-27T21:32:35.998Z","dependency_job_id":"a8870408-e669-49e9-ae27-89a736605d2c","html_url":"https://github.com/robertdodd/bevy_ui_nav","commit_stats":null,"previous_names":["robertdodd/bevy_ui_nav"],"tags_count":3,"template":false,"template_full_name":null,"purl":"pkg:github/robertdodd/bevy_ui_nav","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robertdodd%2Fbevy_ui_nav","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robertdodd%2Fbevy_ui_nav/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robertdodd%2Fbevy_ui_nav/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robertdodd%2Fbevy_ui_nav/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/robertdodd","download_url":"https://codeload.github.com/robertdodd/bevy_ui_nav/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/robertdodd%2Fbevy_ui_nav/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":34643614,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-06-22T02:00:06.391Z","response_time":106,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"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":[],"created_at":"2024-11-09T22:38:02.369Z","updated_at":"2026-06-22T09:32:16.424Z","avatar_url":"https://github.com/robertdodd.png","language":"Rust","funding_links":[],"categories":[],"sub_categories":[],"readme":"# Bevy Ui Nav\n\nA [Bevy](https://bevyengine.org/) plugin that enables spatial UI navigation between UI nodes via key-presses and\nconsolidates click event handling from key presses and mouse button clicks (using bevy `Interaction` internally).\n\n---\n\n\n## Features\n\n- No external dependencies besides `bevy`.\n- Click events triggered on mouse button release.\n\n## Differences from `bevy-ui-navigation`\n\nThe key difference from [bevy-ui-navigation](https://github.com/nicopap/ui-navigation) are:\n\n- No automatic sub-menu navigation. You need to manually send a `NavRequest::SetFocus` event to change focus to a menu.\n- No external dependencies (`bevy-ui-navigation` depends on `bevy_mod_picking`). `bevy_ui_nav` uses `Interaction` and\n    `RelativeCursorPosition` for mouse events.\n- Automatically handles movement when holding a directional key.\n\n## Usage\n\nAdd the plugin to your app:\n\n```rust\nfn main() {\n    App::new()\n        .add_plugins((DefaultPlugins, BevyUiNavPlugin))\n        .add_systems(Startup, startup)\n        .add_systems(\n            Update,\n            (\n                handle_focus_keypress.before(UiNavSet),\n                button_style.after(UiNavSet),\n                handle_click_events\n                    .after(UiNavSet)\n                    .run_if(on_event::\u003cFocusableClickEvent\u003e()),\n            ),\n        )\n        .run();\n}\n```\n\nAdd `Focusable` components when spawning UI nodes:\n\nNOTE: The following components will always be added to new `Focusable` entities:\n\n- `Interaction`\n- `RelativeCursorPosition`\n\n```rust\ncommands\n    .spawn((\n        // Add focusable here:\n        Focusable::default(),\n        // Add a custom component for identifying which button was clicked:\n        ButtonAction::Quit,\n        // Add along with a standard bevy UI node:\n        ButtonBundle {\n            style: Style {\n                align_items: AlignItems::Center,\n                justify_content: JustifyContent::Center,\n                ..default()\n            },\n            background_color: Color::DARK_GRAY.into(),\n            ..default()\n        },\n    ))\n    .with_children(|p| {\n        p.spawn(TextBundle::from_section(\n            \"Quit\",\n            TextStyle {\n                color: Color::WHITE,\n                ..default()\n            },\n        ));\n    });\n```\n\nHandle click events:\n\n```rust\nfn handle_click_events(\n    mut events: EventReader\u003cFocusableClickEvent\u003e,\n    query: Query\u003c\u0026ButtonAction\u003e,\n    mut app_exit_writer: EventWriter\u003cAppExit\u003e,\n) {\n    for event in events.nav_iter().activated_in_query(\u0026query) {\n        match *button_action {\n            ButtonAction::Quit =\u003e app_exit_writer.send(AppExit),\n            _ =\u003e (),\n        };\n    }\n}\n```\n\n**Note:** `events.nav_iter().activated_in_query(\u0026query)` is equivalent to the following:\n\n```rust\nfor event in events.read() {\n    if let Ok(button_action) = query.get(event.0) {\n        todo!();\n    }\n}\n```\n\nHandle cancel events:\n\n```rust\nfn handle_cancel_events(\n    mut events: EventReader\u003cUiNavCancelEvent\u003e,\n    query: Query\u003c(), With\u003cMyMenu\u003e\u003e,\n    mut app_exit_writer: EventWriter\u003cAppExit\u003e,\n) {\n    for _ in events.nav_iter().activated_in_query(\u0026query) {\n        app_exit_writer.send(AppExit);\n    }\n}\n```\n\nConfigure input mapping:\n\n```rust\npub(crate) const DEFAULT_INPUT_MAP: \u0026[InputMapping] = \u0026[\n    // Keyboard navigation keys\n    InputMapping::Key {\n        keycode: KeyCode::Up,\n        action: ActionType::Up,\n    },\n    InputMapping::Key {\n        keycode: KeyCode::Down,\n        action: ActionType::Down,\n    },\n    InputMapping::Key {\n        keycode: KeyCode::Left,\n        action: ActionType::Left,\n    },\n    InputMapping::Key {\n        keycode: KeyCode::Right,\n        action: ActionType::Right,\n    },\n    // Keyboard action/cancel buttons\n    InputMapping::Key {\n        keycode: KeyCode::Return,\n        action: ActionType::Action,\n    },\n    InputMapping::Key {\n        keycode: KeyCode::Escape,\n        action: ActionType::Cancel,\n    },\n    // Gamepad action/cancel buttons\n    InputMapping::GamepadButton {\n        gamepad: None,\n        button: GamepadButtonType::South,\n        action: ActionType::Action,\n    },\n    InputMapping::GamepadButton {\n        gamepad: None,\n        button: GamepadButtonType::East,\n        action: ActionType::Cancel,\n    },\n    // Gamepad direction stick (left)\n    InputMapping::GamepadAxes {\n        gamepad: None,\n        stick: GamepadStick::Left,\n    },\n];\n\napp.insert_resource(UiNavInputManager::from_input_map(\n    DEFAULT_INPUT_MAP,\n    // `stick_tolerance`: Tolerance for gamepad sticks\n    0.1,\n    // `stick_snap_tolerance`: Tolerance for gamepad sticks snapping to a specified direction\n    0.9,\n));\n```\n\nUpdate button colors when the `Focusable` changes:\n\n```rust\nfn button_style(mut query: Query\u003c(\u0026Focusable, \u0026mut BackgroundColor), Changed\u003cFocusable\u003e\u003e) {\n    for (focusable, mut background_color) in query.iter_mut() {\n        *background_color = match focusable.computed_state() {\n            FocusState::Active | FocusState::Focus =\u003e Color::GRAY,\n            FocusState::Press =\u003e Color::BLACK,\n            _ =\u003e Color::DARK_GRAY,\n        }\n        .into();\n    }\n}\n```\n\nPlay sounds when navigating between focusables:\n\n```rust\nfn handle_focus_change_events(mut events: EventReader\u003cUiNavFocusChangedEvent\u003e) {\n    for event in events.read() {\n        // TODO: Spawn appropriate sound effect\n        todo!();\n    }\n}\n```\n\n## Compatible Bevy versions\n\n| `bevy_ui_nav`   | `bevy` |\n|:----------------|:-------|\n| `0.2` - `0.3`   | `0.16` |\n| `0.1`           | `0.13` |\n\n# Credits\n\n- [bevy-ui-navigation](https://github.com/nicopap/ui-navigation) was the original inspiration, and the source for the\n  event reader implementation used in [event_reader.rs](src/event_reader.rs).\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobertdodd%2Fbevy_ui_nav","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Frobertdodd%2Fbevy_ui_nav","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Frobertdodd%2Fbevy_ui_nav/lists"}