Commit | Line | Data |
---|---|---|
6ba7aef1 | 1 | use boll::*; |
1f42d724 | 2 | use common::{Point, Dimension}; |
3f344b63 | 3 | use core::controller::ControllerManager; |
6566d7e5 | 4 | use core::render::Renderer; |
6ba7aef1 | 5 | use point; // defined in common, but loaded from main... |
95e3e10d | 6 | use rand::Rng; |
541aee90 | 7 | use sdl2::event::{Event, WindowEvent}; |
95e3e10d | 8 | use sdl2::keyboard::Keycode; |
6ba7aef1 | 9 | use sdl2::rect::Rect as SDLRect; |
6566d7e5 | 10 | use sdl2::video::SwapInterval; |
6ba7aef1 | 11 | use sdl2::{EventPump, VideoSubsystem}; |
3bfea951 | 12 | use sprites::SpriteManager; |
6ba7aef1 | 13 | use std::f32::consts::PI; |
902b2b31 | 14 | use time::{Duration, Instant, prelude::*}; |
95e3e10d | 15 | |
6ba7aef1 TW |
16 | const FPS: u32 = 60; |
17 | const NS_PER_FRAME: u32 = 1_000_000_000 / FPS; | |
18 | ||
6edafdc0 TW |
19 | #[derive(Default)] |
20 | pub struct AppBuilder { | |
1f42d724 | 21 | resolution: Dimension<u16>, |
6edafdc0 TW |
22 | state: Option<Box<dyn AppState>>, |
23 | title: Option<String>, | |
3bfea951 TW |
24 | } |
25 | ||
6edafdc0 TW |
26 | impl AppBuilder { |
27 | pub fn with_resolution(mut self, width: u16, height: u16) -> Self { | |
1f42d724 | 28 | self.resolution = Dimension { width, height }; |
6ba7aef1 | 29 | self |
6edafdc0 TW |
30 | } |
31 | ||
6edafdc0 | 32 | pub fn with_state(mut self, state: Box<dyn AppState>) -> Self { |
6ba7aef1 TW |
33 | self.state = Some(state); |
34 | self | |
6edafdc0 TW |
35 | } |
36 | ||
37 | pub fn with_title(mut self, title: &str) -> Self { | |
6ba7aef1 TW |
38 | self.title = Some(title.to_string()); |
39 | self | |
6edafdc0 TW |
40 | } |
41 | ||
fea68d56 | 42 | pub fn build(self) -> Result<App, String> { |
3bfea951 | 43 | let context = sdl2::init().unwrap(); |
fea68d56 | 44 | sdl2::image::init(sdl2::image::InitFlag::PNG)?; |
6ba7aef1 | 45 | let video = context.video()?; |
b0566120 | 46 | //self.print_video_display_modes(&video); |
fea68d56 TW |
47 | |
48 | let window = video | |
6ba7aef1 TW |
49 | .window( |
50 | &self.title.unwrap(), | |
51 | self.resolution.width.into(), | |
52 | self.resolution.height.into(), | |
53 | ) | |
3bfea951 | 54 | .position_centered() |
6ba7aef1 TW |
55 | // .fullscreen() |
56 | // .fullscreen_desktop() | |
3bfea951 | 57 | .opengl() |
6ba7aef1 TW |
58 | .build() |
59 | .unwrap(); | |
3bfea951 | 60 | context.mouse().show_cursor(false); |
6edafdc0 | 61 | |
6566d7e5 TW |
62 | let canvas = window.into_canvas().build().unwrap(); |
63 | let sprites = SpriteManager::new(canvas.texture_creator()); | |
64 | let screen = canvas.output_size().unwrap(); | |
65 | let renderer = Renderer::new(canvas); | |
6edafdc0 | 66 | |
6ba7aef1 | 67 | video.gl_set_swap_interval(SwapInterval::VSync)?; |
fea68d56 TW |
68 | |
69 | let event_pump = context.event_pump()?; | |
6edafdc0 | 70 | |
fea68d56 | 71 | Ok(App { |
6566d7e5 | 72 | renderer, |
3bfea951 TW |
73 | event_pump, |
74 | sprites, | |
eb253fcc | 75 | states: vec!(self.state.unwrap_or_else(|| Box::new(ActiveState::new(screen)))), |
bf7b5671 | 76 | ctrl_man: ControllerManager::new(context.joystick()?, context.haptic()?), |
fea68d56 TW |
77 | }) |
78 | } | |
79 | ||
b0566120 | 80 | #[allow(dead_code)] |
fea68d56 | 81 | fn print_video_display_modes(&self, video: &VideoSubsystem) { |
6ba7aef1 TW |
82 | println!("video subsystem: {:?}", video); |
83 | println!("current_video_driver: {:?}", video.current_video_driver()); | |
84 | for display in 0..video.num_video_displays().unwrap() { | |
85 | println!( | |
86 | "=== display {} - {} ===", | |
87 | display, | |
88 | video.display_name(display).unwrap() | |
89 | ); | |
90 | println!( | |
91 | " display_bounds: {:?}", | |
92 | video.display_bounds(display).unwrap() | |
93 | ); | |
94 | println!( | |
95 | " num_display_modes: {:?}", | |
96 | video.num_display_modes(display).unwrap() | |
97 | ); | |
98 | println!( | |
99 | " desktop_display_mode: {:?}", | |
100 | video.desktop_display_mode(display).unwrap() | |
101 | ); | |
1f362f49 | 102 | let current = video.current_display_mode(display).unwrap(); |
6ba7aef1 TW |
103 | println!( |
104 | " current_display_mode: {:?}", | |
1f362f49 | 105 | current |
6ba7aef1 | 106 | ); |
1f362f49 TW |
107 | for idx in 0..video.num_display_modes(display).unwrap() { |
108 | let mode = video.display_mode(display, idx).unwrap(); | |
6ba7aef1 | 109 | println!( |
1f362f49 TW |
110 | " {}{:2}: {:?}", |
111 | if mode == current { "*" } else { " " }, | |
112 | idx, | |
113 | mode | |
6ba7aef1 TW |
114 | ); |
115 | } | |
116 | } | |
117 | println!("swap interval: {:?}", video.gl_get_swap_interval()); | |
3bfea951 | 118 | } |
6edafdc0 TW |
119 | } |
120 | ||
121 | pub struct App { | |
6566d7e5 | 122 | renderer: Renderer, |
fca4e4f0 TW |
123 | event_pump: EventPump, |
124 | sprites: SpriteManager, | |
eb253fcc | 125 | states: Vec<Box<dyn AppState>>, |
b0566120 | 126 | pub ctrl_man: ControllerManager, |
6edafdc0 TW |
127 | } |
128 | ||
129 | impl App { | |
98995f2b | 130 | #[allow(clippy::new_ret_no_self)] |
6edafdc0 | 131 | pub fn new() -> AppBuilder { |
6ba7aef1 | 132 | Default::default() |
6edafdc0 | 133 | } |
3bfea951 | 134 | |
1e322944 | 135 | pub fn load_sprites(&mut self, sprites: &[(&str, &str)]) { |
3bfea951 TW |
136 | for (name, file) in sprites { |
137 | self.sprites.load(name, file); | |
138 | } | |
139 | } | |
6ba7aef1 TW |
140 | |
141 | pub fn start(&mut self) { | |
902b2b31 | 142 | let mut last_time = Instant::now(); |
6ba7aef1 | 143 | |
3af74c40 | 144 | self.states[0].enter(&self.ctrl_man); |
b0566120 | 145 | |
eb253fcc TW |
146 | loop { |
147 | if let Some(change) = self.handle_events() { | |
148 | self.handle_state_change(change); | |
541aee90 | 149 | } |
6ba7aef1 | 150 | |
902b2b31 TW |
151 | let duration = Instant::now() - last_time; |
152 | last_time = Instant::now(); | |
153 | ||
bf7b5671 | 154 | self.ctrl_man.update(duration); |
eb253fcc TW |
155 | |
156 | if let Some(state) = self.states.last_mut() { | |
157 | if let Some(change) = state.update(duration) { | |
158 | self.handle_state_change(change); | |
159 | } | |
160 | } else { | |
161 | break; | |
162 | } | |
541aee90 TW |
163 | |
164 | self.render(); | |
6ba7aef1 | 165 | } |
eb253fcc | 166 | } |
6ba7aef1 | 167 | |
eb253fcc TW |
168 | fn handle_state_change(&mut self, change: StateChange) { |
169 | match change { | |
170 | StateChange::Push(mut state) => { | |
171 | // if let Some(s) = self.states.last_mut() { | |
172 | // s.pause(); | |
173 | // } | |
174 | state.enter(&mut self.ctrl_man); | |
175 | self.states.push(state); | |
176 | } | |
177 | StateChange::Pop => { | |
178 | if let Some(mut s) = self.states.pop() { | |
179 | s.leave(); | |
180 | } | |
181 | } | |
182 | StateChange::Exit => { | |
183 | while let Some(mut s) = self.states.pop() { | |
184 | s.leave(); | |
185 | } | |
186 | } | |
187 | } | |
6ba7aef1 | 188 | } |
541aee90 | 189 | |
eb253fcc | 190 | fn handle_events(&mut self) -> Option<StateChange> { |
541aee90 | 191 | for event in self.event_pump.poll_iter() { |
3f344b63 | 192 | self.ctrl_man.handle_event(&event); |
541aee90 TW |
193 | match event { |
194 | Event::Quit { .. } | |
195 | | Event::KeyDown { | |
196 | keycode: Some(Keycode::Escape), | |
197 | .. | |
198 | } => { | |
eb253fcc | 199 | return Some(StateChange::Pop) |
541aee90 TW |
200 | } |
201 | Event::KeyDown { | |
202 | keycode: Some(Keycode::F11), | |
203 | .. | |
204 | } => { | |
6566d7e5 | 205 | self.renderer.toggle_fullscreen(); |
541aee90 TW |
206 | } |
207 | Event::Window { | |
208 | win_event: WindowEvent::Resized(x, y), | |
209 | .. | |
210 | } => { | |
211 | println!("window resized({}, {})", x, y) | |
212 | } | |
213 | Event::Window { | |
214 | win_event: WindowEvent::Maximized, | |
215 | .. | |
216 | } => { | |
217 | println!("window maximized") | |
218 | } | |
219 | Event::Window { | |
220 | win_event: WindowEvent::Restored, | |
221 | .. | |
222 | } => { | |
223 | println!("window restored") | |
224 | } | |
225 | Event::Window { | |
226 | win_event: WindowEvent::Enter, | |
227 | .. | |
228 | } => { | |
229 | println!("window enter") | |
230 | } | |
231 | Event::Window { | |
232 | win_event: WindowEvent::Leave, | |
233 | .. | |
234 | } => { | |
235 | println!("window leave") | |
236 | } | |
237 | Event::Window { | |
238 | win_event: WindowEvent::FocusGained, | |
239 | .. | |
240 | } => { | |
241 | println!("window focus gained") | |
242 | } | |
243 | Event::Window { | |
244 | win_event: WindowEvent::FocusLost, | |
245 | .. | |
246 | } => { | |
247 | println!("window focus lost") | |
248 | } | |
eb253fcc TW |
249 | _ => { |
250 | if let Some(state) = self.states.last_mut() { | |
40c949e5 TW |
251 | if let Some(change) = state.handle_event(event) { |
252 | return Some(change); | |
253 | } | |
eb253fcc TW |
254 | } else { |
255 | return Some(StateChange::Exit) | |
256 | } | |
257 | }, | |
541aee90 TW |
258 | } |
259 | } | |
eb253fcc | 260 | None |
541aee90 TW |
261 | } |
262 | ||
263 | fn render(&mut self) { | |
6566d7e5 | 264 | self.renderer.clear(); |
eb253fcc | 265 | self.states.last_mut().unwrap().render(&mut self.renderer, &mut self.sprites); |
6566d7e5 | 266 | self.renderer.present(); |
541aee90 | 267 | } |
3bfea951 | 268 | } |
95e3e10d | 269 | |
eb253fcc TW |
270 | pub enum StateChange { |
271 | Push(Box<dyn AppState>), | |
272 | Pop, | |
273 | Exit, | |
274 | } | |
275 | ||
95e3e10d | 276 | pub trait AppState { |
a82a4d23 | 277 | fn enter(&mut self, ctrl_man: &ControllerManager); |
b0566120 | 278 | fn leave(&mut self); |
eb253fcc | 279 | fn update(&mut self, dt: Duration) -> Option<StateChange>; |
6566d7e5 | 280 | fn render(&mut self, renderer: &mut Renderer, sprites: &SpriteManager); |
eb253fcc | 281 | fn handle_event(&mut self, event: Event) -> Option<StateChange>; |
95e3e10d TW |
282 | } |
283 | ||
284 | type Bollar = Vec<Box<dyn Boll>>; | |
285 | ||
541aee90 | 286 | #[derive(Default)] |
95e3e10d | 287 | pub struct ActiveState { |
1f42d724 | 288 | screen: Dimension<u32>, |
95e3e10d TW |
289 | bolls: Bollar, |
290 | boll_size: u32, | |
541aee90 | 291 | mario_angle: f64, |
95e3e10d TW |
292 | } |
293 | ||
294 | impl ActiveState { | |
77034de9 | 295 | pub fn new(screen: (u32, u32)) -> ActiveState { |
95e3e10d TW |
296 | ActiveState { |
297 | bolls: Bollar::new(), | |
298 | boll_size: 1, | |
1f42d724 | 299 | screen: Dimension::from(screen), |
541aee90 | 300 | ..Default::default() |
95e3e10d TW |
301 | } |
302 | } | |
303 | ||
304 | fn change_boll_count(&mut self, delta: i32) { | |
98995f2b | 305 | #[allow(clippy::comparison_chain)] |
95e3e10d TW |
306 | if delta > 0 { |
307 | for _i in 0..delta { | |
308 | self.add_boll(); | |
309 | } | |
310 | } else if delta < 0 { | |
5cbbebbe | 311 | for _i in 0..(-delta) { |
95e3e10d TW |
312 | self.bolls.pop(); |
313 | } | |
314 | } | |
315 | } | |
316 | ||
317 | fn add_boll(&mut self) { | |
318 | let mut rng = rand::thread_rng(); | |
319 | self.bolls.push(Box::new(SquareBoll { | |
6ba7aef1 | 320 | pos: point!( |
77034de9 TW |
321 | rng.gen_range(0, self.screen.width) as f64, |
322 | rng.gen_range(0, self.screen.height) as f64 | |
6ba7aef1 | 323 | ), |
95e3e10d TW |
324 | vel: point!(rng.gen_range(-2.0, 2.0), rng.gen_range(-2.0, 2.0)), |
325 | })); | |
326 | } | |
327 | } | |
328 | ||
329 | impl AppState for ActiveState { | |
a82a4d23 | 330 | fn enter(&mut self, _ctrl_man: &ControllerManager) {} |
b0566120 | 331 | |
eb253fcc | 332 | fn update(&mut self, dt: Duration) -> Option<StateChange> { |
93679b27 | 333 | for b in &mut self.bolls { |
95e3e10d TW |
334 | b.update(); |
335 | } | |
336 | ||
337 | match dt { | |
902b2b31 TW |
338 | ns if ns < (NS_PER_FRAME - 90_0000).nanoseconds() => self.change_boll_count(100), |
339 | ns if ns > (NS_PER_FRAME + 90_0000).nanoseconds() => self.change_boll_count(-100), | |
95e3e10d TW |
340 | _ => {} |
341 | } | |
eb253fcc TW |
342 | |
343 | None | |
95e3e10d TW |
344 | } |
345 | ||
6566d7e5 | 346 | fn render(&mut self, renderer: &mut Renderer, sprites: &SpriteManager) { |
541aee90 TW |
347 | /* draw square of blocks */ { |
348 | let blocks = 20; | |
349 | let size = 32; | |
350 | let offset = point!( | |
351 | (self.screen.width as i32 - (blocks + 1) * size) / 2, | |
352 | (self.screen.height as i32 - (blocks + 1) * size) / 2 | |
353 | ); | |
354 | let block = sprites.get("block"); | |
355 | for i in 0..blocks { | |
6566d7e5 TW |
356 | renderer |
357 | .blit( | |
541aee90 TW |
358 | block, |
359 | None, | |
360 | SDLRect::new((i) * size + offset.x, offset.y, size as u32, size as u32), | |
6566d7e5 TW |
361 | ); |
362 | renderer | |
363 | .blit( | |
541aee90 TW |
364 | block, |
365 | None, | |
366 | SDLRect::new( | |
367 | (blocks - i) * size + offset.x, | |
368 | (blocks) * size + offset.y, | |
369 | size as u32, | |
370 | size as u32, | |
371 | ), | |
6566d7e5 TW |
372 | ); |
373 | renderer | |
374 | .blit( | |
541aee90 TW |
375 | block, |
376 | None, | |
377 | SDLRect::new( | |
378 | offset.x, | |
379 | (blocks - i) * size + offset.y, | |
380 | size as u32, | |
381 | size as u32, | |
382 | ), | |
6566d7e5 TW |
383 | ); |
384 | renderer | |
385 | .blit( | |
541aee90 TW |
386 | block, |
387 | None, | |
388 | SDLRect::new( | |
389 | (blocks) * size + offset.x, | |
390 | (i) * size + offset.y, | |
391 | size as u32, | |
392 | size as u32, | |
393 | ), | |
6566d7e5 | 394 | ); |
541aee90 TW |
395 | } |
396 | } | |
397 | ||
398 | /* draw mario */ { | |
399 | let size = 64; | |
400 | let offset = point!( | |
401 | (self.screen.width as i32 - size) / 2, | |
402 | (self.screen.height as i32 - size) / 2 | |
403 | ); | |
404 | let radius = 110.0 + size as f32 * 0.5; | |
405 | let angle = (self.mario_angle as f32 - 90.0) * PI / 180.0; | |
406 | let offset2 = point!((angle.cos() * radius) as i32, (angle.sin() * radius) as i32); | |
6566d7e5 TW |
407 | renderer |
408 | .blit_ex( | |
541aee90 TW |
409 | sprites.get("mario"), |
410 | None, | |
411 | SDLRect::new( | |
412 | offset.x + offset2.x, | |
413 | offset.y + offset2.y, | |
414 | size as u32, | |
415 | size as u32, | |
416 | ), | |
417 | self.mario_angle, | |
418 | sdl2::rect::Point::new(size / 2, size / 2), | |
419 | false, | |
420 | false, | |
6566d7e5 | 421 | ); |
541aee90 TW |
422 | self.mario_angle = (self.mario_angle + 1.0) % 360.0; |
423 | } | |
424 | ||
425 | /* draw circles and ellipses*/ { | |
426 | let p = point!((self.screen.width / 2) as i16, (self.screen.height / 2) as i16); | |
6566d7e5 TW |
427 | renderer.circle(p, 100, (255, 255, 255)); |
428 | renderer.circle(p, 110, (255, 255, 255)); | |
429 | renderer.ellipse(p, (50, 100), (255, 255, 255)); | |
430 | renderer.ellipse(p, (110, 55), (255, 255, 255)); | |
541aee90 TW |
431 | } |
432 | ||
93679b27 | 433 | for b in &self.bolls { |
6566d7e5 | 434 | b.draw(renderer, self.boll_size); |
95e3e10d TW |
435 | } |
436 | } | |
437 | ||
b0566120 | 438 | fn leave(&mut self) { |
95e3e10d TW |
439 | println!("number of bolls: {}", self.bolls.len()); |
440 | } | |
441 | ||
eb253fcc | 442 | fn handle_event(&mut self, event: Event) -> Option<StateChange> { |
95e3e10d | 443 | match event { |
6ba7aef1 TW |
444 | Event::KeyDown { |
445 | keycode: Some(Keycode::KpPlus), | |
446 | .. | |
447 | } => self.boll_size = std::cmp::min(self.boll_size + 1, 32), | |
448 | Event::KeyDown { | |
449 | keycode: Some(Keycode::KpMinus), | |
450 | .. | |
451 | } => self.boll_size = std::cmp::max(self.boll_size - 1, 1), | |
452 | Event::MouseMotion { x, y, .. } => self.bolls.push(Box::new(CircleBoll::new( | |
453 | point!(x as f64, y as f64), | |
454 | point!(0.0, 0.0), | |
455 | ))), | |
95e3e10d TW |
456 | _ => {} |
457 | } | |
eb253fcc | 458 | None |
95e3e10d TW |
459 | } |
460 | } |