refactor ui logic and impl

This commit is contained in:
2026-10-21 00:21:01 -05:00
parent 25557d85ce
commit 66fb0b4e23
5 changed files with 120 additions and 110 deletions
+5 -108
View File
@@ -1,6 +1,7 @@
use crate::AppState;
use crate::focus_navigation::{NavigationInputState, NavigationSystems};
use crate::save::{ActiveGameSave, create_game_save, delete_game_save, get_saves};
use crate::{AppState, WINDOW_TARGET_HEIGHT, WINDOW_TARGET_WIDTH};
use crate::ui_logic::{InteractionColors, Selected, scale_ui, style_ui_elements};
use bevy::color::palettes::tailwind::SLATE_900;
use bevy::input::gamepad::GamepadButton;
use bevy::input_focus::{
@@ -9,7 +10,6 @@ use bevy::input_focus::{
};
use bevy::picking::hover::Hovered;
use bevy::text::{EditableText, EditableTextFilter, TextCursorStyle};
use bevy::ui::Pressed;
use bevy::ui::auto_directional_navigation::AutoDirectionalNavigation;
use bevy::ui_widgets::{Button, TextInput};
use bevy::{prelude::*, ui_widgets::Activate};
@@ -40,55 +40,6 @@ const N_LAYERS: usize = 5;
#[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Debug)]
struct GameNameTextField;
/// A UI object selected by activation, distinct from keyboard/pointer focus.
#[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Debug)]
struct Selected;
/// Defines colors that a UI object should turn when interactions occur.
#[derive(Component, Clone, PartialEq, Default, Debug)]
pub struct InteractionColors {
pub default: Color,
pub hovered: Color,
pub pressed: Color,
pub selected: Color,
pub selected_hovered: Color,
pub selected_pressed: Color,
pub default_outline: Color,
pub focused_outline: Color,
}
impl InteractionColors {
pub const DEFAULT: InteractionColors = InteractionColors {
default: Color::srgb(0.25, 0.63, 0.84),
hovered: Color::srgb(0.31, 0.77, 0.93),
pressed: Color::srgb(0.16, 0.55, 0.89),
selected: Color::srgb(0.93, 0.66, 0.06),
selected_hovered: Color::srgb(1., 0.74, 0.16),
selected_pressed: Color::srgb(0.86, 0.56, 0.),
default_outline: Color::BLACK,
focused_outline: Color::WHITE,
};
pub const GOOD: InteractionColors = InteractionColors {
default: Color::srgb(0.25, 0.75, 0.25),
hovered: Color::srgb(0.35, 0.85, 0.35),
pressed: Color::srgb(0.15, 0.65, 0.15),
selected: Color::srgb(0.35, 0.8, 0.35),
selected_hovered: Color::srgb(0.45, 0.9, 0.45),
selected_pressed: Color::srgb(0.25, 0.7, 0.25),
default_outline: Color::BLACK,
focused_outline: Color::WHITE,
};
pub const DANGER: InteractionColors = InteractionColors {
default: Color::srgb(0.75, 0.25, 0.25),
hovered: Color::srgb(0.85, 0.35, 0.35),
pressed: Color::srgb(0.65, 0.15, 0.15),
selected: Color::srgb(0.8, 0.3, 0.3),
selected_hovered: Color::srgb(0.9, 0.4, 0.4),
selected_pressed: Color::srgb(0.7, 0.2, 0.2),
default_outline: Color::BLACK,
focused_outline: Color::WHITE,
};
}
/// The root of the menu
#[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Debug)]
struct MenuRoot;
@@ -551,6 +502,9 @@ fn button_activate_observer(
// push next layer
menu.push(next_layer.0.clone());
// TODO: This custom "selected" logic, where there is one selected button
// on the list of options is a lot like a radio button. Probably should replace
// this custom impl with a modified radio button impl
for (entity, selected) in q_selected.iter_many(layer_entries).matched() {
if entity == event.entity {
if !selected {
@@ -563,63 +517,6 @@ fn button_activate_observer(
}
}
/// Styles the UI elements according to their [`InteractionColors`].
fn style_ui_elements(
input_focus: Res<InputFocus>,
mut q_elements: Query<
(
Entity,
Option<&Hovered>,
Has<Pressed>,
Option<&mut BackgroundColor>,
Option<&mut BorderColor>,
&InteractionColors,
Has<Selected>,
),
Or<(With<BackgroundColor>, With<BorderColor>)>,
>,
) {
let focused_entity = input_focus.get();
for (entity, hovered, pressed, bg, border, colors, selected) in &mut q_elements {
let is_focused = focused_entity == Some(entity);
// if the entity is focused, its similar to it being hovered.
let hovered = hovered.is_some_and(|h| h.get()) || is_focused;
let new_color = match (selected, pressed, hovered) {
(true, true, _) => colors.selected_pressed,
(true, false, true) => colors.selected_hovered,
(true, false, false) => colors.selected,
(false, true, _) => colors.pressed,
(false, false, true) => colors.hovered,
(false, false, false) => colors.default,
};
if let Some(mut border) = border {
if is_focused {
if border.top != colors.focused_outline {
border.set_all(colors.focused_outline);
}
} else {
if border.top != colors.default_outline {
border.set_all(colors.default_outline);
}
}
}
if let Some(mut bg) = bg {
if bg.0 != new_color {
bg.0 = new_color;
}
}
}
}
fn scale_ui(mut ui_scale: ResMut<UiScale>, window: Single<&Window>) {
let size = window.size();
let scale_factor = f32::min(size.x / WINDOW_TARGET_WIDTH, size.y / WINDOW_TARGET_HEIGHT);
ui_scale.0 = scale_factor;
}
// TODO: generify the inputing of the nav inputs that are in here
fn focus_rightmost_layer_on_navigation(
keyboard: Res<ButtonInput<KeyCode>>,