1 | #include <csignal> |
2 | #include <SDL2/SDL_image.h> |
3 | #include <SDL2/SDL_ttf.h> |
4 | #include "Sound_Queue.h" |
5 | #include "apu.hpp" |
6 | #include "cartridge.hpp" |
7 | #include "cpu.hpp" |
8 | #include "menu.hpp" |
9 | #include "gui.hpp" |
10 | #include "config.hpp" |
11 | |
12 | namespace GUI { |
13 | |
14 | // SDL structures: |
15 | SDL_Window* window; |
16 | SDL_Renderer* renderer; |
17 | SDL_Texture* gameTexture; |
18 | SDL_Texture* background; |
19 | TTF_Font* font; |
20 | u8 const* keys; |
21 | Sound_Queue* soundQueue; |
22 | SDL_Joystick* joystick[] = { nullptr, nullptr }; |
23 | |
24 | // Menus: |
25 | Menu* ; |
26 | Menu* mainMenu; |
27 | Menu* ; |
28 | Menu* ; |
29 | Menu* [2]; |
30 | Menu* [2]; |
31 | FileMenu* ; |
32 | |
33 | bool pause = true; |
34 | |
35 | /* Set the window size multiplier */ |
36 | void set_size(int mul) |
37 | { |
38 | last_window_size = mul; |
39 | SDL_SetWindowSize(window, WIDTH * mul, HEIGHT * mul); |
40 | SDL_SetWindowPosition(window, SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED); |
41 | } |
42 | |
43 | /* Initialize GUI */ |
44 | void init() |
45 | { |
46 | // Initialize graphics system: |
47 | SDL_Init(SDL_INIT_VIDEO | SDL_INIT_AUDIO | SDL_INIT_JOYSTICK); |
48 | SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, "linear" ); |
49 | TTF_Init(); |
50 | |
51 | for (int i = 0; i < SDL_NumJoysticks(); i++) |
52 | joystick[i] = SDL_JoystickOpen(i); |
53 | |
54 | APU::init(); |
55 | soundQueue = new Sound_Queue; |
56 | soundQueue->init(96000); |
57 | |
58 | // Initialize graphics structures: |
59 | window = SDL_CreateWindow ("LaiNES" , |
60 | SDL_WINDOWPOS_CENTERED, SDL_WINDOWPOS_CENTERED, |
61 | WIDTH * last_window_size, HEIGHT * last_window_size, 0); |
62 | |
63 | renderer = SDL_CreateRenderer(window, -1, |
64 | SDL_RENDERER_ACCELERATED | SDL_RENDERER_PRESENTVSYNC); |
65 | SDL_RenderSetLogicalSize(renderer, WIDTH, HEIGHT); |
66 | |
67 | gameTexture = SDL_CreateTexture (renderer, |
68 | SDL_PIXELFORMAT_ARGB8888, SDL_TEXTUREACCESS_STREAMING, |
69 | WIDTH, HEIGHT); |
70 | |
71 | font = TTF_OpenFont("res/font.ttf" , FONT_SZ); |
72 | keys = SDL_GetKeyboardState(0); |
73 | |
74 | // Initial background: |
75 | SDL_Surface* backSurface = IMG_Load("res/init.png" ); |
76 | background = SDL_CreateTextureFromSurface(renderer, backSurface); |
77 | SDL_SetTextureColorMod(background, 60, 60, 60); |
78 | SDL_FreeSurface(backSurface); |
79 | |
80 | // Menus: |
81 | mainMenu = new Menu; |
82 | mainMenu->add(new Entry("Load ROM" , []{ menu = fileMenu; })); |
83 | mainMenu->add(new Entry("Settings" , []{ menu = settingsMenu; })); |
84 | mainMenu->add(new Entry("Exit" , []{ exit(0); })); |
85 | |
86 | settingsMenu = new Menu; |
87 | settingsMenu->add(new Entry("<" , []{ menu = mainMenu; })); |
88 | settingsMenu->add(new Entry("Video" , []{ menu = videoMenu; })); |
89 | settingsMenu->add(new Entry("Controller 1" , []{ menu = useJoystick[0] ? joystickMenu[0] : keyboardMenu[0]; })); |
90 | settingsMenu->add(new Entry("Controller 2" , []{ menu = useJoystick[1] ? joystickMenu[1] : keyboardMenu[1]; })); |
91 | settingsMenu->add(new Entry("Save Settings" , []{ save_settings(); menu = mainMenu; })); |
92 | |
93 | videoMenu = new Menu; |
94 | videoMenu->add(new Entry("<" , []{ menu = settingsMenu; })); |
95 | videoMenu->add(new Entry("Size 1x" , []{ set_size(1); })); |
96 | videoMenu->add(new Entry("Size 2x" , []{ set_size(2); })); |
97 | videoMenu->add(new Entry("Size 3x" , []{ set_size(3); })); |
98 | videoMenu->add(new Entry("Size 4x" , []{ set_size(4); })); |
99 | |
100 | for (int i = 0; i < 2; i++) |
101 | { |
102 | keyboardMenu[i] = new Menu; |
103 | keyboardMenu[i]->add(new Entry("<" , []{ menu = settingsMenu; })); |
104 | if (joystick[i] != nullptr) |
105 | keyboardMenu[i]->add(new Entry("Joystick >" , [=]{ menu = joystickMenu[i]; useJoystick[i] = true; })); |
106 | keyboardMenu[i]->add(new ControlEntry("Up" , &KEY_UP[i])); |
107 | keyboardMenu[i]->add(new ControlEntry("Down" , &KEY_DOWN[i])); |
108 | keyboardMenu[i]->add(new ControlEntry("Left" , &KEY_LEFT[i])); |
109 | keyboardMenu[i]->add(new ControlEntry("Right" , &KEY_RIGHT[i])); |
110 | keyboardMenu[i]->add(new ControlEntry("A" , &KEY_A[i])); |
111 | keyboardMenu[i]->add(new ControlEntry("B" , &KEY_B[i])); |
112 | keyboardMenu[i]->add(new ControlEntry("Start" , &KEY_START[i])); |
113 | keyboardMenu[i]->add(new ControlEntry("Select" , &KEY_SELECT[i])); |
114 | |
115 | if (joystick[i] != nullptr) |
116 | { |
117 | joystickMenu[i] = new Menu; |
118 | joystickMenu[i]->add(new Entry("<" , []{ menu = settingsMenu; })); |
119 | joystickMenu[i]->add(new Entry("< Keyboard" , [=]{ menu = keyboardMenu[i]; useJoystick[i] = false; })); |
120 | joystickMenu[i]->add(new ControlEntry("Up" , &BTN_UP[i])); |
121 | joystickMenu[i]->add(new ControlEntry("Down" , &BTN_DOWN[i])); |
122 | joystickMenu[i]->add(new ControlEntry("Left" , &BTN_LEFT[i])); |
123 | joystickMenu[i]->add(new ControlEntry("Right" , &BTN_RIGHT[i])); |
124 | joystickMenu[i]->add(new ControlEntry("A" , &BTN_A[i])); |
125 | joystickMenu[i]->add(new ControlEntry("B" , &BTN_B[i])); |
126 | joystickMenu[i]->add(new ControlEntry("Start" , &BTN_START[i])); |
127 | joystickMenu[i]->add(new ControlEntry("Select" , &BTN_SELECT[i])); |
128 | } |
129 | } |
130 | |
131 | fileMenu = new FileMenu; |
132 | |
133 | menu = mainMenu; |
134 | } |
135 | |
136 | /* Render a texture on screen */ |
137 | void render_texture(SDL_Texture* texture, int x, int y) |
138 | { |
139 | int w, h; |
140 | SDL_Rect dest; |
141 | |
142 | SDL_QueryTexture(texture, NULL, NULL, &dest.w, &dest.h); |
143 | if (x == TEXT_CENTER) |
144 | dest.x = WIDTH/2 - dest.w/2; |
145 | else if (x == TEXT_RIGHT) |
146 | dest.x = WIDTH - dest.w - 10; |
147 | else |
148 | dest.x = x + 10; |
149 | dest.y = y + 5; |
150 | |
151 | SDL_RenderCopy(renderer, texture, NULL, &dest); |
152 | } |
153 | |
154 | /* Generate a texture from text */ |
155 | SDL_Texture* gen_text(std::string text, SDL_Color color) |
156 | { |
157 | SDL_Surface* surface = TTF_RenderText_Blended(font, text.c_str(), color); |
158 | SDL_Texture* texture = SDL_CreateTextureFromSurface(renderer, surface); |
159 | |
160 | SDL_FreeSurface(surface); |
161 | return texture; |
162 | } |
163 | |
164 | /* Get the joypad state from SDL */ |
165 | u8 get_joypad_state(int n) |
166 | { |
167 | const int DEAD_ZONE = 8000; |
168 | |
169 | u8 j = 0; |
170 | if (useJoystick[n]) |
171 | { |
172 | j |= (SDL_JoystickGetButton(joystick[n], BTN_A[n])) << 0; // A. |
173 | j |= (SDL_JoystickGetButton(joystick[n], BTN_B[n])) << 1; // B. |
174 | j |= (SDL_JoystickGetButton(joystick[n], BTN_SELECT[n])) << 2; // Select. |
175 | j |= (SDL_JoystickGetButton(joystick[n], BTN_START[n])) << 3; // Start. |
176 | |
177 | j |= (SDL_JoystickGetButton(joystick[n], BTN_UP[n])) << 4; // Up. |
178 | j |= (SDL_JoystickGetAxis(joystick[n], 1) < -DEAD_ZONE) << 4; |
179 | j |= (SDL_JoystickGetButton(joystick[n], BTN_DOWN[n])) << 5; // Down. |
180 | j |= (SDL_JoystickGetAxis(joystick[n], 1) > DEAD_ZONE) << 5; |
181 | j |= (SDL_JoystickGetButton(joystick[n], BTN_LEFT[n])) << 6; // Left. |
182 | j |= (SDL_JoystickGetAxis(joystick[n], 0) < -DEAD_ZONE) << 6; |
183 | j |= (SDL_JoystickGetButton(joystick[n], BTN_RIGHT[n])) << 7; // Right. |
184 | j |= (SDL_JoystickGetAxis(joystick[n], 0) > DEAD_ZONE) << 7; |
185 | } |
186 | else |
187 | { |
188 | j |= (keys[KEY_A[n]]) << 0; |
189 | j |= (keys[KEY_B[n]]) << 1; |
190 | j |= (keys[KEY_SELECT[n]]) << 2; |
191 | j |= (keys[KEY_START[n]]) << 3; |
192 | j |= (keys[KEY_UP[n]]) << 4; |
193 | j |= (keys[KEY_DOWN[n]]) << 5; |
194 | j |= (keys[KEY_LEFT[n]]) << 6; |
195 | j |= (keys[KEY_RIGHT[n]]) << 7; |
196 | } |
197 | return j; |
198 | } |
199 | |
200 | /* Send the rendered frame to the GUI */ |
201 | void new_frame(u32* pixels) |
202 | { |
203 | SDL_UpdateTexture(gameTexture, NULL, pixels, WIDTH * sizeof(u32)); |
204 | } |
205 | |
206 | void new_samples(const blip_sample_t* samples, size_t count) |
207 | { |
208 | soundQueue->write(samples, count); |
209 | } |
210 | |
211 | /* Render the screen */ |
212 | void render() |
213 | { |
214 | SDL_RenderClear(renderer); |
215 | |
216 | // Draw the NES screen: |
217 | if (Cartridge::loaded()) |
218 | SDL_RenderCopy(renderer, gameTexture, NULL, NULL); |
219 | else |
220 | SDL_RenderCopy(renderer, background, NULL, NULL); |
221 | |
222 | // Draw the menu: |
223 | if (pause) menu->render(); |
224 | |
225 | SDL_RenderPresent(renderer); |
226 | } |
227 | |
228 | /* Play/stop the game */ |
229 | void toggle_pause() |
230 | { |
231 | pause = not pause; |
232 | menu = mainMenu; |
233 | |
234 | if (pause) |
235 | SDL_SetTextureColorMod(gameTexture, 60, 60, 60); |
236 | else |
237 | SDL_SetTextureColorMod(gameTexture, 255, 255, 255); |
238 | } |
239 | |
240 | /* Prompt for a key, return the scancode */ |
241 | SDL_Scancode query_key() |
242 | { |
243 | SDL_Texture* prompt = gen_text("Press a key..." , { 255, 255, 255 }); |
244 | render_texture(prompt, TEXT_CENTER, HEIGHT - FONT_SZ*4); |
245 | SDL_RenderPresent(renderer); |
246 | |
247 | SDL_Event e; |
248 | while (true) |
249 | { |
250 | SDL_PollEvent(&e); |
251 | if (e.type == SDL_KEYDOWN) |
252 | return e.key.keysym.scancode; |
253 | } |
254 | } |
255 | |
256 | int query_button() |
257 | { |
258 | SDL_Texture* prompt = gen_text("Press a button..." , { 255, 255, 255 }); |
259 | render_texture(prompt, TEXT_CENTER, HEIGHT - FONT_SZ*4); |
260 | SDL_RenderPresent(renderer); |
261 | |
262 | SDL_Event e; |
263 | while (true) |
264 | { |
265 | SDL_PollEvent(&e); |
266 | if (e.type == SDL_JOYBUTTONDOWN) |
267 | return e.jbutton.button; |
268 | } |
269 | } |
270 | |
271 | /* Run the emulator */ |
272 | void run() |
273 | { |
274 | SDL_Event e; |
275 | |
276 | // Framerate control: |
277 | u32 frameStart, frameTime; |
278 | const int FPS = 60; |
279 | const int DELAY = 1000.0f / FPS; |
280 | |
281 | while (true) |
282 | { |
283 | frameStart = SDL_GetTicks(); |
284 | |
285 | // Handle events: |
286 | while (SDL_PollEvent(&e)) |
287 | switch (e.type) |
288 | { |
289 | case SDL_QUIT: return; |
290 | case SDL_KEYDOWN: |
291 | if (keys[SDL_SCANCODE_ESCAPE] and Cartridge::loaded()) |
292 | toggle_pause(); |
293 | else if (pause) |
294 | menu->update(keys); |
295 | } |
296 | |
297 | if (not pause) CPU::run_frame(); |
298 | render(); |
299 | |
300 | // Wait to mantain framerate: |
301 | frameTime = SDL_GetTicks() - frameStart; |
302 | if (frameTime < DELAY) |
303 | SDL_Delay((int)(DELAY - frameTime)); |
304 | } |
305 | } |
306 | |
307 | |
308 | } |
309 | |