| 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 | |