Compare commits

9 Commits

Author SHA1 Message Date
4bf3c44b5e Move widgets to a folder module 2025-08-25 18:17:39 -05:00
45e44ef8b1 Begin work on a rotator UI 2025-08-25 18:07:53 -05:00
e362df8682 Plug in the new UiTheme resource
Fixed struct field visibility, initialize the resource, and register it
with the EGUI Debug overlay. The button observers have also been updated
to use the resource.
2025-08-25 13:10:09 -05:00
4d8a178b74 Remove print-debug observer system
This was just so I could see two Observers executing from the same
trigger.
2025-08-25 13:04:40 -05:00
74302afab1 Replace "constants" module with "resources"
These aren't constants and they were never meant to be.

I've also started collecting the UI theme values. For now, just button
colors.
2025-08-25 12:54:29 -05:00
aa0c8b421b Handle machine button UI events with observers
Observers are "just" systems with a Trigger as their first parameter.
I've made 4 systems to handle the start and stop of the press and hover
actions.

Having the button *actually* do something (not just change colors) will
be achieved by attaching another button to the on-press trigger. I'm
expecting that the machine-ui-spawning system will do that, but in
principle it could be done by anything with access to the UI's button
entity ID.
2025-08-25 11:36:26 -05:00
875d157a21 Check-in the progress
Lots of nested components are making my head hurt. I'm not sure how to
solve the button call-back problem, yet.
2025-08-25 08:22:08 -05:00
a0cfc86f59 Center & Span the red rectangle to be a banner
Figured out how to place nodes in the grid. This will eventually become
the banner that says what the machine does.
2025-08-25 08:22:08 -05:00
79f66b2868 Colored squares on a grid layout
I'm still kinda figuring out how to do more complex UIs, so I'll be
making several commits that aren't very interesting to review... such as
this one.
2025-08-25 08:22:08 -05:00
5 changed files with 272 additions and 15 deletions

View File

@@ -1,12 +0,0 @@
//! Program constants & defaults
use bevy::prelude::*;
#[derive(Debug, Reflect, Resource)]
struct ConfigDefaults {}
impl FromWorld for ConfigDefaults {
fn from_world(world: &mut World) -> Self {
todo!()
}
}

View File

@@ -3,7 +3,7 @@ use bevy_inspector_egui::{bevy_egui::EguiPlugin, quick::WorldInspectorPlugin};
mod assets;
mod card;
mod constants;
mod resources;
mod widgets;
fn main() {
@@ -17,6 +17,26 @@ fn main() {
}))
.add_plugins(EguiPlugin::default())
.add_plugins(WorldInspectorPlugin::new())
.add_systems(Startup, assets::load_assets)
.init_resource::<resources::UiTheme>()
.register_type::<resources::UiTheme>()
.add_systems(
Startup,
(
setup,
assets::load_assets,
widgets::spawn_rotator_machine_ui,
),
)
.run();
}
fn setup(mut commands: Commands) {
commands.spawn(Camera2d);
}
/// Generic utility for despawning entities that have a given component.
fn despawn<T: Component>(mut commands: Commands, to_despawn: Query<Entity, With<T>>) {
for entity in to_despawn {
commands.entity(entity).despawn();
}
}

33
src/resources.rs Normal file
View File

@@ -0,0 +1,33 @@
//! Program constants & defaults
use bevy::{color::palettes::css::*, prelude::*};
#[derive(Debug, Reflect, Resource)]
#[reflect(Resource)]
pub struct UiTheme {
// TODO: Panes
// Colors for the "Big Red Buttons" (the main actions of the machines)
// normal
pub brb_bg: Color,
pub brb_border: Color,
// hover
pub brb_hover_bg: Color,
pub brb_hover_border: Color,
// pressed
pub brb_pressed_bg: Color,
pub brb_pressed_border: Color,
}
impl FromWorld for UiTheme {
fn from_world(world: &mut World) -> Self {
Self {
brb_bg: RED.into(),
brb_border: DARK_RED.into(),
brb_hover_bg: PINK.into(),
brb_hover_border: RED.into(),
brb_pressed_bg: GREEN.into(),
brb_pressed_border: DARK_GREEN.into(),
}
}
}

