/*
* 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::*};
use bevy_dice::DieVariant;
const THROW_COLOR: Srgba = LIME_400;
const DESPAWN_COLOR: Srgba = RED_400;
const VARIANT_COLOR: Srgba = AMBER_400;
pub struct UIPlugin;
impl Plugin for UIPlugin {
fn build(&self, app: &mut App) {
app.add_systems(Startup, setup).add_systems(
Update,
(
button_interaction,
variant_dropdown_interaction,
variant_button_interaction,
),
);
app.add_event::<ToggleVariantButtons>()
.add_event::<VariantSelected>()
.add_event::<ThrowPressed>()
.add_event::<DespawnPressed>();
app.add_observer(toggle_variant_buttons)
.add_observer(set_selected_variant_text);
}
}
#[derive(Component)]
enum ButtonFunction {
Throw,
Despawn,
}
#[derive(Event)]
pub struct ThrowPressed;
#[derive(Event)]
pub struct DespawnPressed;
fn button(function: ButtonFunction, text: impl Into<String>, color: Srgba) -> impl Bundle {
(
function,
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(color)),
BorderColor(Color::from(color.darker(0.3))),
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(text),
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();
}
}
}
}
#[derive(Component)]
struct VariantDropdown;
#[derive(Component)]
struct SelectedVariantText;
#[derive(Event)]
struct ToggleVariantButtons;
#[derive(Component)]
struct VariantButton(DieVariant);
#[derive(Event)]
pub struct VariantSelected(pub DieVariant);
fn variant_button(variant: VariantButton, text: impl Into<String>) -> impl Bundle {
(
variant,
Button,
Node {
width: Val::Px(120.0),
height: Val::Px(40.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(VARIANT_COLOR)),
BorderColor(Color::from(VARIANT_COLOR.darker(0.3))),
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,
),
Visibility::Hidden,
children![(
Text::new(text),
TextFont {
font_size: 18.0,
..Default::default()
},
TextColor(Color::BLACK),
),],
)
}
fn variant_dropdown_interaction(
mut commands: Commands,
q_interaction: Query<
(&Interaction, &mut BackgroundColor),
(Changed<Interaction>, With<VariantDropdown>),
>,
) {
for (interaction, mut background) in q_interaction {
match *interaction {
Interaction::Pressed => {
commands.trigger(ToggleVariantButtons);
*background = VARIANT_COLOR.darker(0.3).into();
}
Interaction::Hovered => {
*background = VARIANT_COLOR.darker(0.1).into();
}
Interaction::None => {
*background = VARIANT_COLOR.into();
}
}
}
}
fn toggle_variant_buttons(
_trigger: Trigger<ToggleVariantButtons>,
mut q_variant_button_visibilities: Query<&mut Visibility, With<VariantButton>>,
) {
for mut visibility in &mut q_variant_button_visibilities {
visibility.toggle_visible_hidden();
}
}
fn variant_button_interaction(
mut commands: Commands,
q_interaction: Query<
(&Interaction, &mut BackgroundColor, &VariantButton),
(Changed<Interaction>, With<Button>),
>,
) {
for (interaction, mut background, variant_button) in q_interaction {
match *interaction {
Interaction::Pressed => {
commands.trigger(ToggleVariantButtons);
commands.trigger(VariantSelected(variant_button.0));
*background = VARIANT_COLOR.darker(0.3).into();
}
Interaction::Hovered => {
*background = VARIANT_COLOR.darker(0.1).into();
}
Interaction::None => {
*background = VARIANT_COLOR.into();
}
}
}
}
fn set_selected_variant_text(
trigger: Trigger<VariantSelected>,
mut q_text: Query<&mut Text, With<SelectedVariantText>>,
) {
if let Ok(mut text) = q_text.single_mut() {
**text = format!("{:?}", trigger.0);
}
}
fn setup(mut commands: Commands) {
let buttons = commands
.spawn((
Node {
width: Val::Percent(100.0),
flex_direction: FlexDirection::Row,
align_items: AlignItems::FlexEnd,
justify_content: JustifyContent::SpaceEvenly,
overflow: Overflow::scroll_y(),
..Default::default()
},
children![
button(ButtonFunction::Throw, "Throw", THROW_COLOR),
button(ButtonFunction::Despawn, "Despawn Dice", DESPAWN_COLOR),
],
))
.id();
let dropdown = commands
.spawn((
Node {
width: Val::Percent(100.0),
flex_direction: FlexDirection::Column,
overflow: Overflow::scroll_y(),
..Default::default()
},
children![(
VariantDropdown,
Button,
Node {
width: Val::Px(120.0),
height: Val::Px(40.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(8.0)),
..Default::default()
},
BackgroundColor(Color::from(VARIANT_COLOR)),
BorderColor(Color::from(VARIANT_COLOR.darker(0.3))),
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![(
SelectedVariantText,
// D6 is the default.
Text::new("D6"),
TextFont {
font_size: 18.0,
..Default::default()
},
TextColor(Color::BLACK),
),],
)],
))
.id();
for variant in DieVariant::VALUES {
commands.entity(dropdown).with_child(variant_button(
VariantButton(variant),
format!("{:?}", variant),
));
}
let layout = commands
.spawn((Node {
width: Val::Percent(100.0),
height: Val::Percent(100.0),
flex_direction: FlexDirection::Column,
align_items: AlignItems::FlexEnd,
justify_content: JustifyContent::SpaceBetween,
padding: UiRect::all(Val::Px(8.0)),
..Default::default()
},))
.id();
commands.entity(layout).add_children(&[dropdown, buttons]);
}