Commit | Line | Data |
---|---|---|
3f344b63 | 1 | use core::controller::ControllerManager; |
6566d7e5 | 2 | use core::render::Renderer; |
b1002380 | 3 | use geometry::{Dimension}; |
541aee90 | 4 | use sdl2::event::{Event, WindowEvent}; |
95e3e10d | 5 | use sdl2::keyboard::Keycode; |
6566d7e5 | 6 | use sdl2::video::SwapInterval; |
6ba7aef1 | 7 | use sdl2::{EventPump, VideoSubsystem}; |
3bfea951 | 8 | use sprites::SpriteManager; |
b1002380 | 9 | use time::{Duration, Instant}; |
6ba7aef1 | 10 | |
6edafdc0 TW |
11 | #[derive(Default)] |
12 | pub struct AppBuilder { | |
1f42d724 | 13 | resolution: Dimension<u16>, |
6edafdc0 TW |
14 | state: Option<Box<dyn AppState>>, |
15 | title: Option<String>, | |
3bfea951 TW |
16 | } |
17 | ||
6edafdc0 TW |
18 | impl AppBuilder { |
19 | pub fn with_resolution(mut self, width: u16, height: u16) -> Self { | |
1f42d724 | 20 | self.resolution = Dimension { width, height }; |
6ba7aef1 | 21 | self |
6edafdc0 TW |
22 | } |
23 | ||
6edafdc0 | 24 | pub fn with_state(mut self, state: Box<dyn AppState>) -> Self { |
6ba7aef1 TW |
25 | self.state = Some(state); |
26 | self | |
6edafdc0 TW |
27 | } |
28 | ||
29 | pub fn with_title(mut self, title: &str) -> Self { | |
6ba7aef1 TW |
30 | self.title = Some(title.to_string()); |
31 | self | |
6edafdc0 TW |
32 | } |
33 | ||
fea68d56 | 34 | pub fn build(self) -> Result<App, String> { |
3bfea951 | 35 | let context = sdl2::init().unwrap(); |
fea68d56 | 36 | sdl2::image::init(sdl2::image::InitFlag::PNG)?; |
6ba7aef1 | 37 | let video = context.video()?; |
b0566120 | 38 | //self.print_video_display_modes(&video); |
fea68d56 TW |
39 | |
40 | let window = video | |
6ba7aef1 TW |
41 | .window( |
42 | &self.title.unwrap(), | |
43 | self.resolution.width.into(), | |
44 | self.resolution.height.into(), | |
45 | ) | |
3bfea951 | 46 | .position_centered() |
6ba7aef1 TW |
47 | // .fullscreen() |
48 | // .fullscreen_desktop() | |
3bfea951 | 49 | .opengl() |
6ba7aef1 TW |
50 | .build() |
51 | .unwrap(); | |
3bfea951 | 52 | context.mouse().show_cursor(false); |
6edafdc0 | 53 | |
6566d7e5 TW |
54 | let canvas = window.into_canvas().build().unwrap(); |
55 | let sprites = SpriteManager::new(canvas.texture_creator()); | |
6566d7e5 | 56 | let renderer = Renderer::new(canvas); |
6edafdc0 | 57 | |
6ba7aef1 | 58 | video.gl_set_swap_interval(SwapInterval::VSync)?; |
fea68d56 TW |
59 | |
60 | let event_pump = context.event_pump()?; | |
6edafdc0 | 61 | |
fea68d56 | 62 | Ok(App { |
6566d7e5 | 63 | renderer, |
3bfea951 TW |
64 | event_pump, |
65 | sprites, | |
b1002380 | 66 | states: vec!(self.state.unwrap()), |
09cd68fe | 67 | ctrl_man: ControllerManager::new(context.game_controller()?, context.haptic()?), |
fea68d56 TW |
68 | }) |
69 | } | |
70 | ||
b0566120 | 71 | #[allow(dead_code)] |
fea68d56 | 72 | fn print_video_display_modes(&self, video: &VideoSubsystem) { |
6ba7aef1 TW |
73 | println!("video subsystem: {:?}", video); |
74 | println!("current_video_driver: {:?}", video.current_video_driver()); | |
75 | for display in 0..video.num_video_displays().unwrap() { | |
76 | println!( | |
77 | "=== display {} - {} ===", | |
78 | display, | |
79 | video.display_name(display).unwrap() | |
80 | ); | |
81 | println!( | |
82 | " display_bounds: {:?}", | |
83 | video.display_bounds(display).unwrap() | |
84 | ); | |
85 | println!( | |
86 | " num_display_modes: {:?}", | |
87 | video.num_display_modes(display).unwrap() | |
88 | ); | |
89 | println!( | |
90 | " desktop_display_mode: {:?}", | |
91 | video.desktop_display_mode(display).unwrap() | |
92 | ); | |
1f362f49 | 93 | let current = video.current_display_mode(display).unwrap(); |
6ba7aef1 TW |
94 | println!( |
95 | " current_display_mode: {:?}", | |
1f362f49 | 96 | current |
6ba7aef1 | 97 | ); |
1f362f49 TW |
98 | for idx in 0..video.num_display_modes(display).unwrap() { |
99 | let mode = video.display_mode(display, idx).unwrap(); | |
6ba7aef1 | 100 | println!( |
1f362f49 TW |
101 | " {}{:2}: {:?}", |
102 | if mode == current { "*" } else { " " }, | |
103 | idx, | |
104 | mode | |
6ba7aef1 TW |
105 | ); |
106 | } | |
107 | } | |
108 | println!("swap interval: {:?}", video.gl_get_swap_interval()); | |
3bfea951 | 109 | } |
6edafdc0 TW |
110 | } |
111 | ||
112 | pub struct App { | |
6566d7e5 | 113 | renderer: Renderer, |
fca4e4f0 TW |
114 | event_pump: EventPump, |
115 | sprites: SpriteManager, | |
eb253fcc | 116 | states: Vec<Box<dyn AppState>>, |
b0566120 | 117 | pub ctrl_man: ControllerManager, |
6edafdc0 TW |
118 | } |
119 | ||
120 | impl App { | |
98995f2b | 121 | #[allow(clippy::new_ret_no_self)] |
6edafdc0 | 122 | pub fn new() -> AppBuilder { |
6ba7aef1 | 123 | Default::default() |
6edafdc0 | 124 | } |
3bfea951 | 125 | |
1e322944 | 126 | pub fn load_sprites(&mut self, sprites: &[(&str, &str)]) { |
3bfea951 TW |
127 | for (name, file) in sprites { |
128 | self.sprites.load(name, file); | |
129 | } | |
130 | } | |
6ba7aef1 TW |
131 | |
132 | pub fn start(&mut self) { | |
902b2b31 | 133 | let mut last_time = Instant::now(); |
6ba7aef1 | 134 | |
3af74c40 | 135 | self.states[0].enter(&self.ctrl_man); |
b0566120 | 136 | |
eb253fcc TW |
137 | loop { |
138 | if let Some(change) = self.handle_events() { | |
139 | self.handle_state_change(change); | |
541aee90 | 140 | } |
6ba7aef1 | 141 | |
902b2b31 TW |
142 | let duration = Instant::now() - last_time; |
143 | last_time = Instant::now(); | |
144 | ||
bf7b5671 | 145 | self.ctrl_man.update(duration); |
eb253fcc TW |
146 | |
147 | if let Some(state) = self.states.last_mut() { | |
148 | if let Some(change) = state.update(duration) { | |
149 | self.handle_state_change(change); | |
150 | } | |
151 | } else { | |
152 | break; | |
153 | } | |
541aee90 TW |
154 | |
155 | self.render(); | |
6ba7aef1 | 156 | } |
eb253fcc | 157 | } |
6ba7aef1 | 158 | |
eb253fcc TW |
159 | fn handle_state_change(&mut self, change: StateChange) { |
160 | match change { | |
161 | StateChange::Push(mut state) => { | |
162 | // if let Some(s) = self.states.last_mut() { | |
163 | // s.pause(); | |
164 | // } | |
0c56b1f7 | 165 | state.enter(&self.ctrl_man); |
eb253fcc TW |
166 | self.states.push(state); |
167 | } | |
168 | StateChange::Pop => { | |
169 | if let Some(mut s) = self.states.pop() { | |
170 | s.leave(); | |
171 | } | |
172 | } | |
173 | StateChange::Exit => { | |
174 | while let Some(mut s) = self.states.pop() { | |
175 | s.leave(); | |
176 | } | |
177 | } | |
178 | } | |
6ba7aef1 | 179 | } |
541aee90 | 180 | |
eb253fcc | 181 | fn handle_events(&mut self) -> Option<StateChange> { |
541aee90 | 182 | for event in self.event_pump.poll_iter() { |
3f344b63 | 183 | self.ctrl_man.handle_event(&event); |
541aee90 TW |
184 | match event { |
185 | Event::Quit { .. } | |
186 | | Event::KeyDown { | |
187 | keycode: Some(Keycode::Escape), | |
188 | .. | |
189 | } => { | |
eb253fcc | 190 | return Some(StateChange::Pop) |
541aee90 TW |
191 | } |
192 | Event::KeyDown { | |
193 | keycode: Some(Keycode::F11), | |
194 | .. | |
195 | } => { | |
6566d7e5 | 196 | self.renderer.toggle_fullscreen(); |
541aee90 TW |
197 | } |
198 | Event::Window { | |
199 | win_event: WindowEvent::Resized(x, y), | |
200 | .. | |
201 | } => { | |
202 | println!("window resized({}, {})", x, y) | |
203 | } | |
204 | Event::Window { | |
205 | win_event: WindowEvent::Maximized, | |
206 | .. | |
207 | } => { | |
208 | println!("window maximized") | |
209 | } | |
210 | Event::Window { | |
211 | win_event: WindowEvent::Restored, | |
212 | .. | |
213 | } => { | |
214 | println!("window restored") | |
215 | } | |
216 | Event::Window { | |
217 | win_event: WindowEvent::Enter, | |
218 | .. | |
219 | } => { | |
220 | println!("window enter") | |
221 | } | |
222 | Event::Window { | |
223 | win_event: WindowEvent::Leave, | |
224 | .. | |
225 | } => { | |
226 | println!("window leave") | |
227 | } | |
228 | Event::Window { | |
229 | win_event: WindowEvent::FocusGained, | |
230 | .. | |
231 | } => { | |
232 | println!("window focus gained") | |
233 | } | |
234 | Event::Window { | |
235 | win_event: WindowEvent::FocusLost, | |
236 | .. | |
237 | } => { | |
238 | println!("window focus lost") | |
239 | } | |
eb253fcc TW |
240 | _ => { |
241 | if let Some(state) = self.states.last_mut() { | |
40c949e5 TW |
242 | if let Some(change) = state.handle_event(event) { |
243 | return Some(change); | |
244 | } | |
eb253fcc TW |
245 | } else { |
246 | return Some(StateChange::Exit) | |
247 | } | |
248 | }, | |
541aee90 TW |
249 | } |
250 | } | |
eb253fcc | 251 | None |
541aee90 TW |
252 | } |
253 | ||
254 | fn render(&mut self) { | |
6566d7e5 | 255 | self.renderer.clear(); |
0c56b1f7 | 256 | self.states.last_mut().unwrap().render(&mut self.renderer, &self.sprites); |
6566d7e5 | 257 | self.renderer.present(); |
541aee90 | 258 | } |
3bfea951 | 259 | } |
95e3e10d | 260 | |
eb253fcc TW |
261 | pub enum StateChange { |
262 | Push(Box<dyn AppState>), | |
263 | Pop, | |
264 | Exit, | |
265 | } | |
266 | ||
95e3e10d | 267 | pub trait AppState { |
a82a4d23 | 268 | fn enter(&mut self, ctrl_man: &ControllerManager); |
b0566120 | 269 | fn leave(&mut self); |
eb253fcc | 270 | fn update(&mut self, dt: Duration) -> Option<StateChange>; |
6566d7e5 | 271 | fn render(&mut self, renderer: &mut Renderer, sprites: &SpriteManager); |
eb253fcc | 272 | fn handle_event(&mut self, event: Event) -> Option<StateChange>; |
95e3e10d | 273 | } |