Annotation of brogue-ce/src/platform/sdl2-platform.c, Revision 1.1
1.1 ! rubenllo 1: #ifdef SDL_PATHS
! 2: #include <unistd.h>
! 3: #endif
! 4:
! 5: #include <limits.h>
! 6: #include <SDL_image.h>
! 7: #include "platform.h"
! 8: #include "tiles.h"
! 9:
! 10: #define PAUSE_BETWEEN_EVENT_POLLING 36L//17
! 11: #define MAX_REMAPS 128
! 12:
! 13: struct keypair {
! 14: char from;
! 15: char to;
! 16: };
! 17:
! 18: static struct keypair remapping[MAX_REMAPS];
! 19: static size_t nremaps = 0;
! 20: static boolean showGraphics = false;
! 21:
! 22: static rogueEvent lastEvent;
! 23:
! 24:
! 25: static void sdlfatal(char *file, int line) {
! 26: fprintf(stderr, "Fatal SDL error (%s:%d): %s\n", file, line, SDL_GetError());
! 27: exit(1);
! 28: }
! 29:
! 30:
! 31: static void imgfatal(char *file, int line) {
! 32: fprintf(stderr, "Fatal SDL_image error (%s:%d): %s\n", file, line, IMG_GetError());
! 33: exit(1);
! 34: }
! 35:
! 36:
! 37: /*
! 38: If the key is to be processed, returns true and updates event. False
! 39: otherwise. This function only listens for keypresses which do not produce
! 40: corresponding TextInputEvents.
! 41: */
! 42: static boolean eventFromKey(rogueEvent *event, SDL_Keycode key) {
! 43: event->param1 = -1;
! 44:
! 45: switch (key) {
! 46: case SDLK_ESCAPE:
! 47: event->param1 = ESCAPE_KEY;
! 48: return true;
! 49: case SDLK_UP:
! 50: event->param1 = UP_ARROW;
! 51: return true;
! 52: case SDLK_DOWN:
! 53: event->param1 = DOWN_ARROW;
! 54: return true;
! 55: case SDLK_RIGHT:
! 56: event->param1 = RIGHT_ARROW;
! 57: return true;
! 58: case SDLK_LEFT:
! 59: event->param1 = LEFT_ARROW;
! 60: return true;
! 61: case SDLK_RETURN:
! 62: case SDLK_KP_ENTER:
! 63: event->param1 = RETURN_KEY;
! 64: return true;
! 65: case SDLK_BACKSPACE:
! 66: event->param1 = DELETE_KEY;
! 67: return true;
! 68: case SDLK_TAB:
! 69: event->param1 = TAB_KEY;
! 70: return true;
! 71: case SDLK_PRINTSCREEN:
! 72: event->param1 = PRINTSCREEN_KEY;
! 73: return true;
! 74: }
! 75:
! 76: /*
! 77: Only process keypad events when we're holding a modifier, as there is no
! 78: TextInputEvent then.
! 79: */
! 80: if (event->shiftKey || event->controlKey) {
! 81: switch (key) {
! 82: case SDLK_KP_0:
! 83: event->param1 = NUMPAD_0;
! 84: return true;
! 85: case SDLK_KP_1:
! 86: event->param1 = NUMPAD_1;
! 87: return true;
! 88: case SDLK_KP_2:
! 89: event->param1 = NUMPAD_2;
! 90: return true;
! 91: case SDLK_KP_3:
! 92: event->param1 = NUMPAD_3;
! 93: return true;
! 94: case SDLK_KP_4:
! 95: event->param1 = NUMPAD_4;
! 96: return true;
! 97: case SDLK_KP_5:
! 98: event->param1 = NUMPAD_5;
! 99: return true;
! 100: case SDLK_KP_6:
! 101: event->param1 = NUMPAD_6;
! 102: return true;
! 103: case SDLK_KP_7:
! 104: event->param1 = NUMPAD_7;
! 105: return true;
! 106: case SDLK_KP_8:
! 107: event->param1 = NUMPAD_8;
! 108: return true;
! 109: case SDLK_KP_9:
! 110: event->param1 = NUMPAD_9;
! 111: return true;
! 112: }
! 113: }
! 114:
! 115: // Ctrl+letter doesn't give a TextInputEvent
! 116: if (event->controlKey && key >= SDLK_a && key <= SDLK_z) {
! 117: event->param1 = 'a' + (key - SDLK_a);
! 118: if (event->shiftKey) event->param1 -= 'a' - 'A';
! 119: return true;
! 120: }
! 121:
! 122: return false;
! 123: }
! 124:
! 125:
! 126: static boolean _modifierHeld(int mod) {
! 127: SDL_Keymod km = SDL_GetModState();
! 128: return mod == 0 && (km & (KMOD_LSHIFT | KMOD_RSHIFT))
! 129: || mod == 1 && (km & (KMOD_LCTRL | KMOD_RCTRL));
! 130: }
! 131:
! 132:
! 133: static char applyRemaps(char c) {
! 134: for (size_t i=0; i < nremaps; i++) {
! 135: if (remapping[i].from == c) return remapping[i].to;
! 136: }
! 137: return c;
! 138: }
! 139:
! 140:
! 141: /*
! 142: If an event is available, returns true and updates returnEvent. Otherwise
! 143: it returns false and an error event. This function also processes
! 144: platform-specific inputs/behaviours.
! 145: */
! 146: static boolean pollBrogueEvent(rogueEvent *returnEvent, boolean textInput) {
! 147: static int mx = 0, my = 0;
! 148:
! 149: returnEvent->eventType = EVENT_ERROR;
! 150: returnEvent->shiftKey = _modifierHeld(0);
! 151: returnEvent->controlKey = _modifierHeld(1);
! 152:
! 153: SDL_Event event;
! 154: boolean ret = false;
! 155:
! 156:
! 157: // ~ for (int i=0; i < 100 && SDL_PollEvent(&event); i++) {
! 158: while (SDL_PollEvent(&event)) {
! 159: if (event.type == SDL_QUIT) {
! 160: // the player clicked the X button!
! 161: SDL_Quit();
! 162: quitImmediately();
! 163: } else if (event.type == SDL_WINDOWEVENT && event.window.event == SDL_WINDOWEVENT_RESIZED) {
! 164: resizeWindow(event.window.data1, event.window.data2);
! 165: } else if (event.type == SDL_KEYDOWN) {
! 166: SDL_Keycode key = event.key.keysym.sym;
! 167:
! 168: if (key == SDLK_PAGEUP) {
! 169: resizeWindow(max(windowWidth * 11/10, windowWidth + 1), max(windowHeight * 11/10, windowHeight + 1));
! 170: continue;
! 171: } else if (key == SDLK_PAGEDOWN) {
! 172: fullScreen = false;
! 173: resizeWindow(max(windowWidth * 10/11, 1), max(windowHeight * 10/11, 1));
! 174: continue;
! 175: } else if (key == SDLK_F11 || key == SDLK_F12
! 176: || key == SDLK_RETURN && (SDL_GetModState() & KMOD_ALT)) {
! 177: fullScreen = !fullScreen;
! 178: resizeWindow(-1, -1); // Reset to starting resolution
! 179: continue;
! 180: }
! 181:
! 182: if (eventFromKey(returnEvent, key)) {
! 183: returnEvent->eventType = KEYSTROKE;
! 184: return true;
! 185: }
! 186: } else if (event.type == SDL_TEXTINPUT && (unsigned char)(event.text.text[0]) < 0x80) {
! 187: /*
! 188: It's difficult/impossible to check what characters are on the
! 189: shifts of keys. So to detect '&', '>' etc. reliably we need to
! 190: listen for text input events as well as keydowns. This results
! 191: in hybrid keyboard code, where Brogue KEYSTROKEs can come from
! 192: different SDL events.
! 193: */
! 194: char c = event.text.text[0];
! 195:
! 196: if (!textInput) {
! 197: c = applyRemaps(c);
! 198: if (c == '=' || c == '+') {
! 199: resizeWindow(max(windowWidth * 11/10, windowWidth + 1), max(windowHeight * 11/10, windowHeight + 1));
! 200: } else if (c == '-') {
! 201: fullScreen = false;
! 202: resizeWindow(max(windowWidth * 10/11, 1), max(windowHeight * 10/11, 1));
! 203: }
! 204: }
! 205:
! 206: returnEvent->eventType = KEYSTROKE;
! 207: returnEvent->param1 = c;
! 208: // ~ printf("textinput %s\n", event.text.text);
! 209: return true;
! 210: } else if (event.type == SDL_MOUSEBUTTONDOWN || event.type == SDL_MOUSEBUTTONUP) {
! 211: if (event.button.button == SDL_BUTTON_LEFT || event.button.button == SDL_BUTTON_RIGHT) {
! 212: if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_LEFT) {
! 213: returnEvent->eventType = MOUSE_DOWN;
! 214: } else if (event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_LEFT) {
! 215: returnEvent->eventType = MOUSE_UP;
! 216: } else if (event.type == SDL_MOUSEBUTTONDOWN && event.button.button == SDL_BUTTON_RIGHT) {
! 217: returnEvent->eventType = RIGHT_MOUSE_DOWN;
! 218: } else if (event.type == SDL_MOUSEBUTTONUP && event.button.button == SDL_BUTTON_RIGHT) {
! 219: returnEvent->eventType = RIGHT_MOUSE_UP;
! 220: }
! 221: returnEvent->param1 = event.button.x * COLS / windowWidth;
! 222: returnEvent->param2 = event.button.y * ROWS / windowHeight;
! 223: return true;
! 224: }
! 225: } else if (event.type == SDL_MOUSEMOTION) {
! 226: // We don't want to return on a mouse motion event, because only the last
! 227: // in the queue is important. That's why we just set ret=true
! 228: int xcell = event.motion.x * COLS / windowWidth,
! 229: ycell = event.motion.y * ROWS / windowHeight;
! 230: if (xcell != mx || ycell != my) {
! 231: returnEvent->eventType = MOUSE_ENTERED_CELL;
! 232: returnEvent->param1 = xcell;
! 233: returnEvent->param2 = ycell;
! 234: mx = xcell;
! 235: my = ycell;
! 236: ret = true;
! 237: }
! 238: }
! 239: }
! 240:
! 241: return ret;
! 242: }
! 243:
! 244:
! 245: static void _gameLoop() {
! 246: #ifdef SDL_PATHS
! 247: char *path = SDL_GetBasePath();
! 248: if (path) {
! 249: path[strlen(path) - 1] = '\0'; // remove trailing separator
! 250: strcpy(dataDirectory, path);
! 251: } else {
! 252: fprintf(stderr, "Failed to find the path to the application\n");
! 253: exit(1);
! 254: }
! 255: free(path);
! 256:
! 257: path = SDL_GetPrefPath("Brogue", "Brogue CE");
! 258: if (!path || chdir(path) != 0) {
! 259: fprintf(stderr, "Failed to find or change to the save directory\n");
! 260: exit(1);
! 261: }
! 262: free(path);
! 263: #endif
! 264:
! 265: if (SDL_Init(SDL_INIT_VIDEO) < 0) sdlfatal(__FILE__, __LINE__);
! 266:
! 267: if (!(IMG_Init(IMG_INIT_PNG) & IMG_INIT_PNG)) imgfatal(__FILE__, __LINE__);
! 268:
! 269: initTiles();
! 270:
! 271: lastEvent.eventType = EVENT_ERROR;
! 272:
! 273: resizeWindow(windowWidth, windowHeight);
! 274:
! 275: rogueMain();
! 276:
! 277: SDL_Quit();
! 278: }
! 279:
! 280:
! 281: static boolean _pauseForMilliseconds(short ms) {
! 282: updateScreen();
! 283: SDL_Delay(ms);
! 284:
! 285: if (lastEvent.eventType != EVENT_ERROR
! 286: && lastEvent.eventType != MOUSE_ENTERED_CELL) {
! 287: return true; // SDL already gave us an interrupting event to process
! 288: }
! 289:
! 290: return pollBrogueEvent(&lastEvent, false) // ask SDL for a new event if one is available
! 291: && lastEvent.eventType != EVENT_ERROR // and check if it is interrupting
! 292: && lastEvent.eventType != MOUSE_ENTERED_CELL;
! 293: }
! 294:
! 295:
! 296: static void _nextKeyOrMouseEvent(rogueEvent *returnEvent, boolean textInput, boolean colorsDance) {
! 297: long tstart, dt;
! 298:
! 299: updateScreen();
! 300:
! 301: if (lastEvent.eventType != EVENT_ERROR) {
! 302: *returnEvent = lastEvent;
! 303: lastEvent.eventType = EVENT_ERROR;
! 304: return;
! 305: }
! 306:
! 307: while (true) {
! 308: tstart = SDL_GetTicks();
! 309:
! 310: if (colorsDance) {
! 311: shuffleTerrainColors(3, true);
! 312: commitDraws();
! 313: }
! 314:
! 315: updateScreen();
! 316:
! 317: if (pollBrogueEvent(returnEvent, textInput)) break;
! 318:
! 319: dt = PAUSE_BETWEEN_EVENT_POLLING - (SDL_GetTicks() - tstart);
! 320: if (dt > 0) {
! 321: SDL_Delay(dt);
! 322: }
! 323: }
! 324: }
! 325:
! 326:
! 327: /*
! 328: Returns the index of the sprite representing the given glyph. Sprites <256 are
! 329: from the text font sheet, 256+ are from the tiles sheet.
! 330: */
! 331: static int fontIndex(enum displayGlyph glyph) {
! 332: // These are the only non-ASCII glyphs which always come from the font sheet
! 333: if (glyph == G_UP_ARROW) return 0x90;
! 334: if (glyph == G_DOWN_ARROW) return 0x91;
! 335:
! 336: if (glyph < 128) {
! 337: // ASCII characters map directly
! 338: return glyph;
! 339: } else if (showGraphics && glyph >= 128) {
! 340: // Tile glyphs have sprite indices starting at 256
! 341: // -2 to disregard the up and down arrow glyphs
! 342: return glyph + 128 - 2;
! 343: } else {
! 344: unsigned int code = glyphToUnicode(glyph);
! 345: switch (code) {
! 346: case U_MIDDLE_DOT: return 0x80;
! 347: case U_FOUR_DOTS: return 0x81;
! 348: case U_DIAMOND: return 0x82;
! 349: case U_FLIPPED_V: return 0x83;
! 350: case U_ARIES: return 0x84;
! 351: case U_ESZETT: return 0xdf;
! 352: case U_ANKH: return 0x85;
! 353: case U_MUSIC_NOTE: return 0x86;
! 354: case U_CIRCLE: return 0x87;
! 355: case U_LIGHTNING_BOLT: return 0x99;
! 356: case U_FILLED_CIRCLE: return 0x89;
! 357: case U_NEUTER: return 0x8a;
! 358: case U_U_ACUTE: return 0xda;
! 359: case U_CURRENCY: return 0xa4;
! 360: case U_UP_ARROW: return 0x90;
! 361: case U_DOWN_ARROW: return 0x91;
! 362: case U_LEFT_ARROW: return 0x92;
! 363: case U_RIGHT_ARROW: return 0x93;
! 364: case U_OMEGA: return 0x96;
! 365: case U_CIRCLE_BARS: return 0x8c;
! 366: case U_FILLED_CIRCLE_BARS: return 0x8d;
! 367:
! 368: default:
! 369: brogueAssert(code < 256);
! 370: return code;
! 371: }
! 372: }
! 373: }
! 374:
! 375:
! 376: static void _plotChar(
! 377: enum displayGlyph inputChar,
! 378: short x, short y,
! 379: short foreRed, short foreGreen, short foreBlue,
! 380: short backRed, short backGreen, short backBlue
! 381: ) {
! 382: updateTile(y, x, fontIndex(inputChar),
! 383: foreRed, foreGreen, foreBlue,
! 384: backRed, backGreen, backBlue);
! 385: }
! 386:
! 387:
! 388: static void _remap(const char *from, const char *to) {
! 389: if (nremaps < MAX_REMAPS) {
! 390: remapping[nremaps].from = from[0];
! 391: remapping[nremaps].to = to[0];
! 392: nremaps++;
! 393: }
! 394: }
! 395:
! 396:
! 397: /*
! 398: * Take screenshot in current working directory (ScreenshotN.png)
! 399: */
! 400: static boolean _takeScreenshot() {
! 401: SDL_Surface *screenshot = captureScreen();
! 402: if (!screenshot) return false;
! 403:
! 404: // choose filename
! 405: char screenshotFilepath[BROGUE_FILENAME_MAX];
! 406: getAvailableFilePath(screenshotFilepath, "Screenshot", SCREENSHOT_SUFFIX);
! 407: strcat(screenshotFilepath, SCREENSHOT_SUFFIX);
! 408:
! 409: // save to PNG
! 410: IMG_SavePNG(screenshot, screenshotFilepath);
! 411: SDL_FreeSurface(screenshot);
! 412:
! 413: return true;
! 414: }
! 415:
! 416:
! 417: static boolean _setGraphicsEnabled(boolean state) {
! 418: showGraphics = state;
! 419: refreshScreen();
! 420: updateScreen();
! 421: return state;
! 422: }
! 423:
! 424:
! 425: struct brogueConsole sdlConsole = {
! 426: _gameLoop,
! 427: _pauseForMilliseconds,
! 428: _nextKeyOrMouseEvent,
! 429: _plotChar,
! 430: _remap,
! 431: _modifierHeld,
! 432: NULL,
! 433: _takeScreenshot,
! 434: _setGraphicsEnabled
! 435: };
CVSweb