4 Commits

Author SHA1 Message Date
0c042f9937 Begin work on a reusable on-hover color component
The two buttons ("close" and the "Big Red" button) have identical hover
code, except for which color theme they select. I was about to start
work on the cutting board, particularly it's cut-line selectors, and
realized that would make a 3rd copy of this code.

This commit adds just the background color handler. It seems to work as
expected on the CloseButton. I'll finish removing the old observers once
I have the BorderColor version in place.
2025-08-31 11:35:42 -05:00
ea9055e46c (autoformat) 2025-08-30 09:24:38 -05:00
be9cb3fe69 Fix: pass the machine ID through, not PLACEHOLDER
Oops. Yay for rushed work.
2025-08-30 09:23:59 -05:00
6fb3539329 Use hook on FuelGauge to show text on first spawn
There is an on-add hook that can be used to maintain invariants. I'll
use it to ensure the label text always gets the current fuel value upon
spawning the widget, regardless of whether or not the caller remembers
to set it up themselves.
2025-08-30 09:21:57 -05:00
5 changed files with 81 additions and 10 deletions

View File

@@ -43,7 +43,7 @@ pub mod consumables {
impl Default for Fuel { impl Default for Fuel {
fn default() -> Self { fn default() -> Self {
Self(5) Self(5)
} }
} }
#[derive(Event)] #[derive(Event)]

View File

@@ -2,9 +2,7 @@ use bevy::{color::palettes::css::*, prelude::*, window::WindowResolution};
use bevy_inspector_egui::{bevy_egui::EguiPlugin, quick::WorldInspectorPlugin}; use bevy_inspector_egui::{bevy_egui::EguiPlugin, quick::WorldInspectorPlugin};
use crate::{ use crate::{
game::{ game::machines::{CuttingMachine, FlippingMachine, RotatingMachine, TransposingMachine},
machines::{CuttingMachine, FlippingMachine, RotatingMachine, TransposingMachine},
},
resources::UiTheme, resources::UiTheme,
widgets::SpawnUi, widgets::SpawnUi,
}; };

View File

@@ -1,9 +1,15 @@
use bevy::{color::palettes::css::*, prelude::*, ui::Val::*}; use bevy::{
color::palettes::css::*,
ecs::{component::HookContext, world::DeferredWorld},
prelude::*,
ui::Val::*,
};
use crate::game::consumables::{Fuel, FuelChanged}; use crate::game::consumables::{Fuel, FuelChanged};
#[derive(Component)] #[derive(Component)]
#[require(Label)] #[require(Label)]
#[component(on_add = FuelGauge::refresh_label)]
pub struct FuelGauge(Entity); pub struct FuelGauge(Entity);
impl FuelGauge { impl FuelGauge {
@@ -41,4 +47,23 @@ impl FuelGauge {
); );
label.0 = format!("Fuel: {}", fuel.0); label.0 = format!("Fuel: {}", fuel.0);
} }
fn refresh_label(mut world: DeferredWorld, ctx: HookContext) {
// Get the machine ID from the FuelGauge component.
let Some(machine_id) = world.get::<FuelGauge>(ctx.entity).map(|fg| fg.0) else {
panic!("Couldn't get a FuelGauge during it's on-add hook run!");
};
// and fuel level
let Some(fuel) = world.get::<Fuel>(machine_id).map(|lvl| lvl.0) else {
// TODO: Maybe don't panic and just emit a warning.
panic!("Couldn't get a Fuel for a machine during a FuelGauge on-add hook.");
};
// Get an excl ref to the Text component so we can redraw its text
let Some(mut label) = world.get_mut::<Text>(ctx.entity) else {
panic!()
};
label.0 = format!("Fuel: {}", fuel);
}
} }

View File

