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
12namespace GUI {
13
14// SDL structures:
15SDL_Window* window;
16SDL_Renderer* renderer;
17SDL_Texture* gameTexture;
18SDL_Texture* background;
19TTF_Font* font;
20u8 const* keys;
21Sound_Queue* soundQueue;
22SDL_Joystick* joystick[] = { nullptr, nullptr };
23
24// Menus:
25Menu* menu;
26Menu* mainMenu;
27Menu* settingsMenu;
28Menu* videoMenu;
29Menu* keyboardMenu[2];
30Menu* joystickMenu[2];
31FileMenu* fileMenu;
32
33bool pause = true;
34
35/* Set the window size multiplier */
36void 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 */
44void 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 */
137void 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 */
155SDL_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 */
165u8 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 */
201void new_frame(u32* pixels)
202{
203 SDL_UpdateTexture(gameTexture, NULL, pixels, WIDTH * sizeof(u32));
204}
205
206void new_samples(const blip_sample_t* samples, size_t count)
207{
208 soundQueue->write(samples, count);
209}
210
211/* Render the screen */
212void 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 */
229void 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 */
241SDL_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
256int 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 */
272void 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