diff --git a/Cargo.lock b/Cargo.lock index a3d251d..9644b97 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -3129,7 +3129,7 @@ dependencies = [ [[package]] name = "maze-ascension" -version = "1.0.2" +version = "1.0.3" dependencies = [ "anyhow", "bevy", diff --git a/Cargo.toml b/Cargo.toml index 3ef7be1..9d09bdc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "maze-ascension" authors = ["Kristofers Solo "] -version = "1.0.2" +version = "1.0.3" edition = "2021" [dependencies] diff --git a/justfile b/justfile index e75c474..a5ebc42 100644 --- a/justfile +++ b/justfile @@ -12,7 +12,7 @@ native-release: # Run web dev web-dev: - RUST_BACKTRACE=full trunk serve + RUSTC_WRAPPER=sccache RUST_BACKTRACE=full trunk serve # Run web release web-release: diff --git a/src/screens/loading.rs b/src/screens/loading.rs index f96d866..9c742be 100644 --- a/src/screens/loading.rs +++ b/src/screens/loading.rs @@ -7,7 +7,7 @@ use crate::{ hint::assets::HintAssets, player::assets::PlayerAssets, screens::Screen, - theme::{interaction::InteractionAssets, prelude::*}, + theme::{assets::InteractionAssets, prelude::*}, }; pub fn plugin(app: &mut App) { diff --git a/src/theme/assets.rs b/src/theme/assets.rs new file mode 100644 index 0000000..6a0a962 --- /dev/null +++ b/src/theme/assets.rs @@ -0,0 +1,24 @@ +use bevy::prelude::*; + +#[derive(Resource, Asset, Reflect, Clone)] +pub struct InteractionAssets { + #[dependency] + pub(super) hover: Handle, + #[dependency] + pub(super) press: Handle, +} + +impl InteractionAssets { + pub const PATH_BUTTON_HOVER: &'static str = "audio/sound_effects/button_hover.ogg"; + pub const PATH_BUTTON_PRESS: &'static str = "audio/sound_effects/button_press.ogg"; +} + +impl FromWorld for InteractionAssets { + fn from_world(world: &mut World) -> Self { + let assets = world.resource::(); + Self { + hover: assets.load(Self::PATH_BUTTON_HOVER), + press: assets.load(Self::PATH_BUTTON_PRESS), + } + } +} diff --git a/src/theme/components.rs b/src/theme/components.rs new file mode 100644 index 0000000..de7d5b8 --- /dev/null +++ b/src/theme/components.rs @@ -0,0 +1,16 @@ +use bevy::prelude::*; + +/// Palette for widget interactions. Add this to an entity that supports +/// [`Interaction`]s, such as a button, to change its [`BackgroundColor`] based +/// on the current interaction state. +#[derive(Component, Debug, Reflect)] +#[reflect(Component)] +pub struct InteractionPalette { + pub none: Color, + pub hovered: Color, + pub pressed: Color, +} + +#[derive(Debug, Reflect, Component)] +#[reflect(Component)] +pub struct UrlLink(pub String); diff --git a/src/theme/events.rs b/src/theme/events.rs new file mode 100644 index 0000000..ca3a63d --- /dev/null +++ b/src/theme/events.rs @@ -0,0 +1,6 @@ +use bevy::prelude::*; + +/// Event triggered on a UI entity when the [`Interaction`] component on the same entity changes to +/// [`Interaction::Pressed`]. Observe this event to detect e.g. button presses. +#[derive(Event)] +pub struct OnPress; diff --git a/src/theme/interaction.rs b/src/theme/interaction.rs deleted file mode 100644 index c1df879..0000000 --- a/src/theme/interaction.rs +++ /dev/null @@ -1,102 +0,0 @@ -use bevy::prelude::*; - -use crate::{asset_tracking::LoadResource, audio::SoundEffect}; - -pub(super) fn plugin(app: &mut App) { - app.register_type::(); - app.load_resource::(); - app.add_systems( - Update, - ( - trigger_on_press, - apply_interaction_palette, - trigger_interaction_sound_effect, - ) - .run_if(resource_exists::), - ); -} - -/// Palette for widget interactions. Add this to an entity that supports -/// [`Interaction`]s, such as a button, to change its [`BackgroundColor`] based -/// on the current interaction state. -#[derive(Component, Debug, Reflect)] -#[reflect(Component)] -pub struct InteractionPalette { - pub none: Color, - pub hovered: Color, - pub pressed: Color, -} - -/// Event triggered on a UI entity when the [`Interaction`] component on the same entity changes to -/// [`Interaction::Pressed`]. Observe this event to detect e.g. button presses. -#[derive(Event)] -pub struct OnPress; - -fn trigger_on_press( - interaction_query: Query<(Entity, &Interaction), Changed>, - mut commands: Commands, -) { - for (entity, interaction) in &interaction_query { - if matches!(interaction, Interaction::Pressed) { - commands.trigger_targets(OnPress, entity); - } - } -} - -fn apply_interaction_palette( - mut palette_query: Query< - (&Interaction, &InteractionPalette, &mut BackgroundColor), - Changed, - >, -) { - for (interaction, palette, mut background) in &mut palette_query { - *background = match interaction { - Interaction::None => palette.none, - Interaction::Hovered => palette.hovered, - Interaction::Pressed => palette.pressed, - } - .into(); - } -} - -#[derive(Resource, Asset, Reflect, Clone)] -pub struct InteractionAssets { - #[dependency] - hover: Handle, - #[dependency] - press: Handle, -} - -impl InteractionAssets { - pub const PATH_BUTTON_HOVER: &'static str = "audio/sound_effects/button_hover.ogg"; - pub const PATH_BUTTON_PRESS: &'static str = "audio/sound_effects/button_press.ogg"; -} - -impl FromWorld for InteractionAssets { - fn from_world(world: &mut World) -> Self { - let assets = world.resource::(); - Self { - hover: assets.load(Self::PATH_BUTTON_HOVER), - press: assets.load(Self::PATH_BUTTON_PRESS), - } - } -} - -fn trigger_interaction_sound_effect( - interaction_query: Query<&Interaction, Changed>, - interaction_assets: Res, - mut commands: Commands, -) { - for interaction in &interaction_query { - let source = match interaction { - Interaction::Hovered => interaction_assets.hover.clone(), - Interaction::Pressed => interaction_assets.press.clone(), - _ => continue, - }; - commands.spawn(( - AudioPlayer::(source), - PlaybackSettings::DESPAWN, - SoundEffect, - )); - } -} diff --git a/src/theme/mod.rs b/src/theme/mod.rs index 86f1e5c..f64b348 100644 --- a/src/theme/mod.rs +++ b/src/theme/mod.rs @@ -2,23 +2,33 @@ // Unused utilities may trigger this lints undesirably. +pub mod assets; mod colorscheme; -pub mod interaction; +pub mod components; +pub mod events; pub mod palette; +mod systems; mod widgets; #[allow(unused_imports)] pub mod prelude { pub use super::{ colorscheme::{ColorScheme, ColorSchemeWrapper}, - interaction::{InteractionPalette, OnPress}, + components::{InteractionPalette, UrlLink}, + events::OnPress, palette as ui_palette, widgets::{Containers as _, Widgets as _}, }; } +use assets::InteractionAssets; use bevy::prelude::*; +use prelude::InteractionPalette; + +use crate::asset_tracking::LoadResource; pub(super) fn plugin(app: &mut App) { - app.add_plugins(interaction::plugin); + app.register_type::(); + app.load_resource::(); + app.add_plugins(systems::plugin); } diff --git a/src/theme/systems/button.rs b/src/theme/systems/button.rs new file mode 100644 index 0000000..76242e8 --- /dev/null +++ b/src/theme/systems/button.rs @@ -0,0 +1,52 @@ +use bevy::prelude::*; + +use crate::{ + audio::SoundEffect, + theme::{assets::InteractionAssets, events::OnPress, prelude::InteractionPalette}, +}; + +pub fn trigger_on_press( + interaction_query: Query<(Entity, &Interaction), Changed>, + mut commands: Commands, +) { + for (entity, interaction) in &interaction_query { + if matches!(interaction, Interaction::Pressed) { + commands.trigger_targets(OnPress, entity); + } + } +} + +pub fn apply_interaction_palette( + mut palette_query: Query< + (&Interaction, &InteractionPalette, &mut BackgroundColor), + Changed, + >, +) { + for (interaction, palette, mut background) in &mut palette_query { + *background = match interaction { + Interaction::None => palette.none, + Interaction::Hovered => palette.hovered, + Interaction::Pressed => palette.pressed, + } + .into(); + } +} + +pub fn trigger_interaction_sound_effect( + interaction_query: Query<&Interaction, Changed>, + interaction_assets: Res, + mut commands: Commands, +) { + for interaction in &interaction_query { + let source = match interaction { + Interaction::Hovered => interaction_assets.hover.clone(), + Interaction::Pressed => interaction_assets.press.clone(), + _ => continue, + }; + commands.spawn(( + AudioPlayer::(source), + PlaybackSettings::DESPAWN, + SoundEffect, + )); + } +} diff --git a/src/theme/systems/mod.rs b/src/theme/systems/mod.rs new file mode 100644 index 0000000..875a6fe --- /dev/null +++ b/src/theme/systems/mod.rs @@ -0,0 +1,18 @@ +mod button; + +use bevy::prelude::*; +use button::{apply_interaction_palette, trigger_interaction_sound_effect, trigger_on_press}; + +use super::assets::InteractionAssets; + +pub(super) fn plugin(app: &mut App) { + app.add_systems( + Update, + ( + trigger_on_press, + apply_interaction_palette, + trigger_interaction_sound_effect, + ) + .run_if(resource_exists::), + ); +} diff --git a/src/theme/widgets.rs b/src/theme/widgets.rs index 85f1962..df9107b 100644 --- a/src/theme/widgets.rs +++ b/src/theme/widgets.rs @@ -1,10 +1,13 @@ //! Helper traits for creating common widgets. -use bevy::{ecs::system::EntityCommands, prelude::*, ui::Val::*}; +use bevy::{ + ecs::system::EntityCommands, prelude::*, ui::Val::*, window::SystemCursorIcon, + winit::cursor::CursorIcon, +}; use rose_pine::RosePineDawn; -use super::prelude::ColorScheme; -use crate::theme::{interaction::InteractionPalette, palette::*}; +use super::prelude::{ColorScheme, InteractionPalette}; +use crate::theme::palette::*; /// An extension trait for spawning UI widgets. pub trait Widgets { @@ -35,6 +38,7 @@ impl Widgets for T { border: UiRect::all(Px(4.)), ..default() }, + CursorIcon::System(SystemCursorIcon::Pointer), BorderRadius::all(Px(8.)), BorderColor(RosePineDawn::Text.to_color()), InteractionPalette {