2017-10-04 09:43:31 +00:00
|
|
|
#include <SDL2/SDL.h>
|
|
|
|
#include <stdbool.h>
|
|
|
|
#include "utils.h"
|
|
|
|
#include "gui.h"
|
|
|
|
#include "font.h"
|
|
|
|
|
|
|
|
static const SDL_Color gui_palette[4] = {{8, 24, 16,}, {57, 97, 57,}, {132, 165, 99}, {198, 222, 140}};
|
|
|
|
static uint32_t gui_palette_native[4];
|
|
|
|
|
|
|
|
SDL_Window *window = NULL;
|
|
|
|
SDL_Renderer *renderer = NULL;
|
|
|
|
SDL_Texture *texture = NULL;
|
|
|
|
SDL_PixelFormat *pixel_format = NULL;
|
2017-10-13 22:41:51 +00:00
|
|
|
enum pending_command pending_command;
|
|
|
|
unsigned command_parameter;
|
2017-10-04 09:43:31 +00:00
|
|
|
|
|
|
|
#ifdef __APPLE__
|
2017-10-13 22:41:51 +00:00
|
|
|
#define MODIFIER_NAME " " CMD_STRING
|
2017-10-04 09:43:31 +00:00
|
|
|
#else
|
2017-10-13 22:41:51 +00:00
|
|
|
#define MODIFIER_NAME CTRL_STRING
|
2017-10-04 09:43:31 +00:00
|
|
|
#endif
|
|
|
|
|
2017-12-22 20:25:53 +00:00
|
|
|
configuration_t configuration =
|
|
|
|
{
|
|
|
|
{ SDL_SCANCODE_RIGHT,
|
|
|
|
SDL_SCANCODE_LEFT,
|
|
|
|
SDL_SCANCODE_UP,
|
|
|
|
SDL_SCANCODE_DOWN,
|
|
|
|
SDL_SCANCODE_X,
|
|
|
|
SDL_SCANCODE_Z,
|
|
|
|
SDL_SCANCODE_BACKSPACE,
|
|
|
|
SDL_SCANCODE_RETURN,
|
|
|
|
SDL_SCANCODE_SPACE
|
|
|
|
},
|
|
|
|
GB_COLOR_CORRECTION_EMULATE_HARDWARE,
|
|
|
|
GB_SDL_SCALING_INTEGER_FACTOR,
|
|
|
|
};
|
|
|
|
|
2017-10-04 09:43:31 +00:00
|
|
|
|
2017-10-13 22:41:51 +00:00
|
|
|
static const char *help[] ={
|
|
|
|
"Drop a GB or GBC ROM\n"
|
|
|
|
"file to play.\n"
|
2017-10-04 09:43:31 +00:00
|
|
|
"\n"
|
2017-12-22 20:25:53 +00:00
|
|
|
"Keyboard Shortcuts:\n"
|
|
|
|
" Open Menu: Escape\n"
|
2017-10-13 22:41:51 +00:00
|
|
|
" Reset: " MODIFIER_NAME "+R\n"
|
|
|
|
" Pause: " MODIFIER_NAME "+P\n"
|
|
|
|
" Toggle DMG/CGB: " MODIFIER_NAME "+T\n"
|
|
|
|
" Save state: " MODIFIER_NAME "+(0-9)\n"
|
|
|
|
" Load state: " MODIFIER_NAME "+" SHIFT_STRING "+(0-9)\n"
|
2017-10-04 09:43:31 +00:00
|
|
|
#ifdef __APPLE__
|
2017-10-13 22:41:51 +00:00
|
|
|
" Mute/Unmute: " MODIFIER_NAME "+" SHIFT_STRING "+M\n"
|
2017-10-04 09:43:31 +00:00
|
|
|
#else
|
2017-10-13 22:41:51 +00:00
|
|
|
" Mute/Unmute: " MODIFIER_NAME "+M\n"
|
2017-10-04 09:43:31 +00:00
|
|
|
#endif
|
2017-10-13 22:41:51 +00:00
|
|
|
" Break Debugger: " CTRL_STRING "+C"
|
|
|
|
};
|
2017-10-04 09:43:31 +00:00
|
|
|
|
|
|
|
void update_viewport(void)
|
|
|
|
{
|
|
|
|
int win_width, win_height;
|
|
|
|
SDL_GetWindowSize(window, &win_width, &win_height);
|
|
|
|
double x_factor = win_width / 160.0;
|
|
|
|
double y_factor = win_height / 144.0;
|
|
|
|
|
2017-12-22 20:25:53 +00:00
|
|
|
if (configuration.scaling_mode == GB_SDL_SCALING_INTEGER_FACTOR) {
|
2017-10-04 09:43:31 +00:00
|
|
|
x_factor = (int)(x_factor);
|
|
|
|
y_factor = (int)(y_factor);
|
|
|
|
}
|
|
|
|
|
2017-12-22 20:25:53 +00:00
|
|
|
if (configuration.scaling_mode != GB_SDL_SCALING_ENTIRE_WINDOW) {
|
2017-10-04 09:43:31 +00:00
|
|
|
if (x_factor > y_factor) {
|
|
|
|
x_factor = y_factor;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
y_factor = x_factor;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned new_width = x_factor * 160;
|
|
|
|
unsigned new_height = y_factor * 144;
|
|
|
|
|
|
|
|
SDL_Rect rect = (SDL_Rect){(win_width - new_width) / 2, (win_height - new_height) /2,
|
|
|
|
new_width, new_height};
|
|
|
|
SDL_RenderSetViewport(renderer, &rect);
|
|
|
|
}
|
|
|
|
|
|
|
|
/* Does NOT check for bounds! */
|
|
|
|
static void draw_char(uint32_t *buffer, unsigned char ch, uint32_t color)
|
|
|
|
{
|
2017-10-13 22:41:51 +00:00
|
|
|
if (ch < ' ' || ch > font_max) {
|
2017-10-04 09:43:31 +00:00
|
|
|
ch = '?';
|
|
|
|
}
|
|
|
|
|
|
|
|
uint8_t *data = &font[(ch - ' ') * GLYPH_WIDTH * GLYPH_HEIGHT];
|
|
|
|
|
|
|
|
for (unsigned y = GLYPH_HEIGHT; y--;) {
|
|
|
|
for (unsigned x = GLYPH_WIDTH; x--;) {
|
|
|
|
if (*(data++)) {
|
|
|
|
(*buffer) = color;
|
|
|
|
}
|
|
|
|
buffer++;
|
|
|
|
}
|
|
|
|
buffer += 160 - GLYPH_WIDTH;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-13 22:41:51 +00:00
|
|
|
static void draw_unbordered_text(uint32_t *buffer, unsigned x, unsigned y, const char *string, uint32_t color)
|
2017-10-04 09:43:31 +00:00
|
|
|
{
|
|
|
|
unsigned orig_x = x;
|
|
|
|
while (*string) {
|
|
|
|
if (*string == '\n') {
|
|
|
|
x = orig_x;
|
|
|
|
y += GLYPH_HEIGHT + 4;
|
|
|
|
string++;
|
|
|
|
continue;
|
|
|
|
}
|
|
|
|
|
2017-10-13 22:41:51 +00:00
|
|
|
if (x > 160 - GLYPH_WIDTH || y == 0 || y > 144 - GLYPH_HEIGHT) {
|
2017-10-04 09:43:31 +00:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
|
2017-10-13 22:41:51 +00:00
|
|
|
draw_char(&buffer[x + 160 * y], *string, color);
|
2017-10-04 09:43:31 +00:00
|
|
|
x += GLYPH_WIDTH;
|
|
|
|
string++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-10-13 22:41:51 +00:00
|
|
|
static void draw_text(uint32_t *buffer, unsigned x, unsigned y, const char *string, uint32_t color, uint32_t border)
|
|
|
|
{
|
|
|
|
draw_unbordered_text(buffer, x - 1, y, string, border);
|
|
|
|
draw_unbordered_text(buffer, x + 1, y, string, border);
|
|
|
|
draw_unbordered_text(buffer, x, y - 1, string, border);
|
|
|
|
draw_unbordered_text(buffer, x, y + 1, string, border);
|
|
|
|
draw_unbordered_text(buffer, x, y, string, color);
|
|
|
|
}
|
|
|
|
|
2017-10-14 14:08:52 +00:00
|
|
|
enum decoration {
|
|
|
|
DECORATION_NONE,
|
|
|
|
DECORATION_SELECTION,
|
|
|
|
DECORATION_ARROWS,
|
|
|
|
};
|
|
|
|
|
|
|
|
static void draw_text_centered(uint32_t *buffer, unsigned y, const char *string, uint32_t color, uint32_t border, enum decoration decoration)
|
2017-10-13 22:41:51 +00:00
|
|
|
{
|
|
|
|
unsigned x = 160 / 2 - (unsigned) strlen(string) * GLYPH_WIDTH / 2;
|
|
|
|
draw_text(buffer, x, y, string, color, border);
|
2017-10-14 14:08:52 +00:00
|
|
|
switch (decoration) {
|
|
|
|
case DECORATION_SELECTION:
|
|
|
|
draw_text(buffer, x - GLYPH_WIDTH, y, SELECTION_STRING, color, border);
|
|
|
|
break;
|
|
|
|
case DECORATION_ARROWS:
|
|
|
|
draw_text(buffer, x - GLYPH_WIDTH, y, LEFT_ARROW_STRING, color, border);
|
|
|
|
draw_text(buffer, 160 - x, y, RIGHT_ARROW_STRING, color, border);
|
|
|
|
break;
|
|
|
|
|
|
|
|
case DECORATION_NONE:
|
|
|
|
break;
|
2017-10-13 22:41:51 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
struct menu_item {
|
|
|
|
const char *string;
|
2017-12-22 20:25:53 +00:00
|
|
|
void (*handler)(unsigned);
|
|
|
|
const char *(*value_getter)(unsigned);
|
|
|
|
void (*backwards_handler)(unsigned);
|
2017-10-13 22:41:51 +00:00
|
|
|
};
|
|
|
|
static const struct menu_item *current_menu = NULL;
|
2017-10-14 11:10:26 +00:00
|
|
|
static const struct menu_item *root_menu = NULL;
|
2017-10-13 22:41:51 +00:00
|
|
|
static unsigned current_selection = 0;
|
|
|
|
|
|
|
|
static enum {
|
|
|
|
SHOWING_DROP_MESSAGE,
|
|
|
|
SHOWING_MENU,
|
|
|
|
SHOWING_HELP,
|
2017-12-22 20:25:53 +00:00
|
|
|
WAITING_FOR_KEY,
|
2017-12-22 22:39:04 +00:00
|
|
|
WAITING_FOR_JBUTTON,
|
2017-10-13 22:41:51 +00:00
|
|
|
} gui_state;
|
|
|
|
|
2017-12-22 22:39:04 +00:00
|
|
|
unsigned auto_detect_progress = 0;
|
|
|
|
unsigned auto_detect_inputs[3];
|
|
|
|
|
2017-12-22 20:25:53 +00:00
|
|
|
static void item_exit(unsigned index)
|
2017-10-13 22:41:51 +00:00
|
|
|
{
|
2017-10-14 11:10:26 +00:00
|
|
|
pending_command = GB_SDL_QUIT_COMMAND;
|
2017-10-13 22:41:51 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
static unsigned current_help_page = 0;
|
2017-12-22 20:25:53 +00:00
|
|
|
static void item_help(unsigned index)
|
2017-10-04 09:43:31 +00:00
|
|
|
{
|
2017-10-13 22:41:51 +00:00
|
|
|
current_help_page = 0;
|
|
|
|
gui_state = SHOWING_HELP;
|
2017-10-04 09:43:31 +00:00
|
|
|
}
|
|
|
|
|
2017-12-22 20:25:53 +00:00
|
|
|
static void enter_graphics_menu(unsigned index);
|
|
|
|
static void enter_controls_menu(unsigned index);
|
2017-12-22 22:39:04 +00:00
|
|
|
static void enter_joypad_menu(unsigned index);
|
2017-10-14 11:10:26 +00:00
|
|
|
|
2017-10-13 22:41:51 +00:00
|
|
|
static const struct menu_item paused_menu[] = {
|
|
|
|
{"Resume", NULL},
|
2017-10-14 11:10:26 +00:00
|
|
|
{"Graphic Options", enter_graphics_menu},
|
2017-12-22 22:39:04 +00:00
|
|
|
{"Keyboard", enter_controls_menu},
|
|
|
|
{"Joypad", enter_joypad_menu},
|
2017-10-13 22:41:51 +00:00
|
|
|
{"Help", item_help},
|
|
|
|
{"Exit", item_exit},
|
|
|
|
{NULL,}
|
|
|
|
};
|
|
|
|
|
|
|
|
static const struct menu_item nonpaused_menu[] = {
|
2017-10-14 11:10:26 +00:00
|
|
|
{"Graphic Options", enter_graphics_menu},
|
2017-12-22 22:39:04 +00:00
|
|
|
{"Keyboard", enter_controls_menu},
|
|
|
|
{"Joypad", enter_joypad_menu},
|
2017-10-13 22:41:51 +00:00
|
|
|
{"Help", item_help},
|
|
|
|
{"Exit", item_exit},
|
|
|
|
{NULL,}
|
|
|
|
};
|
|
|
|
|
2017-12-22 20:25:53 +00:00
|
|
|
const char *current_scaling_mode(unsigned index)
|
2017-10-14 11:10:26 +00:00
|
|
|
{
|
2017-12-22 20:25:53 +00:00
|
|
|
return (const char *[]){"Fill Entire Window", "Retain Aspect Ratio", "Retain Integer Factor"}
|
|
|
|
[configuration.scaling_mode];
|
2017-10-14 11:10:26 +00:00
|
|
|
}
|
|
|
|
|
2017-12-22 20:25:53 +00:00
|
|
|
const char *current_color_correction_mode(unsigned index)
|
2017-10-14 11:10:26 +00:00
|
|
|
{
|
2017-12-22 20:25:53 +00:00
|
|
|
return (const char *[]){"Disabled", "Correct Color Curves", "Emulate Hardware", "Preserve Brightness"}
|
|
|
|
[configuration.color_correction_mode];
|
2017-10-14 11:10:26 +00:00
|
|
|
}
|
|
|
|
|
2017-12-22 20:25:53 +00:00
|
|
|
void cycle_scaling(unsigned index)
|
2017-10-14 14:08:52 +00:00
|
|
|
{
|
2017-12-22 20:25:53 +00:00
|
|
|
configuration.scaling_mode++;
|
|
|
|
if (configuration.scaling_mode == GB_SDL_SCALING_MAX) {
|
|
|
|
configuration.scaling_mode = 0;
|
2017-10-14 14:08:52 +00:00
|
|
|
}
|
|
|
|
update_viewport();
|
|
|
|
SDL_RenderClear(renderer);
|
|
|
|
SDL_RenderCopy(renderer, texture, NULL, NULL);
|
|
|
|
SDL_RenderPresent(renderer);
|
|
|
|
}
|
|
|
|
|
2017-12-22 20:25:53 +00:00
|
|
|
void cycle_scaling_backwards(unsigned index)
|
2017-10-14 14:08:52 +00:00
|
|
|
{
|
2017-12-22 20:25:53 +00:00
|
|
|
if (configuration.scaling_mode == 0) {
|
|
|
|
configuration.scaling_mode = GB_SDL_SCALING_MAX - 1;
|
2017-10-14 14:08:52 +00:00
|
|
|
}
|
|
|
|
else {
|
2017-12-22 20:25:53 +00:00
|
|
|
configuration.scaling_mode--;
|
2017-10-14 14:08:52 +00:00
|
|
|
}
|
|
|
|
update_viewport();
|
|
|
|
SDL_RenderClear(renderer);
|
|
|
|
SDL_RenderCopy(renderer, texture, NULL, NULL);
|
|
|
|
SDL_RenderPresent(renderer);
|
|
|
|
}
|
|
|
|
|
2017-12-22 20:25:53 +00:00
|
|
|
static void cycle_color_correction(unsigned index)
|
2017-10-14 11:10:26 +00:00
|
|
|
{
|
2017-12-22 20:25:53 +00:00
|
|
|
if (configuration.color_correction_mode == GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS) {
|
|
|
|
configuration.color_correction_mode = GB_COLOR_CORRECTION_DISABLED;
|
2017-10-14 11:10:26 +00:00
|
|
|
}
|
|
|
|
else {
|
2017-12-22 20:25:53 +00:00
|
|
|
configuration.color_correction_mode++;
|
2017-10-14 11:10:26 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2017-12-22 20:25:53 +00:00
|
|
|
static void cycle_color_correction_backwards(unsigned index)
|
2017-10-14 14:08:52 +00:00
|
|
|
{
|
2017-12-22 20:25:53 +00:00
|
|
|
if (configuration.color_correction_mode == GB_COLOR_CORRECTION_DISABLED) {
|
|
|
|
configuration.color_correction_mode = GB_COLOR_CORRECTION_PRESERVE_BRIGHTNESS;
|
2017-10-14 14:08:52 +00:00
|
|
|
}
|
|
|
|
else {
|
2017-12-22 20:25:53 +00:00
|
|
|
configuration.color_correction_mode--;
|
2017-10-14 14:08:52 +00:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2017-12-22 20:25:53 +00:00
|
|
|
static void return_to_root_menu(unsigned index)
|
2017-10-14 11:10:26 +00:00
|
|
|
{
|
|
|
|
current_menu = root_menu;
|
|
|
|
current_selection = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct menu_item graphics_menu[] = {
|
2017-10-14 14:08:52 +00:00
|
|
|
{"Scaling Mode:", cycle_scaling, current_scaling_mode, cycle_scaling_backwards},
|
|
|
|
{"Color Correction:", cycle_color_correction, current_color_correction_mode, cycle_color_correction_backwards},
|
2017-10-14 11:10:26 +00:00
|
|
|
{"Back", return_to_root_menu},
|
|
|
|
{NULL,}
|
|
|
|
};
|
|
|
|
|
2017-12-22 20:25:53 +00:00
|
|
|
static void enter_graphics_menu(unsigned index)
|
2017-10-14 11:10:26 +00:00
|
|
|
{
|
|
|
|
current_menu = graphics_menu;
|
|
|
|
current_selection = 0;
|
|
|
|
}
|
2017-10-04 09:43:31 +00:00
|
|
|
|
2017-12-22 20:25:53 +00:00
|
|
|
static const char *key_name(unsigned index)
|
|
|
|
{
|
|
|
|
return SDL_GetScancodeName(configuration.keys[index]);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void modify_key(unsigned index)
|
|
|
|
{
|
|
|
|
gui_state = WAITING_FOR_KEY;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct menu_item controls_menu[] = {
|
|
|
|
{"Right:", modify_key, key_name,},
|
|
|
|
{"Left:", modify_key, key_name,},
|
|
|
|
{"Up:", modify_key, key_name,},
|
|
|
|
{"Down:", modify_key, key_name,},
|
|
|
|
{"A:", modify_key, key_name,},
|
|
|
|
{"B:", modify_key, key_name,},
|
|
|
|
{"Select:", modify_key, key_name,},
|
|
|
|
{"Start:", modify_key, key_name,},
|
|
|
|
{"Turbo:", modify_key, key_name,},
|
|
|
|
{"Back", return_to_root_menu},
|
|
|
|
{NULL,}
|
|
|
|
};
|
|
|
|
|
|
|
|
static void enter_controls_menu(unsigned index)
|
|
|
|
{
|
|
|
|
current_menu = controls_menu;
|
|
|
|
current_selection = 0;
|
|
|
|
}
|
|
|
|
|
2017-12-22 22:39:04 +00:00
|
|
|
static unsigned joypad_index = 0;
|
|
|
|
SDL_Joystick *joystick = NULL;
|
|
|
|
const char *current_joypad_name(unsigned index)
|
|
|
|
{
|
|
|
|
static char name[23] = {0,};
|
|
|
|
const char *orig_name = joystick? SDL_JoystickName(joystick) : NULL;
|
|
|
|
if (!orig_name) return "Not Found";
|
|
|
|
unsigned i = 0;
|
|
|
|
|
|
|
|
// SDL returns a name with repeated and trailing spaces
|
|
|
|
while (*orig_name && i < sizeof(name) - 2) {
|
|
|
|
if (orig_name[0] != ' ' || orig_name[1] != ' ') {
|
|
|
|
name[i++] = *orig_name;
|
|
|
|
}
|
|
|
|
orig_name++;
|
|
|
|
}
|
|
|
|
if (i && name[i - 1] == ' ') {
|
|
|
|
i--;
|
|
|
|
}
|
|
|
|
name[i] = 0;
|
|
|
|
|
|
|
|
return name;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cycle_joypads(unsigned index)
|
|
|
|
{
|
|
|
|
joypad_index++;
|
|
|
|
if (joypad_index >= SDL_NumJoysticks()) {
|
|
|
|
joypad_index = 0;
|
|
|
|
}
|
|
|
|
if (joystick) {
|
|
|
|
SDL_JoystickClose(joystick);
|
|
|
|
}
|
|
|
|
joystick = SDL_JoystickOpen(joypad_index);
|
|
|
|
}
|
|
|
|
|
|
|
|
unsigned fix_joypad_button(unsigned button)
|
|
|
|
{
|
|
|
|
if (configuration.div_joystick) {
|
|
|
|
button >>= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (button < 4) {
|
|
|
|
if (configuration.swap_joysticks_bits_1_and_2) {
|
|
|
|
button = (int[]){0, 2, 1, 3}[button];
|
|
|
|
}
|
|
|
|
|
|
|
|
if (configuration.flip_joystick_bit_1) {
|
|
|
|
button ^= 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return button;
|
|
|
|
}
|
|
|
|
|
|
|
|
static void cycle_joypads_backwards(unsigned index)
|
|
|
|
{
|
|
|
|
joypad_index++;
|
|
|
|
if (joypad_index >= SDL_NumJoysticks()) {
|
|
|
|
joypad_index = SDL_NumJoysticks() - 1;
|
|
|
|
}
|
|
|
|
if (joystick) {
|
|
|
|
SDL_JoystickClose(joystick);
|
|
|
|
}
|
|
|
|
joystick = SDL_JoystickOpen(joypad_index);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void detect_joypad_layout(unsigned index)
|
|
|
|
{
|
|
|
|
gui_state = WAITING_FOR_JBUTTON;
|
|
|
|
auto_detect_progress = 0;
|
|
|
|
}
|
|
|
|
|
|
|
|
static const struct menu_item joypad_menu[] = {
|
|
|
|
{"Joypad:", cycle_joypads, current_joypad_name, cycle_joypads_backwards},
|
|
|
|
{"Detect layout", detect_joypad_layout},
|
|
|
|
{NULL,}
|
|
|
|
};
|
|
|
|
|
|
|
|
static void enter_joypad_menu(unsigned index)
|
|
|
|
{
|
|
|
|
current_menu = joypad_menu;
|
|
|
|
current_selection = 0;
|
|
|
|
}
|
|
|
|
|
2017-10-04 09:43:31 +00:00
|
|
|
extern void set_filename(const char *new_filename, bool new_should_free);
|
2017-10-13 22:41:51 +00:00
|
|
|
void run_gui(bool is_running)
|
2017-10-04 09:43:31 +00:00
|
|
|
{
|
2017-12-22 22:39:04 +00:00
|
|
|
if (joystick && !SDL_NumJoysticks()) {
|
|
|
|
SDL_JoystickClose(joystick);
|
|
|
|
joystick = NULL;
|
|
|
|
}
|
|
|
|
else if (!joystick && SDL_NumJoysticks()) {
|
|
|
|
joystick = SDL_JoystickOpen(0);
|
|
|
|
}
|
|
|
|
/* Draw the background screen */
|
2017-10-04 09:43:31 +00:00
|
|
|
static SDL_Surface *converted_background = NULL;
|
|
|
|
if (!converted_background) {
|
|
|
|
SDL_Surface *background = SDL_LoadBMP(executable_relative_path("background.bmp"));
|
|
|
|
SDL_SetPaletteColors(background->format->palette, gui_palette, 0, 4);
|
|
|
|
converted_background = SDL_ConvertSurface(background, pixel_format, 0);
|
|
|
|
SDL_LockSurface(converted_background);
|
|
|
|
SDL_FreeSurface(background);
|
|
|
|
|
|
|
|
for (unsigned i = 4; i--; ) {
|
|
|
|
gui_palette_native[i] = SDL_MapRGB(pixel_format, gui_palette[i].r, gui_palette[i].g, gui_palette[i].b);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
uint32_t pixels[160 * 144];
|
2017-12-22 20:25:53 +00:00
|
|
|
SDL_Event event = {0,};
|
2017-10-13 22:41:51 +00:00
|
|
|
gui_state = is_running? SHOWING_MENU : SHOWING_DROP_MESSAGE;
|
|
|
|
bool should_render = true;
|
2017-10-14 11:10:26 +00:00
|
|
|
current_menu = root_menu = is_running? paused_menu : nonpaused_menu;
|
|
|
|
current_selection = 0;
|
2017-12-22 20:25:53 +00:00
|
|
|
do {
|
2017-12-22 22:39:04 +00:00
|
|
|
/* Convert Joypad events (We only generate down events) */
|
|
|
|
if (gui_state != WAITING_FOR_KEY && gui_state != WAITING_FOR_JBUTTON) {
|
|
|
|
switch (event.type) {
|
|
|
|
case SDL_JOYBUTTONDOWN:
|
|
|
|
event.type = SDL_KEYDOWN;
|
|
|
|
event.jbutton.button = fix_joypad_button(event.jbutton.button);
|
|
|
|
if (event.jbutton.button < 4) {
|
|
|
|
event.key.keysym.scancode = (event.jbutton.button & 1) ? SDL_SCANCODE_RETURN : SDL_SCANCODE_ESCAPE;
|
|
|
|
}
|
|
|
|
else if (event.jbutton.button == 8 || event.jbutton.button == 9) {
|
|
|
|
event.key.keysym.scancode = SDL_SCANCODE_ESCAPE;
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
|
|
|
|
case SDL_JOYAXISMOTION: {
|
|
|
|
static bool axis_active[2] = {false, false};
|
|
|
|
if ((event.jaxis.axis >> configuration.div_joystick) & 1) {
|
|
|
|
if (event.jaxis.value > 0x4000) {
|
|
|
|
if (!axis_active[1]) {
|
|
|
|
event.type = SDL_KEYDOWN;
|
|
|
|
event.key.keysym.scancode = SDL_SCANCODE_DOWN;
|
|
|
|
}
|
|
|
|
axis_active[1] = true;
|
|
|
|
}
|
|
|
|
else if (event.jaxis.value < -0x4000) {
|
|
|
|
if (!axis_active[0]) {
|
|
|
|
event.type = SDL_KEYDOWN;
|
|
|
|
event.key.keysym.scancode = SDL_SCANCODE_UP;
|
|
|
|
}
|
|
|
|
axis_active[1] = true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
axis_active[1] = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (event.jaxis.value > 0x4000) {
|
|
|
|
if (!axis_active[0]) {
|
|
|
|
event.type = SDL_KEYDOWN;
|
|
|
|
event.key.keysym.scancode = SDL_SCANCODE_RIGHT;
|
|
|
|
}
|
|
|
|
axis_active[0] = true;
|
|
|
|
}
|
|
|
|
else if (event.jaxis.value < -0x4000) {
|
|
|
|
if (!axis_active[0]) {
|
|
|
|
event.type = SDL_KEYDOWN;
|
|
|
|
event.key.keysym.scancode = SDL_SCANCODE_LEFT;
|
|
|
|
}
|
|
|
|
axis_active[0] = true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
axis_active[0] = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-10-04 09:43:31 +00:00
|
|
|
switch (event.type) {
|
|
|
|
case SDL_QUIT: {
|
2017-10-14 11:10:26 +00:00
|
|
|
if (!is_running) {
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
pending_command = GB_SDL_QUIT_COMMAND;
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2017-10-04 09:43:31 +00:00
|
|
|
}
|
|
|
|
case SDL_WINDOWEVENT: {
|
|
|
|
if (event.window.event == SDL_WINDOWEVENT_RESIZED) {
|
|
|
|
update_viewport();
|
|
|
|
SDL_RenderClear(renderer);
|
|
|
|
SDL_RenderCopy(renderer, texture, NULL, NULL);
|
|
|
|
SDL_RenderPresent(renderer);
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
}
|
|
|
|
case SDL_DROPFILE: {
|
|
|
|
set_filename(event.drop.file, true);
|
2017-10-13 22:41:51 +00:00
|
|
|
pending_command = GB_SDL_NEW_FILE_COMMAND;
|
2017-10-04 09:43:31 +00:00
|
|
|
return;
|
|
|
|
}
|
2017-12-22 22:39:04 +00:00
|
|
|
case SDL_JOYBUTTONDOWN:
|
|
|
|
{
|
|
|
|
if (gui_state == WAITING_FOR_JBUTTON) {
|
|
|
|
should_render = true;
|
|
|
|
auto_detect_inputs[auto_detect_progress++] = event.jbutton.button;
|
|
|
|
if (auto_detect_progress == 3) {
|
|
|
|
gui_state = SHOWING_MENU;
|
|
|
|
|
|
|
|
configuration.div_joystick =
|
|
|
|
((auto_detect_inputs[0] | auto_detect_inputs[1] | auto_detect_inputs[2]) & 1) == 0 &&
|
|
|
|
auto_detect_inputs[0] > 9;
|
|
|
|
|
|
|
|
if (configuration.div_joystick) {
|
|
|
|
auto_detect_inputs[0] >>= 1;
|
|
|
|
auto_detect_inputs[1] >>= 1;
|
|
|
|
auto_detect_inputs[2] >>= 1;
|
|
|
|
}
|
|
|
|
|
|
|
|
configuration.swap_joysticks_bits_1_and_2 =
|
|
|
|
(auto_detect_inputs[1] & 1) == (auto_detect_inputs[2] & 1);
|
|
|
|
|
|
|
|
if (configuration.swap_joysticks_bits_1_and_2) {
|
|
|
|
auto_detect_inputs[1] = (int[]){0, 2, 1, 3}[auto_detect_inputs[1]];
|
|
|
|
auto_detect_inputs[2] = (int[]){0, 2, 1, 3}[auto_detect_inputs[2]];
|
|
|
|
}
|
|
|
|
|
|
|
|
configuration.flip_joystick_bit_1 = auto_detect_inputs[2] & 1;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
2017-10-04 09:43:31 +00:00
|
|
|
case SDL_KEYDOWN:
|
2017-12-22 20:25:53 +00:00
|
|
|
if (event.key.keysym.scancode == SDL_SCANCODE_ESCAPE) {
|
2017-10-13 22:41:51 +00:00
|
|
|
if (is_running) {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
if (gui_state == SHOWING_DROP_MESSAGE) {
|
|
|
|
gui_state = SHOWING_MENU;
|
|
|
|
}
|
|
|
|
else if (gui_state == SHOWING_MENU) {
|
|
|
|
gui_state = SHOWING_DROP_MESSAGE;
|
|
|
|
}
|
2017-12-22 20:25:53 +00:00
|
|
|
current_selection = 0;
|
|
|
|
current_menu = root_menu;
|
2017-10-13 22:41:51 +00:00
|
|
|
should_render = true;
|
|
|
|
}
|
2017-10-04 09:43:31 +00:00
|
|
|
}
|
2017-10-13 22:41:51 +00:00
|
|
|
|
|
|
|
if (gui_state == SHOWING_MENU) {
|
2017-10-14 17:24:13 +00:00
|
|
|
if (event.key.keysym.scancode == SDL_SCANCODE_DOWN && current_menu[current_selection + 1].string) {
|
2017-10-13 22:41:51 +00:00
|
|
|
current_selection++;
|
|
|
|
should_render = true;
|
|
|
|
}
|
2017-10-14 17:24:13 +00:00
|
|
|
else if (event.key.keysym.scancode == SDL_SCANCODE_UP && current_selection) {
|
2017-10-13 22:41:51 +00:00
|
|
|
current_selection--;
|
|
|
|
should_render = true;
|
|
|
|
}
|
2017-10-14 17:24:13 +00:00
|
|
|
else if (event.key.keysym.scancode == SDL_SCANCODE_RETURN) {
|
2017-10-13 22:41:51 +00:00
|
|
|
if (current_menu[current_selection].handler) {
|
2017-12-22 20:25:53 +00:00
|
|
|
current_menu[current_selection].handler(current_selection);
|
2017-10-14 11:10:26 +00:00
|
|
|
if (pending_command) {
|
|
|
|
if (!is_running && pending_command == GB_SDL_QUIT_COMMAND) {
|
|
|
|
exit(0);
|
|
|
|
}
|
|
|
|
return;
|
|
|
|
}
|
2017-10-13 22:41:51 +00:00
|
|
|
should_render = true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
return;
|
|
|
|
}
|
|
|
|
}
|
2017-10-14 17:24:13 +00:00
|
|
|
else if (event.key.keysym.scancode == SDL_SCANCODE_RIGHT && current_menu[current_selection].backwards_handler) {
|
2017-12-22 20:25:53 +00:00
|
|
|
current_menu[current_selection].handler(current_selection);
|
2017-10-14 14:08:52 +00:00
|
|
|
should_render = true;
|
|
|
|
}
|
2017-10-14 17:24:13 +00:00
|
|
|
else if (event.key.keysym.scancode == SDL_SCANCODE_LEFT && current_menu[current_selection].backwards_handler) {
|
2017-12-22 20:25:53 +00:00
|
|
|
current_menu[current_selection].backwards_handler(current_selection);
|
2017-10-14 14:08:52 +00:00
|
|
|
should_render = true;
|
|
|
|
}
|
2017-10-13 22:41:51 +00:00
|
|
|
}
|
2017-12-22 20:25:53 +00:00
|
|
|
else if (gui_state == SHOWING_HELP) {
|
2017-10-13 22:41:51 +00:00
|
|
|
current_help_page++;
|
|
|
|
if (current_help_page == sizeof(help) / sizeof(help[0])) {
|
|
|
|
gui_state = SHOWING_MENU;
|
|
|
|
}
|
|
|
|
should_render = true;
|
2017-10-04 09:43:31 +00:00
|
|
|
}
|
2017-12-22 20:25:53 +00:00
|
|
|
else if (gui_state == WAITING_FOR_KEY) {
|
|
|
|
configuration.keys[current_selection] = event.key.keysym.scancode;
|
|
|
|
gui_state = SHOWING_MENU;
|
|
|
|
should_render = true;
|
|
|
|
}
|
2017-10-04 09:43:31 +00:00
|
|
|
break;
|
|
|
|
}
|
2017-12-22 20:25:53 +00:00
|
|
|
|
|
|
|
if (should_render) {
|
|
|
|
should_render = false;
|
|
|
|
memcpy(pixels, converted_background->pixels, sizeof(pixels));
|
|
|
|
|
|
|
|
switch (gui_state) {
|
|
|
|
case SHOWING_DROP_MESSAGE:
|
|
|
|
draw_text_centered(pixels, 116, "Drop a GB or GBC", gui_palette_native[3], gui_palette_native[0], false);
|
|
|
|
draw_text_centered(pixels, 128, "file to play", gui_palette_native[3], gui_palette_native[0], false);
|
|
|
|
break;
|
|
|
|
case SHOWING_MENU:
|
|
|
|
draw_text_centered(pixels, 8, "SameBoy", gui_palette_native[3], gui_palette_native[0], false);
|
|
|
|
unsigned i = 0, y = 24;
|
|
|
|
for (const struct menu_item *item = current_menu; item->string; item++, i++) {
|
|
|
|
if (item->value_getter && !item->backwards_handler) {
|
|
|
|
char line[25];
|
|
|
|
snprintf(line, sizeof(line), "%s%*s", item->string, 24 - (int)strlen(item->string), item->value_getter(i));
|
|
|
|
draw_text_centered(pixels, y, line, gui_palette_native[3], gui_palette_native[0],
|
|
|
|
i == current_selection ? DECORATION_SELECTION : DECORATION_NONE);
|
|
|
|
y += 12;
|
|
|
|
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
draw_text_centered(pixels, y, item->string, gui_palette_native[3], gui_palette_native[0],
|
|
|
|
i == current_selection && !item->value_getter ? DECORATION_SELECTION : DECORATION_NONE);
|
|
|
|
y += 12;
|
|
|
|
if (item->value_getter) {
|
|
|
|
draw_text_centered(pixels, y, item->value_getter(i), gui_palette_native[3], gui_palette_native[0],
|
|
|
|
i == current_selection ? DECORATION_ARROWS : DECORATION_NONE);
|
|
|
|
y += 12;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
break;
|
|
|
|
case SHOWING_HELP:
|
|
|
|
draw_text(pixels, 2, 2, help[current_help_page], gui_palette_native[3], gui_palette_native[0]);
|
|
|
|
break;
|
|
|
|
case WAITING_FOR_KEY:
|
|
|
|
draw_text_centered(pixels, 68, "Press a Key", gui_palette_native[3], gui_palette_native[0], DECORATION_NONE);
|
|
|
|
break;
|
2017-12-22 22:39:04 +00:00
|
|
|
case WAITING_FOR_JBUTTON:
|
|
|
|
draw_text_centered(pixels, 68, (const char *[])
|
|
|
|
{
|
|
|
|
"Press button for Start",
|
|
|
|
"Press button for A",
|
|
|
|
"Press button for B",
|
|
|
|
} [auto_detect_progress],
|
|
|
|
gui_palette_native[3], gui_palette_native[0], DECORATION_NONE);
|
|
|
|
break;
|
2017-12-22 20:25:53 +00:00
|
|
|
}
|
|
|
|
|
|
|
|
SDL_UpdateTexture(texture, NULL, pixels, 160 * sizeof (uint32_t));
|
|
|
|
SDL_RenderClear(renderer);
|
|
|
|
SDL_RenderCopy(renderer, texture, NULL, NULL);
|
|
|
|
SDL_RenderPresent(renderer);
|
|
|
|
}
|
|
|
|
} while (SDL_WaitEvent(&event));
|
2017-10-04 09:43:31 +00:00
|
|
|
}
|