refactor(themes): separate into files

This commit is contained in:
Kristofers Solo 2025-01-07 10:25:42 +02:00
parent 69eacd42d5
commit 0f4899319d
12 changed files with 140 additions and 112 deletions

2
Cargo.lock generated
View File

@ -3129,7 +3129,7 @@ dependencies = [
[[package]] [[package]]
name = "maze-ascension" name = "maze-ascension"
version = "1.0.2" version = "1.0.3"
dependencies = [ dependencies = [
"anyhow", "anyhow",
"bevy", "bevy",

View File

@ -1,7 +1,7 @@
[package] [package]
name = "maze-ascension" name = "maze-ascension"
authors = ["Kristofers Solo <dev@kristofers.xyz>"] authors = ["Kristofers Solo <dev@kristofers.xyz>"]
version = "1.0.2" version = "1.0.3"
edition = "2021" edition = "2021"
[dependencies] [dependencies]

View File

@ -12,7 +12,7 @@ native-release:
# Run web dev # Run web dev
web-dev: web-dev:
RUST_BACKTRACE=full trunk serve RUSTC_WRAPPER=sccache RUST_BACKTRACE=full trunk serve
# Run web release # Run web release
web-release: web-release:

View File

@ -7,7 +7,7 @@ use crate::{
hint::assets::HintAssets, hint::assets::HintAssets,
player::assets::PlayerAssets, player::assets::PlayerAssets,
screens::Screen, screens::Screen,
theme::{interaction::InteractionAssets, prelude::*}, theme::{assets::InteractionAssets, prelude::*},
}; };
pub fn plugin(app: &mut App) { pub fn plugin(app: &mut App) {

24
src/theme/assets.rs Normal file
View File

@ -0,0 +1,24 @@
use bevy::prelude::*;
#[derive(Resource, Asset, Reflect, Clone)]
pub struct InteractionAssets {
#[dependency]
pub(super) hover: Handle<AudioSource>,
#[dependency]
pub(super) press: Handle<AudioSource>,
}
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::<AssetServer>();
Self {
hover: assets.load(Self::PATH_BUTTON_HOVER),
press: assets.load(Self::PATH_BUTTON_PRESS),
}
}
}

16
src/theme/components.rs Normal file
View File

@ -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);

6
src/theme/events.rs Normal file
View File

@ -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;

View File

@ -1,102 +0,0 @@
use bevy::prelude::*;
use crate::{asset_tracking::LoadResource, audio::SoundEffect};
pub(super) fn plugin(app: &mut App) {
app.register_type::<InteractionPalette>();
app.load_resource::<InteractionAssets>();
app.add_systems(
Update,
(
trigger_on_press,
apply_interaction_palette,
trigger_interaction_sound_effect,
)
.run_if(resource_exists::<InteractionAssets>),
);
}
/// 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<Interaction>>,
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<Interaction>,
>,
) {
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<AudioSource>,
#[dependency]
press: Handle<AudioSource>,
}
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::<AssetServer>();
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>>,
interaction_assets: Res<InteractionAssets>,
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::<AudioSource>(source),
PlaybackSettings::DESPAWN,
SoundEffect,
));
}
}

View File

@ -2,23 +2,33 @@
// Unused utilities may trigger this lints undesirably. // Unused utilities may trigger this lints undesirably.
pub mod assets;
mod colorscheme; mod colorscheme;
pub mod interaction; pub mod components;
pub mod events;
pub mod palette; pub mod palette;
mod systems;
mod widgets; mod widgets;
#[allow(unused_imports)] #[allow(unused_imports)]
pub mod prelude { pub mod prelude {
pub use super::{ pub use super::{
colorscheme::{ColorScheme, ColorSchemeWrapper}, colorscheme::{ColorScheme, ColorSchemeWrapper},
interaction::{InteractionPalette, OnPress}, components::{InteractionPalette, UrlLink},
events::OnPress,
palette as ui_palette, palette as ui_palette,
widgets::{Containers as _, Widgets as _}, widgets::{Containers as _, Widgets as _},
}; };
} }
use assets::InteractionAssets;
use bevy::prelude::*; use bevy::prelude::*;
use prelude::InteractionPalette;
use crate::asset_tracking::LoadResource;
pub(super) fn plugin(app: &mut App) { pub(super) fn plugin(app: &mut App) {
app.add_plugins(interaction::plugin); app.register_type::<InteractionPalette>();
app.load_resource::<InteractionAssets>();
app.add_plugins(systems::plugin);
} }

View File

@ -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<Interaction>>,
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<Interaction>,
>,
) {
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>>,
interaction_assets: Res<InteractionAssets>,
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::<AudioSource>(source),
PlaybackSettings::DESPAWN,
SoundEffect,
));
}
}

18
src/theme/systems/mod.rs Normal file
View File

@ -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::<InteractionAssets>),
);
}

View File

@ -1,10 +1,13 @@
//! Helper traits for creating common widgets. //! 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 rose_pine::RosePineDawn;
use super::prelude::ColorScheme; use super::prelude::{ColorScheme, InteractionPalette};
use crate::theme::{interaction::InteractionPalette, palette::*}; use crate::theme::palette::*;
/// An extension trait for spawning UI widgets. /// An extension trait for spawning UI widgets.
pub trait Widgets { pub trait Widgets {
@ -35,6 +38,7 @@ impl<T: SpawnUi> Widgets for T {
border: UiRect::all(Px(4.)), border: UiRect::all(Px(4.)),
..default() ..default()
}, },
CursorIcon::System(SystemCursorIcon::Pointer),
BorderRadius::all(Px(8.)), BorderRadius::all(Px(8.)),
BorderColor(RosePineDawn::Text.to_color()), BorderColor(RosePineDawn::Text.to_color()),
InteractionPalette { InteractionPalette {