DEVELOPMENT ENVIRONMENT

~liljamo/bevy_dice

a6e5cb1da77417e6c80c4ecfed5ebb25221a4073 — Jonni Liljamo 24 days ago e7fff70
feat(demo): add bevy UI for throw and despawn
2 files changed, 170 insertions(+), 21 deletions(-)

M crates/bevy_dice_demo/src/main.rs
A crates/bevy_dice_demo/src/ui.rs
M crates/bevy_dice_demo/src/main.rs => crates/bevy_dice_demo/src/main.rs +24 -21
@@ 4,6 4,8 @@
 * This file is licensed under MIT, see LICENSE for more information.
 */

#![allow(clippy::type_complexity)]

use std::collections::HashMap;

use avian3d::prelude::*;


@@ 17,6 19,7 @@ use bevy_inspector_egui::quick::WorldInspectorPlugin;
extern crate console_error_panic_hook;

mod toast;
mod ui;

fn main() {
    #[cfg(feature = "wasm")]


@@ 58,7 61,8 @@ fn main() {
        ..Default::default()
    });

    app.add_plugins(DicePlugin).add_plugins(toast::ToastPlugin);
    app.add_plugins(DicePlugin)
        .add_plugins((toast::ToastPlugin, ui::UIPlugin));

    app.init_resource::<ExampleAssets>();
    app.init_state::<ExampleState>()


@@ 71,7 75,9 @@ fn main() {

    app.add_systems(Update, rotate_light);

    app.add_observer(despawn_dice).add_observer(show_die_result);
    app.add_observer(throw_die)
        .add_observer(despawn_dice)
        .add_observer(show_die_result);

    app.run();
}


@@ 175,7 181,7 @@ impl Default for UIState {
    }
}

fn ui(mut commands: Commands, mut contexts: EguiContexts, mut r_ui_state: ResMut<UIState>) {
fn ui(mut contexts: EguiContexts, mut r_ui_state: ResMut<UIState>) {
    egui::Window::new("Demo").show(contexts.ctx_mut(), |ui| {
        egui::ComboBox::from_label("")
            .selected_text(format!("{:?}", r_ui_state.selected_variant))


@@ 188,29 194,26 @@ fn ui(mut commands: Commands, mut contexts: EguiContexts, mut r_ui_state: ResMut
                    );
                }
            });

        if ui.button("Throw").clicked() {
            commands.trigger(ThrowDie {
                id: r_ui_state.die_id,
                variant: r_ui_state.selected_variant,
                angular_velocity: None,
                position: Some(Vec3::new(0.0, 4.0, 0.0)),
                rotation: None,
            });
            r_ui_state.die_id += 1;
        }

        if ui.button("Despawn Dice").clicked() {
            commands.trigger(DespawnDice);
        }
    });
}

#[derive(Event)]
struct DespawnDice;
fn throw_die(
    _trigger: Trigger<ui::ThrowPressed>,
    mut commands: Commands,
    mut r_ui_state: ResMut<UIState>,
) {
    commands.trigger(ThrowDie {
        id: r_ui_state.die_id,
        variant: r_ui_state.selected_variant,
        angular_velocity: None,
        position: Some(Vec3::new(0.0, 4.0, 0.0)),
        rotation: None,
    });
    r_ui_state.die_id += 1;
}

