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