@@ -52,7 +52,7 @@ impl SpawnUi for RotatingMachine {
fn spawn_ui(mut commands: Commands, theme: Res<UiTheme>, machine_id: Entity) { fn spawn_ui(mut commands: Commands, theme: Res<UiTheme>, machine_id: Entity) {
let base_entity = machine_ui_base(&mut commands, "Rotating Machine", &theme); let base_entity = machine_ui_base(&mut commands, "Rotating Machine", &theme);
commands.entity(base_entity).with_children(|commands| { commands.entity(base_entity).with_children(|commands| {
commands.spawn(FuelGauge::bundle(Entity::PLACEHOLDER)); commands.spawn(FuelGauge::bundle(machine_id));
// Center panel (placeholder for input-output rotation) // Center panel (placeholder for input-output rotation)
commands.spawn(( commands.spawn((
@@ -87,7 +87,7 @@ impl SpawnUi for FlippingMachine {
fn spawn_ui(mut commands: Commands, theme: Res<UiTheme>, machine_id: Entity) { fn spawn_ui(mut commands: Commands, theme: Res<UiTheme>, machine_id: Entity) {
let base_entity = machine_ui_base(&mut commands, "Flipping Machine", &theme); let base_entity = machine_ui_base(&mut commands, "Flipping Machine", &theme);
commands.entity(base_entity).with_children(|commands| { commands.entity(base_entity).with_children(|commands| {
commands.spawn(FuelGauge::bundle(Entity::PLACEHOLDER)); commands.spawn(FuelGauge::bundle(machine_id));
// Center panel (placeholder) // Center panel (placeholder)
commands.spawn(( commands.spawn((
@@ -122,7 +122,7 @@ impl SpawnUi for TransposingMachine {
fn spawn_ui(mut commands: Commands, theme: Res<UiTheme>, machine_id: Entity) { fn spawn_ui(mut commands: Commands, theme: Res<UiTheme>, machine_id: Entity) {
let base_entity = machine_ui_base(&mut commands, "Transposing Machine", &theme); let base_entity = machine_ui_base(&mut commands, "Transposing Machine", &theme);
commands.entity(base_entity).with_children(|commands| { commands.entity(base_entity).with_children(|commands| {
commands.spawn(FuelGauge::bundle(Entity::PLACEHOLDER)); commands.spawn(FuelGauge::bundle(machine_id));
// Center panel (placeholder) // Center panel (placeholder)
commands.spawn(( commands.spawn((

View File

@@ -18,6 +18,8 @@ impl Plugin for GameUiPlugin {
app app
.init_resource::<UiTheme>() .init_resource::<UiTheme>()
.register_type::<UiTheme>() .register_type::<UiTheme>()
.add_observer(HoverBackgroundcolor::hover_start)
.add_observer(HoverBackgroundcolor::hover_stop)
.add_observer(CloseButton::hover_start) .add_observer(CloseButton::hover_start)
.add_observer(CloseButton::hover_stop) .add_observer(CloseButton::hover_stop)
.add_observer(CloseButton::press_start) .add_observer(CloseButton::press_start)
@@ -216,6 +218,7 @@ impl CloseButton {
BackgroundColor(GRAY.into()), BackgroundColor(GRAY.into()),
BorderColor(DARK_GRAY.into()), BorderColor(DARK_GRAY.into()),
BorderRadius::all(Px(2.0)), BorderRadius::all(Px(2.0)),
HoverBackgroundcolor::new(GREEN.into()),
children![( children![(
// TODO: Replace with an icon/sprite // TODO: Replace with an icon/sprite
Text::new("X"), Text::new("X"),
@@ -233,7 +236,7 @@ impl CloseButton {
) { ) {
// Get the components for only the Trigger's target entity // Get the components for only the Trigger's target entity
if let Ok((mut bg, mut border)) = button_colors.get_mut(event.target()) { if let Ok((mut bg, mut border)) = button_colors.get_mut(event.target()) {
bg.0 = ui_theme.quiet_hover_bg; // bg.0 = ui_theme.quiet_hover_bg;
border.0 = ui_theme.quiet_hover_border; border.0 = ui_theme.quiet_hover_border;
} }
} }
@@ -244,7 +247,7 @@ impl CloseButton {
ui_theme: Res<UiTheme>, ui_theme: Res<UiTheme>,
) { ) {
if let Ok((mut bg, mut border)) = button_colors.get_mut(event.target()) { if let Ok((mut bg, mut border)) = button_colors.get_mut(event.target()) {
bg.0 = ui_theme.quiet_bg; // bg.0 = ui_theme.quiet_bg;
border.0 = ui_theme.quiet_border; border.0 = ui_theme.quiet_border;
} }
} }
@@ -273,3 +276,48 @@ impl CloseButton {
} }
} }
} }
/// Background color to use for an item being hovered over.
///
/// Adding this to an entity both sets the color and implicitly enables the
/// on-hover behavior. Without it, the system(s) don't find the entity and so
/// no color changing is exhibited.
#[derive(Component)]
struct HoverBackgroundcolor {
pub color: Color,
// hold onto the original so it can be put back
orig_bg: Option<Color>,
}
impl HoverBackgroundcolor {
fn new(color: Color) -> Self {
Self {
color,
orig_bg: None,
}
}
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 colors: Query<(&mut HoverBackgroundcolor, &mut BackgroundColor)>,
) {
// Get the components for only the Trigger's target entity
if let Ok((mut hover, mut bg)) = colors.get_mut(event.target()) {
hover.orig_bg = Some(bg.0); // store the original
bg.0 = hover.color;
}
}
fn hover_stop(
event: Trigger<Pointer<Out>>,
mut colors: Query<(&HoverBackgroundcolor, &mut BackgroundColor)>,
) {
if let Ok((hover, mut bg)) = colors.get_mut(event.target()) {
bg.0 = hover
.orig_bg
.expect("Tried to restore an original color but there was none!");
}
}
}