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