1use bevy::{
11 dev_tools::states::*,
12 picking::hover::Hovered,
13 prelude::*,
14 ui_widgets::{Activate, ActivateOnPress, Button},
15};
16
17use ui::*;
18
19#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, States)]
20enum AppState {
21 #[default]
22 Menu,
23 InGame,
24}
25
26#[derive(Debug, Clone, Copy, Default, Eq, PartialEq, Hash, SubStates)]
28#[source(AppState = AppState::InGame)]
33enum IsPaused {
34 #[default]
35 Running,
36 Paused,
37}
38
39fn main() {
40 App::new()
41 .add_plugins(DefaultPlugins)
42 .init_state::<AppState>()
43 .add_sub_state::<IsPaused>() .add_systems(Startup, setup)
46 .add_systems(OnEnter(AppState::Menu), setup_menu)
47 .add_observer(on_activate_start_game.run_if(in_state(AppState::Menu)))
48 .add_systems(Update, hover_style)
49 .add_systems(OnExit(AppState::Menu), cleanup_menu)
50 .add_systems(OnEnter(AppState::InGame), setup_game)
51 .add_systems(OnEnter(IsPaused::Paused), setup_paused_screen)
52 .add_systems(
53 Update,
54 (
55 (movement, change_color).run_if(in_state(IsPaused::Running)),
59 toggle_pause.run_if(in_state(AppState::InGame)),
62 ),
63 )
64 .add_systems(Update, log_transitions::<AppState>)
65 .run();
66}
67
68fn on_activate_start_game(_: On<Activate>, mut next_state: ResMut<NextState<AppState>>) {
69 next_state.set(AppState::InGame);
70}
71
72fn hover_style(
73 mut button_query: Query<(&Hovered, &mut BackgroundColor), (Changed<Hovered>, With<Button>)>,
74) {
75 for (hovered, mut color) in &mut button_query {
76 if hovered.get() {
77 *color = HOVERED_BUTTON.into();
78 } else {
79 *color = NORMAL_BUTTON.into();
80 }
81 }
82}
83
84fn cleanup_menu(mut commands: Commands, menu_data: Res<MenuData>) {
85 commands.entity(menu_data.button_entity).despawn();
86}
87
88const SPEED: f32 = 100.0;
89fn movement(
90 time: Res<Time>,
91 input: Res<ButtonInput<KeyCode>>,
92 mut query: Query<&mut Transform, With<Sprite>>,
93) {
94 for mut transform in &mut query {
95 let mut direction = Vec3::ZERO;
96 if input.pressed(KeyCode::ArrowLeft) {
97 direction.x -= 1.0;
98 }
99 if input.pressed(KeyCode::ArrowRight) {
100 direction.x += 1.0;
101 }
102 if input.pressed(KeyCode::ArrowUp) {
103 direction.y += 1.0;
104 }
105 if input.pressed(KeyCode::ArrowDown) {
106 direction.y -= 1.0;
107 }
108
109 if direction != Vec3::ZERO {
110 transform.translation += direction.normalize() * SPEED * time.delta_secs();
111 }
112 }
113}
114
115fn change_color(time: Res<Time>, mut query: Query<&mut Sprite>) {
116 for mut sprite in &mut query {
117 let new_color = LinearRgba {
118 blue: ops::sin(time.elapsed_secs() * 0.5) + 2.0,
119 ..LinearRgba::from(sprite.color)
120 };
121
122 sprite.color = new_color.into();
123 }
124}
125
126fn toggle_pause(
127 input: Res<ButtonInput<KeyCode>>,
128 current_state: Res<State<IsPaused>>,
129 mut next_state: ResMut<NextState<IsPaused>>,
130) {
131 if input.just_pressed(KeyCode::Space) {
132 next_state.set(match current_state.get() {
133 IsPaused::Running => IsPaused::Paused,
134 IsPaused::Paused => IsPaused::Running,
135 });
136 }
137}
138
139mod ui {
140 use crate::*;
141
142 #[derive(Resource)]
143 pub struct MenuData {
144 pub button_entity: Entity,
145 }
146
147 pub const NORMAL_BUTTON: Color = Color::srgb(0.15, 0.15, 0.15);
148 pub const HOVERED_BUTTON: Color = Color::srgb(0.25, 0.25, 0.25);
149
150 pub fn setup(mut commands: Commands) {
151 commands.spawn(Camera2d);
152 }
153
154 pub fn setup_menu(mut commands: Commands) {
155 let button_entity = commands
156 .spawn((
157 Node {
158 width: percent(100),
160 height: percent(100),
161 justify_content: JustifyContent::Center,
162 align_items: AlignItems::Center,
163 ..default()
164 },
165 children![(
166 Button,
167 ActivateOnPress,
168 Hovered::default(),
169 Node {
170 width: px(150),
171 height: px(65),
172 justify_content: JustifyContent::Center,
174 align_items: AlignItems::Center,
176 ..default()
177 },
178 BackgroundColor(NORMAL_BUTTON),
179 children![(
180 Text::new("Play"),
181 TextFont {
182 font_size: FontSize::Px(33.0),
183 ..default()
184 },
185 TextColor(Color::srgb(0.9, 0.9, 0.9)),
186 )]
187 )],
188 ))
189 .id();
190 commands.insert_resource(MenuData { button_entity });
191 }
192
193 pub fn setup_game(mut commands: Commands, asset_server: Res<AssetServer>) {
194 commands.spawn(Sprite::from_image(asset_server.load("branding/icon.png")));
195
196 commands.spawn_scene(bsn! {
197 Node {
198 margin: px(10),
199 }
200 Text("Move with arrow keys.\nPress SPACEBAR to pause.")
201 });
202 }
203
204 pub fn setup_paused_screen(mut commands: Commands) {
205 commands.spawn((
206 DespawnOnExit(IsPaused::Paused),
207 Node {
208 width: percent(100),
210 height: percent(100),
211 justify_content: JustifyContent::Center,
212 align_items: AlignItems::Center,
213 flex_direction: FlexDirection::Column,
214 row_gap: px(10),
215 ..default()
216 },
217 children![(
218 Node {
219 width: px(400),
220 height: px(400),
221 justify_content: JustifyContent::Center,
223 align_items: AlignItems::Center,
225 ..default()
226 },
227 BackgroundColor(NORMAL_BUTTON),
228 children![(
229 Text::new("Paused"),
230 TextFont {
231 font_size: FontSize::Px(33.0),
232 ..default()
233 },
234 TextColor(Color::srgb(0.9, 0.9, 0.9)),
235 )]
236 )],
237 ));
238 }
239}