@@ 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>>,
) {
@@ 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();
+ }
+ }
+ }
+}