Merge branch '56-add-ui-library' into 'dev'

Add UI Library

See merge request softwaregrundprojekt/2025-2026/einzelprojekt/tutorium-moritz/bernroider-dominik/bernroider-dominik!15
This commit is contained in:
Dominik Bernroider
2025-11-28 14:57:03 +00:00
18 changed files with 377 additions and 363 deletions

View File

@@ -39,37 +39,27 @@ fn setup(mut commands: Commands) {
}, },
BackgroundColor(Color::srgba(0.0, 0.0, 0.0, 0.8)), BackgroundColor(Color::srgba(0.0, 0.0, 0.0, 0.8)),
children![ children![
( text_with_component(TextType::Phase, "...", 16.0, Color::WHITE),
TextType::Phase, text_with_component(TextType::Timer, "...", 16.0, Color::WHITE),
Text::new("..."), button(
TextFont::from_font_size(16.0),
TextColor(Color::WHITE)
),
(
TextType::Timer,
Text::new("--:--"),
TextFont::from_font_size(16.0),
TextColor(Color::WHITE)
),
(
Button,
inventory::components::ButtonType::InventoryOpen, inventory::components::ButtonType::InventoryOpen,
Node::default(), ButtonVariant::Secondary,
children![( Node {
Text::new("Inventar"), padding: UiRect::all(px(10)),
TextFont::from_font_size(16.0), ..default()
TextColor(Color::WHITE) },
)] "Inventar",
16.0
), ),
( button(
Button,
ButtonType::SettingsOpen, ButtonType::SettingsOpen,
Node::default(), ButtonVariant::Secondary,
children![( Node {
Text::new("Einstellungen"), padding: UiRect::all(px(10)),
TextFont::from_font_size(16.0), ..default()
TextColor(Color::WHITE) },
)] "Einstellungen",
16.0
) )
], ],
)); ));

View File

