//! This example illustrates how to create a button using the `bevy_ui_widgets` widget set: //! a headless `Button` that fires an `Activate` event when clicked. //! //! The `bevy_ui_widgets` widgets are behavior-only — the `Button` tracks its own pressed state //! and detects clicks, but comes with no styling. We supply the look ourselves. //! //! ## What's next? //! //! - Keyboard & accessibility: add `bevy::input_focus::tab_navigation::TabNavigationPlugin`, wrap //! your UI in a `TabGroup`, and give the button a `TabIndex`. It can then be focused with Tab and //! activated with Enter/Space, firing the same `Activate` event. (`Button` already reports itself //! to accessibility tools via the `AccessibilityNode` it requires.) //! //! - Disabling a button: insert the `bevy::ui::InteractionDisabled` marker to stop it from //! activating, and branch on `Has` in `update_button_appearance` to grey it //! out. See the `standard_widgets` and `standard_widgets_observers` examples for that pattern. //! //! - Activate on press instead of on release: add the `ActivateOnPress` marker (useful for things //! like menu buttons that should fire the instant they're pressed). //! //! - Reacting via observers instead of a polling system: rather than updating appearance every //! frame, you can observe component changes (e.g. `On`). The //! `standard_widgets_observers` example demonstrates this approach. use bevy::{ color::palettes::basic::*, picking::hover::Hovered, prelude::*, ui::Pressed, ui_widgets::{observe, Activate, Button}, }; fn main() { App::new() // `DefaultPlugins` already includes the `bevy_ui_widgets` plugins, so the `Button` // widget's behavior (and its `Activate` event) works out of the box. .add_plugins(DefaultPlugins) .add_systems(Startup, setup) // Update the button's appearance every frame based on its current state. .add_systems(Update, update_button_appearance) .run(); } const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15); const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25); const PRESSED_BUTTON: Color = Color::srgb(0.35, 0.75, 0.35); fn setup(mut commands: Commands, assets: Res) { // ui camera commands.spawn(Camera2d); // A full-screen container that centers the button. commands.spawn(( Node { width: percent(100), height: percent(100), align_items: AlignItems::Center, justify_content: JustifyContent::Center, ..default() }, children![( button(&assets), // React to the button being clicked. `Button` fires an `Activate` event on a // completed click, so we attach an observer for it right here on the button entity. // // Try it: this is where you'd run your own logic (start a game, open a menu, etc.). observe(|_activate: On| { info!("Button clicked!"); }), )], )); } fn button(asset_server: &AssetServer) -> impl Bundle { ( // The headless button widget. It handles pointer/keyboard input and pressed-state // tracking for us; we only provide the look below. Button, // `Hovered` is used by the picking backend to track if the pointer is over the button. Hovered::default(), Node { width: px(150), height: px(65), border: UiRect::all(px(5)), // horizontally center child text justify_content: JustifyContent::Center, // vertically center child text align_items: AlignItems::Center, border_radius: BorderRadius::MAX, ..default() }, BorderColor::all(Color::BLACK), BackgroundColor(NORMAL_BUTTON), children![( Text::new("Button"), TextFont { font: asset_server.load("fonts/FiraSans-Bold.ttf").into(), font_size: FontSize::Px(33.0), ..default() }, TextColor(Color::srgb(0.9, 0.9, 0.9)), TextShadow::default(), )], ) } /// Restyle the button and update its label to reflect its current state. /// /// The `Button` widget maintains a `Pressed` component while the button is held down, and the /// picking backend keeps `Hovered` up to date. We simply read those each frame and pick a look. fn update_button_appearance( mut buttons: Query< ( &Hovered, Has, &mut BackgroundColor, &mut BorderColor, &Children, ), With