View File

@@ -1 +0,0 @@
//! Catch-all location for UI bits

217
src/widgets/mod.rs Normal file
View File

@@ -0,0 +1,217 @@
//! Catch-all location for UI bits
use bevy::{
color::palettes::{css::*, tailwind::*},
prelude::*,
ui::Val::*,
};
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 {
(
Node {
// Position & size
position_type: PositionType::Relative,
width: Percent(60.0),
height: Percent(60.0),
top: Percent(20.0),
left: Percent(20.0),
// 5x5 grid, padding & gutters, etc
aspect_ratio: Some(1.0),
display: Display::Grid,
padding: UiRect::all(Val::Px(10.0)),
grid_template_columns: vec![
GridTrack::min_content(),
GridTrack::flex(1.0),
GridTrack::min_content(),
],
grid_template_rows: vec![GridTrack::min_content(), GridTrack::flex(1.0)],
row_gap: Val::Px(5.0),
column_gap: Val::Px(5.0),
..default()
},
BackgroundColor(SLATE_100.into()),
BorderRadius::all(Percent(2.0)),
children![(
// TODO: A real node with stuff in it (buttons, maybe?)
Node {
justify_content: JustifyContent::Center,
grid_column: GridPlacement::span(3),
..default()
},
BackgroundColor(RED.into()),
Pickable::default(),
children![(
Text::new(header),
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>) {
let mut builder = commands.spawn((
Button,
Node {
width: Px(60.0),
height: Px(60.0),
aspect_ratio: Some(1.0),
// Why is it "align_items" to center the text vertically,
// but "justify_*content*" to center it horizontally?
align_items: AlignItems::Center,
// align_content: AlignContent::Center,
justify_content: JustifyContent::Center,
border: UiRect::all(Px(2.0)),
..Default::default()
},
BackgroundColor(RED.into()),
BorderColor(DARK_RED.into()),
BorderRadius::MAX,
children![
Text::new(text),
TextColor(WHITE.into()),
TextShadow::default(),
],
));
builder.observe(button_hover_start);
builder.observe(button_hover_stop);
builder.observe(button_press_start);
builder.observe(button_press_stop);
}
/// Re-color the button when a pointer passes over it
fn button_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<Button>>,
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.brb_hover_bg;
border.0 = ui_theme.brb_hover_border;
}
}
// TODO: Consolidate these with the help of a NewType enum and `trigger_map()`
// see: https://github.com/bevyengine/bevy/issues/14649
fn button_hover_stop(
event: Trigger<Pointer<Out>>,
mut button_colors: Query<(&mut BackgroundColor, &mut BorderColor), With<Button>>,
ui_theme: Res<UiTheme>,
) {
if let Ok((mut bg, mut border)) = button_colors.get_mut(event.target()) {
bg.0 = ui_theme.brb_bg;
border.0 = ui_theme.brb_border;
}
}
fn button_press_start(
event: Trigger<Pointer<Pressed>>,
mut button_colors: Query<(&mut BackgroundColor, &mut BorderColor), With<Button>>,
ui_theme: Res<UiTheme>,
) {
if let Ok((mut bg, mut border)) = button_colors.get_mut(event.target()) {
bg.0 = ui_theme.brb_pressed_bg;
border.0 = ui_theme.brb_pressed_border;
}
}
fn button_press_stop(
event: Trigger<Pointer<Released>>,
mut button_colors: Query<(&mut BackgroundColor, &mut BorderColor), With<Button>>,
ui_theme: Res<UiTheme>,
) {
if let Ok((mut bg, mut border)) = button_colors.get_mut(event.target()) {
bg.0 = ui_theme.brb_bg;
border.0 = ui_theme.brb_border;
}
}