@@ -10,9 +10,7 @@ pub fn open_settings(commands: &mut Commands) {
position_type: PositionType::Absolute, position_type: PositionType::Absolute,
width: percent(100), width: percent(100),
height: percent(100), height: percent(100),
justify_content: JustifyContent::Center, ..Node::center()
align_items: AlignItems::Center,
..default()
}, },
ZIndex(1), ZIndex(1),
BackgroundColor(Color::srgba(0.0, 0.0, 0.0, 0.8)), BackgroundColor(Color::srgba(0.0, 0.0, 0.0, 0.8)),
@@ -21,10 +19,9 @@ pub fn open_settings(commands: &mut Commands) {
parent parent
.spawn(( .spawn((
Node { Node {
flex_direction: FlexDirection::Column, width: px(700),
align_items: AlignItems::Center,
padding: UiRect::all(px(20.0)), padding: UiRect::all(px(20.0)),
..default() ..Node::vstack(px(20))
}, },
BackgroundColor(Color::srgb(0.2, 0.2, 0.2)), BackgroundColor(Color::srgb(0.2, 0.2, 0.2)),
BorderRadius::all(px(10.0)), BorderRadius::all(px(10.0)),
@@ -32,145 +29,97 @@ pub fn open_settings(commands: &mut Commands) {
.with_children(|parent| { .with_children(|parent| {
parent.spawn(( parent.spawn((
Node { Node {
width: percent(100.0),
justify_content: JustifyContent::SpaceBetween, justify_content: JustifyContent::SpaceBetween,
align_items: AlignItems::Center, ..Node::hstack(px(20))
margin: UiRect::bottom(px(20.0)),
column_gap: px(20.0),
..default()
}, },
children![ children![
( text("Spiel Einstellungen", 40.0, Color::WHITE),
Text::new("Spiel Einstellungen"), pill_button(
TextFont::from_font_size(40.0),
TextColor(Color::WHITE),
),
(
Button,
ButtonType::SettingsClose, ButtonType::SettingsClose,
ButtonVariant::Destructive,
Node { Node {
width: px(40.0), width: px(40),
height: px(40.0), height: px(40),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..default() ..default()
}, },
BackgroundColor(Color::srgb(0.8, 0.2, 0.2)), "X",
BorderRadius::MAX, 24.0
children![( ),
Text::new("X"),
TextFont::from_font_size(24.0),
TextColor(Color::WHITE),
)]
)
], ],
)); ));
parent parent
.spawn(Node { .spawn(Node::vstack(px(10)))
width: percent(100),
flex_direction: FlexDirection::Column,
margin: UiRect::top(px(10.0)),
row_gap: px(10.0),
..default()
})
.with_children(|parent| { .with_children(|parent| {
parent.spawn(( parent.spawn(button(
Button,
ButtonType::SettingsExit, ButtonType::SettingsExit,
ButtonVariant::Secondary,
Node { Node {
width: percent(100), padding: UiRect::all(px(10)),
height: px(80),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
padding: UiRect::horizontal(px(10.0)),
..default() ..default()
}, },
BackgroundColor(NORMAL_BUTTON), "Spiel verlassen",
BorderRadius::all(px(10)), 24.0,
children![(
Text::new("Spiel verlassen"),
TextFont::from_font_size(24.0),
TextColor(Color::WHITE)
)],
)); ));
parent.spawn(( parent.spawn(button(
Button,
ButtonType::SettingsSave, ButtonType::SettingsSave,
ButtonVariant::Secondary,
Node { Node {
width: percent(100), padding: UiRect::all(px(10)),
height: px(80),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
padding: UiRect::horizontal(px(10.0)),
..default() ..default()
}, },
BackgroundColor(NORMAL_BUTTON), "Spiel speichern",
BorderRadius::all(px(10)), 24.0,
children![(
Text::new("Spiel speichern"),
TextFont::from_font_size(24.0),
TextColor(Color::WHITE)
)],
)); ));
parent.spawn(( parent.spawn((
Node { Node {
width: percent(100), justify_content: JustifyContent::Center,
flex_direction: FlexDirection::Row, ..Node::hstack(px(30))
justify_content: JustifyContent::SpaceBetween,
align_items: AlignItems::Center,
column_gap: px(10),
padding: UiRect::horizontal(px(10.0)),
..default()
}, },
children![ children![
( (
Node { Node {
flex_direction: FlexDirection::Column, width: percent(40),
align_items: AlignItems::Center, ..Node::vstack(px(10))
row_gap: px(10),
..default()
}, },
children![ children![
( text("Spiel Einstellungen", 18.0, Color::WHITE),
Text::new("Fokus Phase"), text(
TextFont::from_font_size(12.0), "Tipp: Benutze [Umstellen] um in 10er Schritten zu inkrementieren oder dekrementieren!",
TextColor(Color::WHITE) 16.0,
Color::WHITE
), ),
]
),
(
Node {
align_items: AlignItems::Center,
..Node::vstack(px(10))
},
children![
text("Fokus Phase", 12.0, Color::WHITE),
timer_settings(TimerType::Focus) timer_settings(TimerType::Focus)
] ]
), ),
( (
Node { Node {
flex_direction: FlexDirection::Column,
align_items: AlignItems::Center, align_items: AlignItems::Center,
row_gap: px(10), ..Node::vstack(px(10))
..default()
}, },
children![ children![
( text("Kurze Pause", 12.0, Color::WHITE),
Text::new("Kurze Pause"),
TextFont::from_font_size(12.0),
TextColor(Color::WHITE)
),
timer_settings(TimerType::ShortBreak) timer_settings(TimerType::ShortBreak)
] ]
), ),
( (
Node { Node {
flex_direction: FlexDirection::Column,
align_items: AlignItems::Center, align_items: AlignItems::Center,
row_gap: px(10), ..Node::vstack(px(10))
..default()
}, },
children![ children![
( text("Lange Pause", 12.0, Color::WHITE),
Text::new("Lange Pause"),
TextFont::from_font_size(12.0),
TextColor(Color::WHITE)
),
timer_settings(TimerType::LongBreak) timer_settings(TimerType::LongBreak)
] ]
) )

View File

@@ -4,17 +4,12 @@ use crate::prelude::*;
pub fn timer_settings(timer_type: TimerType) -> impl Bundle { pub fn timer_settings(timer_type: TimerType) -> impl Bundle {
( (
Node { Node {
flex_direction: FlexDirection::Row,
align_items: AlignItems::Center, align_items: AlignItems::Center,
..default() ..Node::hstack(px(0))
}, },
children![ children![
timer_settings_part(SettingsTimerInput::Minutes(timer_type.clone()), 1), timer_settings_part(SettingsTimerInput::Minutes(timer_type.clone()), 1),
( text(":", 24.0, Color::WHITE),
Text::new(":"),
TextFont::from_font_size(24.0),
TextColor(Color::WHITE)
),
timer_settings_part(SettingsTimerInput::Seconds(timer_type.clone()), 1), timer_settings_part(SettingsTimerInput::Seconds(timer_type.clone()), 1),
], ],
) )
@@ -22,52 +17,34 @@ pub fn timer_settings(timer_type: TimerType) -> impl Bundle {
fn timer_settings_part(input: SettingsTimerInput, amount: u32) -> impl Bundle { fn timer_settings_part(input: SettingsTimerInput, amount: u32) -> impl Bundle {
( (
Node { Node::vstack(px(0)),
flex_direction: FlexDirection::Column,
..default()
},
children![ children![
( button(
Button,
ButtonType::SettingsTimerChange { ButtonType::SettingsTimerChange {
input: input.clone(), input: input.clone(),
amount: amount as i32 amount: amount as i32
}, },
ButtonVariant::Secondary,
Node { Node {
width: auto(), width: percent(100),
justify_content: JustifyContent::Center,
..default() ..default()
}, },
BackgroundColor(NORMAL_BUTTON), "+",
children![ 12.0
Text::new("+"),
TextFont::from_font_size(12.0),
TextColor(Color::WHITE)
]
), ),
( text_with_component(input.clone(), "--", 24.0, Color::WHITE),
input.clone(), button(
Text::new("--"),
TextFont::from_font_size(24.0),
TextColor(Color::WHITE)
),
(
Button,
ButtonType::SettingsTimerChange { ButtonType::SettingsTimerChange {
input: input.clone(), input: input.clone(),
amount: -(amount as i32), amount: -(amount as i32)
}, },
ButtonVariant::Secondary,
Node { Node {
width: auto(), width: percent(100),
justify_content: JustifyContent::Center,
..default() ..default()
}, },
BackgroundColor(NORMAL_BUTTON), "-",
children![ 12.0
Text::new("-"),
TextFont::from_font_size(12.0),
TextColor(Color::WHITE)
]
), ),
], ],
) )

View File

@@ -9,9 +9,7 @@ pub fn open_inventory(commands: &mut Commands, items: Query<&ItemStack>) {
position_type: PositionType::Absolute, position_type: PositionType::Absolute,
width: percent(100), width: percent(100),
height: percent(100), height: percent(100),
justify_content: JustifyContent::Center, ..Node::center()
align_items: AlignItems::Center,
..default()
}, },
ZIndex(1), ZIndex(1),
BackgroundColor(Color::srgba(0.0, 0.0, 0.0, 0.8)), BackgroundColor(Color::srgba(0.0, 0.0, 0.0, 0.8)),
@@ -20,10 +18,8 @@ pub fn open_inventory(commands: &mut Commands, items: Query<&ItemStack>) {
parent parent
.spawn(( .spawn((
Node { Node {
flex_direction: FlexDirection::Column,
align_items: AlignItems::Center,
padding: UiRect::all(px(20.0)), padding: UiRect::all(px(20.0)),
..default() ..Node::vstack(px(0))
}, },
BackgroundColor(Color::srgb(0.2, 0.2, 0.2)), BackgroundColor(Color::srgb(0.2, 0.2, 0.2)),
BorderRadius::all(px(10.0)), BorderRadius::all(px(10.0)),
@@ -33,45 +29,30 @@ pub fn open_inventory(commands: &mut Commands, items: Query<&ItemStack>) {
Node { Node {
width: percent(100.0), width: percent(100.0),
justify_content: JustifyContent::SpaceBetween, justify_content: JustifyContent::SpaceBetween,
align_items: AlignItems::Center,
margin: UiRect::bottom(px(20.0)), margin: UiRect::bottom(px(20.0)),
column_gap: px(20.0), ..Node::hstack(px(20))
..default()
}, },
children![ children![
( text("Inventar", 40.0, Color::WHITE),
Text::new("Inventar"), pill_button(
TextFont::from_font_size(40.0),
TextColor(Color::WHITE),
),
(
Button,
ButtonType::InventoryClose, ButtonType::InventoryClose,
ButtonVariant::Destructive,
Node { Node {
width: px(40.0), width: px(40),
height: px(40.0), height: px(40),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..default() ..default()
}, },
BackgroundColor(Color::srgb(0.8, 0.2, 0.2)), "X",
BorderRadius::MAX, 24.0
children![( ),
Text::new("X"),
TextFont::from_font_size(24.0),
TextColor(Color::WHITE),
)]
)
], ],
)); ));
parent parent
.spawn(Node { .spawn(Node {
width: percent(100), width: percent(100),
flex_direction: FlexDirection::Column,
margin: UiRect::top(px(10.0)), margin: UiRect::top(px(10.0)),
row_gap: px(10.0), ..Node::vstack(px(10))
..default()
}) })
.with_children(|parent| { .with_children(|parent| {
for itemstack in items.iter() { for itemstack in items.iter() {

View File

@@ -8,13 +8,10 @@ pub fn list_itemstack(itemstack: &ItemStack) -> impl Bundle {
( (
Node { Node {
flex_direction: FlexDirection::Row,
column_gap: px(8),
align_items: AlignItems::Center,
padding: UiRect::all(px(4)), padding: UiRect::all(px(4)),
..default() ..Node::hstack(px(8))
}, },
BackgroundColor(NORMAL_BUTTON), BackgroundColor(ButtonVariant::Secondary.normal_background()),
BorderRadius::all(px(10)), BorderRadius::all(px(10)),
children![ children![
( (
@@ -24,28 +21,22 @@ pub fn list_itemstack(itemstack: &ItemStack) -> impl Bundle {
aspect_ratio: Some(1.0), aspect_ratio: Some(1.0),
..default() ..default()
}, },
BackgroundColor(HOVERED_BUTTON), BackgroundColor(ButtonVariant::Secondary.hover_background()),
BorderRadius::all(px(10)) BorderRadius::all(px(10))
), ),
( (
Node { Node {
flex_direction: FlexDirection::Column,
justify_content: JustifyContent::Center, justify_content: JustifyContent::Center,
row_gap: px(4),
padding: UiRect::vertical(px(4)), padding: UiRect::vertical(px(4)),
..default() ..Node::vstack(px(4))
}, },
children![ children![
( text(
Text::new(format!("{} ({})", name, itemstack.amount)), format!("{} ({})", name, itemstack.amount),
TextFont::from_font_size(14.0), 14.0,
TextColor(Color::WHITE) Color::WHITE
), ),
( text(itemstack.item_type.description(), 10.0, Color::WHITE)
Text::new(itemstack.item_type.description()),
TextFont::from_font_size(10.0),
TextColor(Color::WHITE)
)
] ]
) )
], ],

View File

@@ -92,3 +92,15 @@ impl SavegamePath {
Self::new(next_index) Self::new(next_index)
} }
} }
#[derive(Component)]
pub enum RootMarker {
PopupSavegameLoad,
}
#[derive(Component)]
pub enum ButtonType {
SavegameLoad { savegame_path: SavegamePath },
SavegameDelete { savegame_path: SavegamePath },
PopupClose,
}

View File

@@ -1,4 +1,5 @@
use crate::features::phase::components::{SessionTracker, TimerSettings}; use crate::features::phase::components::{SessionTracker, TimerSettings};
use crate::features::savegame::ui::load_popup_handler;
use crate::prelude::*; use crate::prelude::*;
use messages::*; use messages::*;
use std::fs::File; use std::fs::File;
@@ -6,6 +7,7 @@ use std::io::{Read, Write};
pub mod components; pub mod components;
pub mod messages; pub mod messages;
pub mod ui;
pub struct SavegamePlugin; pub struct SavegamePlugin;
@@ -16,6 +18,8 @@ impl Plugin for SavegamePlugin {
app.add_systems(Update, dump_savegame.run_if(in_state(AppState::GameScreen))); app.add_systems(Update, dump_savegame.run_if(in_state(AppState::GameScreen)));
app.add_systems(Update, load_savegame.run_if(in_state(AppState::GameScreen))); app.add_systems(Update, load_savegame.run_if(in_state(AppState::GameScreen)));
app.add_systems(Update, load_popup_handler);
} }
} }

View File

@@ -1,5 +1,5 @@
use super::super::components::*; use super::super::components::{ButtonType, RootMarker};
use crate::prelude::*; use crate::{features::savegame::messages::SavegameLoadMessage, prelude::*};
pub fn spawn_load_popup(commands: &mut Commands) { pub fn spawn_load_popup(commands: &mut Commands) {
commands commands
@@ -9,9 +9,7 @@ pub fn spawn_load_popup(commands: &mut Commands) {
position_type: PositionType::Absolute, position_type: PositionType::Absolute,
width: percent(100), width: percent(100),
height: percent(100), height: percent(100),
justify_content: JustifyContent::Center, ..Node::center()
align_items: AlignItems::Center,
..default()
}, },
ZIndex(1), ZIndex(1),
BackgroundColor(Color::srgba(0.0, 0.0, 0.0, 0.8)), BackgroundColor(Color::srgba(0.0, 0.0, 0.0, 0.8)),
@@ -22,13 +20,12 @@ pub fn spawn_load_popup(commands: &mut Commands) {
Node { Node {
width: px(600.0), width: px(600.0),
height: px(500.0), height: px(500.0),
flex_direction: FlexDirection::Column,
align_items: AlignItems::Center,
padding: UiRect::all(px(20.0)), padding: UiRect::all(px(20.0)),
..default() align_items: AlignItems::Center,
..Node::vstack(px(10))
}, },
BackgroundColor(Color::srgb(0.2, 0.2, 0.2)), BackgroundColor(Color::srgb(0.2, 0.2, 0.2)),
BorderRadius::all(Val::Px(10.0)), BorderRadius::all(px(10.0)),
)) ))
.with_children(|parent| { .with_children(|parent| {
parent.spawn(( parent.spawn((
@@ -40,27 +37,17 @@ pub fn spawn_load_popup(commands: &mut Commands) {
..default() ..default()
}, },
children![ children![
( text("Spielstand Auswahl", 40.0, Color::WHITE),
Text::new("Spielstand Auswahl"), pill_button(
TextFont::from_font_size(40.0),
TextColor(Color::WHITE),
),
(
Button,
ButtonType::PopupClose, ButtonType::PopupClose,
ButtonVariant::Destructive,
Node { Node {
width: px(40.0), width: px(40),
height: px(40.0), height: px(40),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..default() ..default()
}, },
BackgroundColor(Color::srgb(0.8, 0.2, 0.2)), "X",
children![( 24.0
Text::new("X"),
TextFont::from_font_size(24.0),
TextColor(Color::WHITE),
)]
) )
], ],
)); ));
@@ -78,20 +65,20 @@ pub fn spawn_load_popup(commands: &mut Commands) {
for savegame in SavegamePath::list() { for savegame in SavegamePath::list() {
parent.spawn(( parent.spawn((
Button, Button,
ButtonType::PopupSavegameLoad { ButtonType::SavegameLoad {
savegame_path: savegame.path.clone(), savegame_path: savegame.path.clone(),
}, },
ButtonVariant::Secondary,
Node { Node {
width: percent(100), width: percent(100),
height: px(80), height: px(80),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
flex_direction: FlexDirection::Row, flex_direction: FlexDirection::Row,
column_gap: px(10.0), column_gap: px(10.0),
padding: UiRect::horizontal(px(10.0)), padding: UiRect::horizontal(px(10.0)),
..default() ..Node::center()
}, },
BackgroundColor(NORMAL_BUTTON), BackgroundColor(ButtonVariant::Secondary.normal_background()),
BorderRadius::all(px(10)),
children![ children![
( (
Node { Node {
@@ -102,43 +89,35 @@ pub fn spawn_load_popup(commands: &mut Commands) {
..default() ..default()
}, },
children![ children![
( text(
Text::new(format!( format!("Spielstand {}", savegame.index + 1),
"Spielstand {}", 24.0,
savegame.index + 1 Color::WHITE
)),
TextFont::from_font_size(24.0),
TextColor(Color::srgb(0.9, 0.9, 0.9))
), ),
( text(
Text::new(format!( format!(
"Beeren: {}, Fokusphasen abgeschlossen: {}", "Beeren: {}, Fokusphasen abgeschlossen: {}",
savegame.total_berries, savegame.total_berries,
savegame.completed_focus savegame.completed_focus
)), ),
TextFont::from_font_size(18.0), 18.0,
TextColor(Color::srgb(0.9, 0.9, 0.9)) Color::WHITE,
) ),
] ]
), ),
( pill_button(
Button, ButtonType::SavegameDelete {
ButtonType::PopupSavegameDelete {
savegame_path: savegame.path.clone() savegame_path: savegame.path.clone()
}, },
ButtonVariant::Destructive,
Node { Node {
width: px(40.0), width: px(40),
height: px(40.0), height: px(40),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..default() ..default()
}, },
children![( "X",
Text::new("X"), 24.0
TextFont::from_font_size(24.0), ),
TextColor(Color::srgb(0.9, 0.9, 0.9))
)]
)
], ],
)); ));
} }
@@ -146,3 +125,38 @@ pub fn spawn_load_popup(commands: &mut Commands) {
}); });
}); });
} }
pub fn load_popup_handler(
mut commands: Commands,
mut next_state: ResMut<NextState<AppState>>,
mut interaction_query: Query<(&Interaction, &ButtonType), (Changed<Interaction>, With<Button>)>,
root_query: Query<(Entity, &RootMarker)>,
mut savegame_messages: MessageWriter<SavegameLoadMessage>,
) {
for (interaction, button_type) in &mut interaction_query {
match *interaction {
Interaction::Pressed => {
match button_type {
ButtonType::PopupClose => {}
ButtonType::SavegameLoad { savegame_path } => {
commands.insert_resource(savegame_path.clone());
next_state.set(AppState::GameScreen);
savegame_messages.write(SavegameLoadMessage);
}
ButtonType::SavegameDelete { savegame_path } => {
if let Err(e) = std::fs::remove_file(savegame_path.clone().0) {
println!("Error while deleting savegame: {:?}", e);
}
}
};
for (entity, root) in root_query.iter() {
match *root {
RootMarker::PopupSavegameLoad => commands.entity(entity).despawn(),
}
}
}
_ => (),
}
}
}

View File

@@ -3,7 +3,6 @@ use crate::prelude::*;
#[derive(Component)] #[derive(Component)]
pub enum RootMarker { pub enum RootMarker {
MainMenu, MainMenu,
PopupSavegameLoad,
} }
#[derive(Component)] #[derive(Component)]
@@ -11,7 +10,4 @@ pub enum ButtonType {
LoadGame, LoadGame,
NewGame, NewGame,
Settings, Settings,
PopupSavegameLoad { savegame_path: SavegamePath },
PopupSavegameDelete { savegame_path: SavegamePath },
PopupClose,
} }

View File

@@ -1,9 +1,8 @@
use crate::{features::savegame::messages::SavegameLoadMessage, prelude::*}; use crate::features::savegame::ui::spawn_load_popup;
use crate::prelude::*;
use components::*; use components::*;
use ui::*;
pub mod components; pub mod components;
pub mod ui;
pub struct StartScreenPlugin; pub struct StartScreenPlugin;
@@ -21,68 +20,45 @@ fn setup(mut commands: Commands) {
Node { Node {
width: percent(100), width: percent(100),
height: percent(100), height: percent(100),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
flex_direction: FlexDirection::Column, flex_direction: FlexDirection::Column,
..default() row_gap: px(10),
..Node::center()
}, },
children![ children![
( text("Pomomon Garden", 64.0, Color::WHITE),
Text::new("Pomonon Garten"), button(
TextFont::from_font_size(64.0),
TextColor(Color::srgb(0.9, 0.9, 0.9))
),
(
Button,
ButtonType::LoadGame, ButtonType::LoadGame,
ButtonVariant::Primary,
Node { Node {
width: px(300), width: px(280),
height: px(65), padding: UiRect::all(px(10)),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..default() ..default()
}, },
BackgroundColor(NORMAL_BUTTON), "Spiel laden",
children![( 33.0
Text::new("Spiel laden"),
TextFont::from_font_size(33.0),
TextColor(Color::srgb(0.9, 0.9, 0.9))
)]
), ),
( button(
Button,
ButtonType::NewGame, ButtonType::NewGame,
ButtonVariant::Primary,
Node { Node {
width: px(300), width: px(280),
height: px(65), padding: UiRect::all(px(10)),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..default() ..default()
}, },
BackgroundColor(NORMAL_BUTTON), "Neues Spiel",
children![( 33.0,
Text::new("Neues Spiel"),
TextFont::from_font_size(33.0),
TextColor(Color::srgb(0.9, 0.9, 0.9))
)]
), ),
( button(
Button,
ButtonType::Settings, ButtonType::Settings,
ButtonVariant::Secondary,
Node { Node {
width: px(300), width: px(280),
height: px(65), padding: UiRect::all(px(10)),
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..default() ..default()
}, },
BackgroundColor(NORMAL_BUTTON), "Einstellungen",
children![( 33.0
Text::new("Einstellungen"), ),
TextFont::from_font_size(33.0),
TextColor(Color::srgb(0.9, 0.9, 0.9))
)]
)
], ],
)); ));
} }
@@ -90,18 +66,11 @@ fn setup(mut commands: Commands) {
fn menu( fn menu(
mut commands: Commands, mut commands: Commands,
mut next_state: ResMut<NextState<AppState>>, mut next_state: ResMut<NextState<AppState>>,
mut interaction_query: Query< mut interaction_query: Query<(&Interaction, &ButtonType), (Changed<Interaction>, With<Button>)>,
(&Interaction, &ButtonType, &mut BackgroundColor),
(Changed<Interaction>, With<Button>),
>,
root_query: Query<(Entity, &RootMarker)>,
mut savegame_messages: MessageWriter<SavegameLoadMessage>,
) { ) {
for (interaction, button_type, mut color) in &mut interaction_query { for (interaction, button_type) in &mut interaction_query {
match *interaction { match *interaction {
Interaction::Pressed => { Interaction::Pressed => {
*color = PRESSED_BUTTON.into();
match button_type { match button_type {
ButtonType::LoadGame => { ButtonType::LoadGame => {
spawn_load_popup(&mut commands); spawn_load_popup(&mut commands);
@@ -110,40 +79,10 @@ fn menu(
commands.insert_resource(SavegamePath::next()); commands.insert_resource(SavegamePath::next());
next_state.set(AppState::GameScreen); next_state.set(AppState::GameScreen);
} }
ButtonType::PopupClose => { ButtonType::Settings => todo!(),
for (entity, root) in root_query.iter() {
match *root {
RootMarker::PopupSavegameLoad => commands.entity(entity).despawn(),
_ => {}
}
}
}
ButtonType::PopupSavegameLoad { savegame_path } => {
commands.insert_resource(savegame_path.clone());
next_state.set(AppState::GameScreen);
savegame_messages.write(SavegameLoadMessage);
}
ButtonType::PopupSavegameDelete { savegame_path } => {
if let Err(e) = std::fs::remove_file(savegame_path.clone().0) {
println!("Error while deleting savegame: {:?}", e);
}
for (entity, root) in root_query.iter() {
match *root {
RootMarker::PopupSavegameLoad => commands.entity(entity).despawn(),
_ => {}
}
}
}
_ => (),
}; };
} }
Interaction::Hovered => { _ => (),
*color = HOVERED_BUTTON.into();
}
Interaction::None => {
*color = NORMAL_BUTTON.into();
}
} }
} }
} }

View File

@@ -6,3 +6,43 @@ pub struct Scroll {
pub entity: Entity, pub entity: Entity,
pub delta: Vec2, pub delta: Vec2,
} }
#[derive(Component, Clone)]
pub enum ButtonVariant {
Primary,
Secondary,
Destructive,
}
impl ButtonVariant {
pub fn normal_background(&self) -> Color {
match self {
ButtonVariant::Primary => Color::srgb(0.35, 0.17, 0.78),
ButtonVariant::Secondary => Color::srgb(0.15, 0.15, 0.15),
ButtonVariant::Destructive => Color::srgb(0.79, 0.17, 0.20),
}
}
pub fn hover_background(&self) -> Color {
match self {
ButtonVariant::Primary => Color::srgb(0.45, 0.27, 0.88),
ButtonVariant::Secondary => Color::srgb(0.25, 0.25, 0.25),
ButtonVariant::Destructive => Color::srgb(0.89, 0.27, 0.30),
}
}
pub fn pressed_background(&self) -> Color {
match self {
ButtonVariant::Primary => Color::srgb(0.55, 0.37, 0.98),
ButtonVariant::Secondary => Color::srgb(0.35, 0.35, 0.35),
ButtonVariant::Destructive => Color::srgb(0.99, 0.37, 0.40),
}
}
pub fn text_color(&self) -> Color {
match self {
ButtonVariant::Primary | ButtonVariant::Destructive => Color::srgb(0.1, 0.1, 0.1),
ButtonVariant::Secondary => Color::WHITE,
}
}
}

View File

@@ -1,8 +1,9 @@
use crate::prelude::*; use crate::prelude::{button::update_buttons, *};
use bevy::{input::mouse::*, picking::hover::HoverMap}; use bevy::{input::mouse::*, picking::hover::HoverMap};
pub mod components; pub mod components;
pub mod consts; pub mod consts;
pub mod ui;
pub struct UiPlugin; pub struct UiPlugin;
@@ -10,6 +11,8 @@ impl Plugin for UiPlugin {
fn build(&self, app: &mut App) { fn build(&self, app: &mut App) {
app.add_systems(Update, scroll_events); app.add_systems(Update, scroll_events);
app.add_observer(on_scroll_handler); app.add_observer(on_scroll_handler);
app.add_systems(Update, update_buttons);
} }
} }

View File

@@ -0,0 +1,59 @@
use crate::prelude::*;
pub fn button(
button_type: impl Component,
variant: ButtonVariant,
mut node: Node,
title: impl Into<String>,
font_size: f32,
) -> impl Bundle {
node.justify_content = JustifyContent::Center;
node.align_items = AlignItems::Center;
(
Button,
button_type,
variant.clone(),
node,
BackgroundColor(variant.normal_background()),
BorderRadius::all(px(10)),
children![text(title, font_size, variant.text_color())],
)
}
pub fn pill_button(
button_type: impl Component,
variant: ButtonVariant,
mut node: Node,
title: impl Into<String>,
font_size: f32,
) -> impl Bundle {
node.justify_content = JustifyContent::Center;
node.align_items = AlignItems::Center;
(
Button,
button_type,
variant.clone(),
node,
BackgroundColor(variant.normal_background()),
BorderRadius::MAX,
children![text(title, font_size, variant.text_color())],
)
}
pub fn update_buttons(
mut interaction_query: Query<
(&Interaction, &ButtonVariant, &mut BackgroundColor),
(Changed<Interaction>, With<Button>),
>,
) {
for (interaction, variant, mut color) in &mut interaction_query {
*color = match *interaction {
Interaction::None => variant.normal_background(),
Interaction::Hovered => variant.hover_background(),
Interaction::Pressed => variant.pressed_background(),
}
.into()
}
}

View File

@@ -0,0 +1,33 @@
use crate::prelude::*;
pub trait Flexbox {
fn hstack(spacing: Val) -> Self;
fn vstack(spacing: Val) -> Self;
fn center() -> Self;
}
impl Flexbox for Node {
fn hstack(spacing: Val) -> Self {
Self {
flex_direction: FlexDirection::Row,
column_gap: spacing,
..default()
}
}
fn vstack(spacing: Val) -> Self {
Self {
flex_direction: FlexDirection::Column,
row_gap: spacing,
..default()
}
}
fn center() -> Self {
Self {
justify_content: JustifyContent::Center,
align_items: AlignItems::Center,
..default()
}
}
}

View File

@@ -0,0 +1,7 @@
pub mod button;
pub mod flexbox;
pub mod texts;
pub use button::{button, pill_button};
pub use flexbox::Flexbox;
pub use texts::{text, text_with_component};

View File

@@ -0,0 +1,19 @@
pub use crate::prelude::*;
pub fn text(content: impl Into<String>, size: f32, color: Color) -> (Text, TextFont, TextColor) {
(
Text::new(content),
TextFont::from_font_size(size),
TextColor(color),
)
}
pub fn text_with_component(
component: impl Component,
content: impl Into<String>,
size: f32,
color: Color,
) -> impl Bundle {
let (a, b, c) = text(content, size, color);
(component, a, b, c)
}

View File

@@ -14,7 +14,7 @@ pub use crate::features::{
messages::{InteractStartMessage, MoveMessage}, messages::{InteractStartMessage, MoveMessage},
}, },
savegame::components::SavegamePath, savegame::components::SavegamePath,
ui::consts::*, ui::{components::ButtonVariant, consts::*, ui::*},
}; };
pub use crate::utils::path::get_internal_path; pub use crate::utils::path::get_internal_path;
pub use bevy::prelude::*; pub use bevy::prelude::*;