fn despawn_dice(
    _trigger: Trigger<DespawnDice>,
    _trigger: Trigger<ui::DespawnPressed>,
    mut commands: Commands,
    q_dice: Query<Entity, With<Die>>,
) {

A crates/bevy_dice_demo/src/ui.rs => crates/bevy_dice_demo/src/ui.rs +146 -0
@@ 0,0 1,146 @@
/*
 * Copyright (C) 2025 Jonni Liljamo <jonni@liljamo.com>
 *
 * This file is licensed under MIT, see LICENSE for more information.
 */

use bevy::{color::palettes::tailwind::*, prelude::*};

const THROW_COLOR: Srgba = LIME_400;
const DESPAWN_COLOR: Srgba = RED_400;

pub struct UIPlugin;

impl Plugin for UIPlugin {
    fn build(&self, app: &mut App) {
        app.add_systems(Startup, setup)
            .add_systems(Update, button_interaction);

        app.add_event::<ThrowPressed>()
            .add_event::<DespawnPressed>();
    }
}

#[derive(Component)]
enum ButtonFunction {
    Throw,
    Despawn,
}

#[derive(Event)]
pub struct ThrowPressed;

#[derive(Event)]
pub struct DespawnPressed;

fn setup(mut commands: Commands) {
    commands.spawn((
        Node {
            width: Val::Percent(100.0),
            height: Val::Percent(100.0),
            flex_direction: FlexDirection::Row,
            align_items: AlignItems::FlexEnd,
            justify_content: JustifyContent::SpaceEvenly,
            padding: UiRect::all(Val::Px(8.0)),
            overflow: Overflow::scroll_y(),
            ..Default::default()
        },
        children![
            (
                ButtonFunction::Throw,
                Button,
                Node {
                    width: Val::Px(180.0),
                    height: Val::Px(60.0),
                    border: UiRect::all(Val::Px(2.0)),
                    flex_direction: FlexDirection::Column,
                    justify_content: JustifyContent::Center,
                    align_items: AlignItems::Center,
                    margin: UiRect::bottom(Val::Px(4.0)),
                    ..Default::default()
                },
                BackgroundColor(Color::from(THROW_COLOR)),
                BorderColor(Color::from(LIME_700)),
                BorderRadius::all(Val::Px(10.0)),
                BoxShadow::new(
                    Color::BLACK.with_alpha(0.8),
                    Val::Px(4.0),
                    Val::Px(4.0),
                    Val::DEFAULT,
                    Val::DEFAULT,
                ),
                children![(
                    Text::new("Throw"),
                    TextFont {
                        font_size: 18.0,
                        ..Default::default()
                    },
                    TextColor(Color::BLACK),
                ),],
            ),
            (
                ButtonFunction::Despawn,
                Button,
                Node {
                    width: Val::Px(180.0),
                    height: Val::Px(60.0),
                    border: UiRect::all(Val::Px(2.0)),
                    flex_direction: FlexDirection::Column,
                    justify_content: JustifyContent::Center,
                    align_items: AlignItems::Center,
                    margin: UiRect::bottom(Val::Px(4.0)),
                    ..Default::default()
                },
                BackgroundColor(Color::from(DESPAWN_COLOR)),
                BorderColor(Color::from(RED_700)),
                BorderRadius::all(Val::Px(10.0)),
                BoxShadow::new(
                    Color::BLACK.with_alpha(0.8),
                    Val::Px(4.0),
                    Val::Px(4.0),
                    Val::DEFAULT,
                    Val::DEFAULT,
                ),
                children![(
                    Text::new("Despawn Dice"),
                    TextFont {
                        font_size: 18.0,
                        ..Default::default()
                    },
                    TextColor(Color::BLACK),
                ),],
            ),
        ],
    ));
}

fn button_interaction(
    mut commands: Commands,
    q_interaction: Query<
        (&Interaction, &mut BackgroundColor, &ButtonFunction),
        (Changed<Interaction>, With<Button>),
    >,
) {
    for (interaction, mut background, function) in q_interaction {
        let color = match function {
            ButtonFunction::Throw => THROW_COLOR,
            ButtonFunction::Despawn => DESPAWN_COLOR,
        };

        match *interaction {
            Interaction::Pressed => {
                match function {
                    ButtonFunction::Throw => commands.trigger(ThrowPressed),
                    ButtonFunction::Despawn => commands.trigger(DespawnPressed),
                };
                *background = color.darker(0.3).into();
            }
            Interaction::Hovered => {
                *background = color.darker(0.1).into();
            }
            Interaction::None => {
                *background = color.into();
            }
        }
    }
}