diff --git a/src/focus_navigation.rs b/src/focus_navigation.rs index e1638f8..fa5990a 100644 --- a/src/focus_navigation.rs +++ b/src/focus_navigation.rs @@ -24,6 +24,9 @@ pub(crate) enum NavigationSystems { #[derive(Resource, Default)] pub(crate) struct NavigationInputState { /// Set by the initializer and consumed by the directional-navigation system in the same frame. + /// + /// This is so that the action that creates a focus can be ignored and used only to set + /// the focus to something. pub(crate) focus_initialized: bool, } diff --git a/src/lobby.rs b/src/lobby.rs index a073fe0..41996af 100644 --- a/src/lobby.rs +++ b/src/lobby.rs @@ -12,7 +12,7 @@ use bevy::{ }; use crate::AppState; -use crate::main_menu::InteractionColors; +use crate::ui_logic::InteractionColors; const PANEL_BACKGROUND: Color = Color::srgb(0.4, 0.4, 0.4); const TEXT_COLOR: Color = Color::srgb(0.9, 0.9, 0.9); @@ -41,7 +41,7 @@ pub struct LobbyUiState { impl Default for LobbyUiState { fn default() -> Self { Self { - privacy: LobbyPrivacy::Public, + privacy: LobbyPrivacy::InviteOnly, port: 5599, password: String::new(), session_active: false, diff --git a/src/main.rs b/src/main.rs index ee85495..17e5ea2 100644 --- a/src/main.rs +++ b/src/main.rs @@ -10,6 +10,7 @@ mod lobby; mod main_menu; mod networking; mod save; +mod ui_logic; // TODO: pull from cargo.toml or something? pub const CURRENT_GAME_VERSION: &str = "v0.0.1"; diff --git a/src/main_menu.rs b/src/main_menu.rs index bc90626..5df4b0a 100644 --- a/src/main_menu.rs +++ b/src/main_menu.rs @@ -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, - mut q_elements: Query< - ( - Entity, - Option<&Hovered>, - Has, - Option<&mut BackgroundColor>, - Option<&mut BorderColor>, - &InteractionColors, - Has, - ), - Or<(With, With)>, - >, -) { - 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, 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>, diff --git a/src/ui_logic.rs b/src/ui_logic.rs new file mode 100644 index 0000000..bf96d85 --- /dev/null +++ b/src/ui_logic.rs @@ -0,0 +1,109 @@ +use crate::{WINDOW_TARGET_HEIGHT, WINDOW_TARGET_WIDTH}; +use bevy::{input_focus::InputFocus, picking::hover::Hovered, prelude::*, ui::Pressed}; + +/// A UI object selected by activation, distinct from keyboard/pointer focus. +#[derive(Component, Copy, Clone, PartialEq, Eq, PartialOrd, Ord, Default, Debug)] +pub 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, + }; +} + +/// Styles the UI elements according to their [`InteractionColors`]. +pub fn style_ui_elements( + input_focus: Res, + mut q_elements: Query< + ( + Entity, + Option<&Hovered>, + Has, + Option<&mut BackgroundColor>, + Option<&mut BorderColor>, + &InteractionColors, + Has, + ), + Or<(With, With)>, + >, +) { + 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; + } + } + } +} + +pub fn scale_ui(mut ui_scale: ResMut, 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; +}