From bda0bb7de3d4794662d8405fd4a26a50cec1a531 Mon Sep 17 00:00:00 2001 From: Robert Garrett Date: Tue, 26 Aug 2025 08:57:03 -0500 Subject: [PATCH] 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. --- src/main.rs | 4 ++ src/resources.rs | 18 +++++++ src/widgets/mod.rs | 115 +++++++++++++++++++++++++++++++++++++++------ 3 files changed, 122 insertions(+), 15 deletions(-) diff --git a/src/main.rs b/src/main.rs index 6c72801..eca60be 100644 --- a/src/main.rs +++ b/src/main.rs @@ -30,6 +30,10 @@ fn main() { 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(); } diff --git a/src/resources.rs b/src/resources.rs index f7cfa57..4236d6e 100644 --- a/src/resources.rs +++ b/src/resources.rs @@ -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(), } } } diff --git a/src/widgets/mod.rs b/src/widgets/mod.rs index 9ec6edc..fbdb5ac 100644 --- a/src/widgets/mod.rs +++ b/src/widgets/mod.rs @@ -38,21 +38,24 @@ fn machine_ui_base(header: impl Into) -> impl Bundle { }, 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. - )], - )], + children![ + ( + // TODO: A real node with stuff in it (buttons, maybe?) + Node { + justify_content: JustifyContent::Center, + grid_column: GridPlacement::span(2), + ..default() + }, + BackgroundColor(RED.into()), + Pickable::default(), + children![( + Text::new(header), + TextColor(BLACK.into()), + // TODO: Text shadow, maybe. I couldn't make it look good. + )], + ), + CloseButton::bundle(), + ], ) } @@ -138,3 +141,85 @@ 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; + +impl CloseButton { + /// Spawn a button that will despawn the top-most node when pressed. + fn bundle() -> impl Bundle { + ( + CloseButton, + 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>, + // 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>, + ui_theme: Res, + ) { + // 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>, + mut button_colors: Query<(&mut BackgroundColor, &mut BorderColor), With>, + ui_theme: Res, + ) { + 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>, + mut button_colors: Query<(&mut BackgroundColor, &mut BorderColor), With>, + ui_theme: Res, + ) { + 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>, + mut commands: Commands, + mut button_colors: Query<(&mut BackgroundColor, &mut BorderColor), With>, + ui_theme: Res, + ) { + 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; + } + } +}