Compare commits

5 Commits

Author SHA1 Message Date
f89a0d2e66 Record target entity in CloseButton
I was originally planning to search up the entity hierarchy to find the
top-most entity and despawn that. This will become a problem if I ever
want to have panels in panels.

Instead, just record the target entity and despawn it.

This means the current usage doesn't work, so I've removed it from the
base machine UI bundle.
2025-08-26 09:10:34 -05:00
bda0bb7de3 Add most of a "CloseButton" widget
I'm trying out a new scope strategy. The CloseButton exists as a real
struct with the `Component` trait. There's a bundle spawning utility
function and several observer systems. The observer systems have been
added directly to the app, which I *believe* means they won't be
duplicated the way the Big Red Button's observers will.
2025-08-26 09:02:08 -05:00
f2f1674451 Rename machine button spawner, "big red button"
It's the big red button. Don't press it.

Or do.
2025-08-25 19:45:35 -05:00
d235d6af5e (autoformat) 2025-08-25 19:18:06 -05:00
e2b2d5b5d9 Move the machine UI spawners to a new submodule 2025-08-25 19:17:54 -05:00
6 changed files with 223 additions and 101 deletions

View File

@@ -1,5 +1,7 @@
//! TODO: module doc :v
use bevy::ecs::component::Component;
/// Value for the "sub tiles" inside a room tile
#[derive(Clone, Copy, Debug, Default, PartialEq)]
pub enum Cell {
@@ -47,7 +49,7 @@ pub enum RotationDir {
/// An invidiual room, or "card" in the player's hand. The room may
/// *or may not* be valid, yet.
#[derive(Clone, Copy, Debug, Default, PartialEq)]
#[derive(Clone, Copy, Component, Debug, Default, PartialEq)]
pub struct Card {
cells: [Cell; 9],
nw: bool,

View File

@@ -1,4 +1,3 @@
use bevy::prelude::*;
/// Data component for info about the player's current set of cards.
@@ -7,7 +6,7 @@ use bevy::prelude::*;
///
/// [`Self::low_water_mark`] is the threshold for drawing new cards on-room-enter.
#[derive(Component)]
pub struct PlayerHand{
pub struct PlayerHand {
cards: Vec<Entity>,
capacity: u8,
low_water_mark: u8,

View File

@@ -1,6 +1,8 @@
use bevy::{prelude::*, window::WindowResolution};
use bevy_inspector_egui::{bevy_egui::EguiPlugin, quick::WorldInspectorPlugin};
use crate::game::machines::RotatingMachine;
mod assets;
mod card;
mod game;
@@ -25,9 +27,13 @@ fn main() {
(
setup,
assets::load_assets,
widgets::spawn_rotator_machine_ui,
RotatingMachine::spawn_rotator_machine_ui,
),
)
.add_observer(widgets::CloseButton::hover_start)
.add_observer(widgets::CloseButton::hover_stop)
.add_observer(widgets::CloseButton::press_start)
.add_observer(widgets::CloseButton::press_stop)
.run();
}

View File

@@ -17,6 +17,17 @@ pub struct UiTheme {
// pressed
pub brb_pressed_bg: Color,
pub brb_pressed_border: Color,
// Colors for low-priority buttons
// normal
pub quiet_bg: Color,
pub quiet_border: Color,
// hover
pub quiet_hover_bg: Color,
pub quiet_hover_border: Color,
// pressed
pub quiet_pressed_bg: Color,
pub quiet_pressed_border: Color,
}
impl FromWorld for UiTheme {
@@ -28,6 +39,13 @@ impl FromWorld for UiTheme {
brb_hover_border: RED.into(),
brb_pressed_bg: GREEN.into(),
brb_pressed_border: DARK_GREEN.into(),
quiet_bg: GRAY.into(),
quiet_border: DARK_GRAY.into(),
quiet_hover_bg: DARK_GRAY.into(),
quiet_hover_border: LIGHT_GRAY.into(),
quiet_pressed_bg: LIGHT_GRAY.into(),
quiet_pressed_border: GRAY.into(),
}
}
}

89
src/widgets/machines.rs Normal file
View File

@@ -0,0 +1,89 @@
use bevy::{color::palettes::css::*, prelude::*, ui::Val::*};
use crate::{
game::machines::*,
widgets::{machine_ui_base, spawn_big_red_button},
};
impl CuttingMachine {
pub fn spawn_cutter_machine_ui(mut commands: Commands) {
commands
.spawn((machine_ui_base("Cutting Machine"),))
.with_children(|commands| {
// Left panel. For fuel or machine stats or whatever.
commands.spawn((
Node {
padding: UiRect::all(Px(10.0)),
..default()
},
BackgroundColor(GREEN.into()),
Pickable::default(),
children![(Text::new("Uses: <n>"), TextColor(BLACK.into()),)],
));
// Center panel (placeholder for the Card view)
commands.spawn((
Node::default(),
BackgroundColor(BLUE.into()),
Pickable::default(),
children![(
Text::new("Card cut view placeholder"),
TextColor(MAGENTA.into()),
TextShadow::default(),
),],
));
// Right panel for the "CUT" button
commands
.spawn((
Node {
align_items: AlignItems::End,
..Default::default()
},
BackgroundColor(DARK_GRAY.into()),
Pickable::default(),
))
.with_children(|cmds| spawn_big_red_button(cmds, "CUT"));
});
}
}
impl RotatingMachine {
pub fn spawn_rotator_machine_ui(mut commands: Commands) {
commands
.spawn((machine_ui_base("Rotating Machine"),))
.with_children(|commands| {
commands.spawn((
Node {
padding: UiRect::all(Px(10.0)),
..Default::default()
},
BackgroundColor(GREEN.into()),
Pickable::default(),
children![(Text::new("Uses: <n>"), TextColor(BLACK.into()))],
));
// Center panel (placeholder for input-output rotation)
commands.spawn((
Node::default(),
BackgroundColor(BLUE.into()),
Pickable::default(),
children![
Text::new("Card rotation side-by-side placeholder"),
TextColor(MAGENTA.into()),
],
));
// Right panel for the rotation controls
commands
.spawn((
Node {
align_items: AlignItems::End,
..Default::default()
},
BackgroundColor(DARK_GRAY.into()),
Pickable::default(),
))
.with_children(|cmds| spawn_big_red_button(cmds, "TURN"));
});
}
}

View File

@@ -1,5 +1,7 @@
//! Catch-all location for UI bits
pub mod machines;
use bevy::{
color::palettes::{css::*, tailwind::*},
prelude::*,
@@ -8,85 +10,6 @@ use bevy::{
use crate::resources::UiTheme;
pub fn spawn_cutter_machine_ui(mut commands: Commands) {
commands
.spawn((machine_ui_base("Cutting Machine"),))
.with_children(|commands| {
// Left panel. For fuel or machine stats or whatever.
commands.spawn((
Node {
padding: UiRect::all(Px(10.0)),
..default()
},
BackgroundColor(GREEN.into()),
Pickable::default(),
children![(Text::new("Uses: <n>"), TextColor(BLACK.into()),)],
));
// Center panel (placeholder for the Card view)
commands.spawn((
Node::default(),
BackgroundColor(BLUE.into()),
Pickable::default(),
children![(
Text::new("Card cut view placeholder"),
TextColor(MAGENTA.into()),
TextShadow::default(),
),],
));
// Right panel for the "CUT" button
commands
.spawn((
Node {
align_items: AlignItems::End,
..Default::default()
},
BackgroundColor(DARK_GRAY.into()),
Pickable::default(),
))
.with_children(|cmds| spawn_machine_button(cmds, "CUT"));
});
}
pub fn spawn_rotator_machine_ui(mut commands: Commands) {
commands
.spawn((machine_ui_base("Rotating Machine"),))
.with_children(|commands| {
commands.spawn((
Node {
padding: UiRect::all(Px(10.0)),
..Default::default()
},
BackgroundColor(GREEN.into()),
Pickable::default(),
children![(Text::new("Uses: <n>"), TextColor(BLACK.into()))],
));
// Center panel (placeholder for input-output rotation)
commands.spawn((
Node::default(),
BackgroundColor(BLUE.into()),
Pickable::default(),
children![
Text::new("Card rotation side-by-side placeholder"),
TextColor(MAGENTA.into()),
]
));
// Right panel for the rotation controls
commands
.spawn((
Node {
align_items: AlignItems::End,
..Default::default()
},
BackgroundColor(DARK_GRAY.into()),
Pickable::default(),
))
.with_children(|cmds| spawn_machine_button(cmds, "TURN"));
});
}
/// The base panel for the machines that manipulate the room cards.
fn machine_ui_base(header: impl Into<String>) -> impl Bundle {
(
@@ -115,11 +38,12 @@ fn machine_ui_base(header: impl Into<String>) -> impl Bundle {
},
BackgroundColor(SLATE_100.into()),
BorderRadius::all(Percent(2.0)),
children![(
children![
(
// TODO: A real node with stuff in it (buttons, maybe?)
Node {
justify_content: JustifyContent::Center,
grid_column: GridPlacement::span(3),
grid_column: GridPlacement::span(2),
..default()
},
BackgroundColor(RED.into()),
@@ -129,12 +53,13 @@ fn machine_ui_base(header: impl Into<String>) -> impl Bundle {
TextColor(BLACK.into()),
// TODO: Text shadow, maybe. I couldn't make it look good.
)],
)],
),
],
)
}
// TODO: Hook up action handling (callback? Observer? Some other weird component?)
fn spawn_machine_button(commands: &mut ChildSpawnerCommands, text: impl Into<String>) {
fn spawn_big_red_button(commands: &mut ChildSpawnerCommands, text: impl Into<String>) {
let mut builder = commands.spawn((
Button,
Node {
@@ -215,3 +140,86 @@ fn button_press_stop(
border.0 = ui_theme.brb_border;
}
}
/// Button marker for closing (despawning) an in-game menu entity.
#[derive(Component)]
#[require(Button)]
pub struct CloseButton(Entity);
impl CloseButton {
/// Spawn a button that will despawn the top-most node when pressed.
fn bundle(target: Entity) -> impl Bundle {
(
CloseButton(target),
Node {
width: Px(20.0),
height: Px(20.0),
aspect_ratio: Some(1.0),
align_items: AlignItems::Center,
justify_content: JustifyContent::Center,
border: UiRect::all(Px(1.0)),
..Default::default()
},
// TODO: Get colors *somehow.*
// - Turn this function into a Bevy System, require `Res<...>`
// - Just pass in the colors, let the caller figure out sourcing them.
BackgroundColor(GRAY.into()),
BorderColor(DARK_GRAY.into()),
BorderRadius::all(Px(2.0)),
children![(
// TODO: Replace with an icon/sprite
Text::new("X"),
TextColor(BLACK.into()),
)],
)
}
pub fn hover_start(
event: Trigger<Pointer<Over>>,
// Get button background and border colors so we can change them.
// Filter for *changed* interactions, and only entities with a [`Button`]
mut button_colors: Query<(&mut BackgroundColor, &mut BorderColor), With<CloseButton>>,
ui_theme: Res<UiTheme>,
) {
// Get the components for only the Trigger's target entity
if let Ok((mut bg, mut border)) = button_colors.get_mut(event.target()) {
bg.0 = ui_theme.quiet_hover_bg;
border.0 = ui_theme.quiet_hover_border;
}
}
pub fn hover_stop(
event: Trigger<Pointer<Out>>,
mut button_colors: Query<(&mut BackgroundColor, &mut BorderColor), With<CloseButton>>,
ui_theme: Res<UiTheme>,
) {
if let Ok((mut bg, mut border)) = button_colors.get_mut(event.target()) {
bg.0 = ui_theme.quiet_bg;
border.0 = ui_theme.quiet_border;
}
}
pub fn press_start(
event: Trigger<Pointer<Pressed>>,
mut button_colors: Query<(&mut BackgroundColor, &mut BorderColor), With<CloseButton>>,
ui_theme: Res<UiTheme>,
) {
if let Ok((mut bg, mut border)) = button_colors.get_mut(event.target()) {
bg.0 = ui_theme.quiet_pressed_bg;
border.0 = ui_theme.quiet_pressed_border;
}
}
pub fn press_stop(
event: Trigger<Pointer<Released>>,
mut commands: Commands,
mut button_colors: Query<(&mut BackgroundColor, &mut BorderColor, &CloseButton)>,
ui_theme: Res<UiTheme>,
) {
if let Ok((mut bg, mut border, CloseButton(target))) = button_colors.get_mut(event.target()) {
bg.0 = ui_theme.quiet_bg;
border.0 = ui_theme.quiet_border;
commands.entity(*target).despawn();
}
}
}