/* Licensing information can be found at the end of the file. */ #include "lzr.h" #include #ifdef LZR_ENABLE_GFX # include #endif #ifdef LZR_ENABLE_IMAGE # include #endif #ifdef LZR_ENABLE_MIXER # include #endif #ifdef LZR_ENABLE_DEVMODE # include #endif #include #include #include #include #define UNPACKED_COLOR color[0], color[1], color[2] #define UNPACKED_COLOR_RGBA color[0], color[1], color[2], color[3] #define SCODE_BIND_MENU SDL_SCANCODE_F1 #define SCODE_FULLSCREEN SDL_SCANCODE_F11 static LZR_Config config = {0}; static char *basepath = NULL; static SDL_Window *window = NULL; static SDL_Renderer *renderer = NULL; static SDL_Texture *target = NULL; static uint_least64_t next_time = 0; static uint_least64_t min_dt = 0; static bool should_quit = false; static struct { SDL_Texture *tex; int width, height; char *path; long mtime; } images[LZR_MAX_IMAGES] = {0}; static unsigned int color[4] = {0}; static unsigned int map[LZR_BUTTON_COUNT] = { SDL_SCANCODE_LEFT, SDL_SCANCODE_RIGHT, SDL_SCANCODE_UP, SDL_SCANCODE_DOWN, SDL_SCANCODE_X, SDL_SCANCODE_C}; static bool input[LZR_BUTTON_COUNT] = {false}; static SDL_Point *points = NULL; static uint64_t tick = 0; static int off_x = 0; static int off_y = 0; static float scale = 1.0; static int mouse_x = 0; static int mouse_y = 0; #ifdef LZR_ENABLE_MIXER static struct { Mix_Chunk *ptr; } sounds[LZR_MAX_SOUNDS] = {0}; static Mix_Music *music = NULL; #endif static char *_lzrstrdup(const char *str) { char *const cpy = malloc(strlen(str) + 1); if (cpy == NULL) return NULL; strcpy(cpy, str); return cpy; } static int _scode_to_button(unsigned int scode) { for (int i = 0; i < LZR_BUTTON_MOUSE_L; i++) if (map[i] == scode) return i; return -1; } static void _draw_btn(SDL_Renderer *ren, int btn, int x, int y, unsigned int size) { #ifdef LZR_ENABLE_GFX const unsigned int size_2thirds = size * 2 / 3; switch (btn) { case LZR_BUTTON_LEFT: filledTrigonRGBA(ren, x - size / 2, y, x + size / 2, y + size_2thirds, x + size / 2, y - size_2thirds, 0, 0, 0, 255); break; case LZR_BUTTON_RIGHT: filledTrigonRGBA(ren, x + size / 2, y, x - size / 2, y + size_2thirds, x - size / 2, y - size_2thirds, 0, 0, 0, 255); break; case LZR_BUTTON_UP: filledTrigonRGBA(ren, x, y - size / 2, x - size_2thirds, y + size / 2, x + size_2thirds, y + size / 2, 0, 0, 0, 255); break; case LZR_BUTTON_DOWN: filledTrigonRGBA(ren, x, y + size / 2, x - size_2thirds, y - size / 2, x + size_2thirds, y - size / 2, 0, 0, 0, 255); break; case LZR_BUTTON_O: filledCircleRGBA(ren, x, y, size * 2 / 3, 0, 0, 0, 255); break; case LZR_BUTTON_X: thickLineRGBA(ren, x - size / 2, y - size / 2, x + size / 2, y + size / 2, size / 16 + 1, 0, 0, 0, 255); thickLineRGBA(ren, x + size / 2, y - size / 2, x - size / 2, y + size / 2, size / 16 + 1, 0, 0, 0, 255); break; default: break; } #else (void)ren, (void)btn, (void)x, (void)y, (void)size; #endif } static void _bind_menu(void) { SDL_Log("entering bind menu"); SDL_Window *win = NULL; SDL_Renderer *ren = NULL; if (SDL_CreateWindowAndRenderer(256, 256, 0, &win, &ren) < 0) { SDL_Log("%s", SDL_GetError()); return; } int btn = 0; SDL_Event e; while (btn < LZR_BUTTON_MOUSE_L) { while (SDL_PollEvent(&e)) { if (e.type != SDL_KEYDOWN || e.key.repeat || e.key.keysym.scancode == SCODE_BIND_MENU || e.key.keysym.scancode == SCODE_FULLSCREEN) continue; if (e.key.keysym.scancode == SDL_SCANCODE_ESCAPE || e.type == SDL_QUIT) goto exit_bind_menu; LZR_ButtonBind(btn, e.key.keysym.scancode); btn++; } SDL_SetRenderDrawColor(ren, 220, 220, 200, 255); SDL_RenderClear(ren); SDL_SetRenderDrawColor(ren, 0, 0, 0, 255); _draw_btn(ren, btn, 128, 128, 104); SDL_RenderPresent(ren); sleep(0); } exit_bind_menu: SDL_DestroyRenderer(ren); SDL_DestroyWindow(win); SDL_Log("leaving bind menu"); } int LZR_Init(LZR_Config cfg) { memcpy(&config, &cfg, sizeof(config)); if (config.display_width == 0) { SDL_Log("display_width can't be 0"); return -1; } if (config.display_height == 0) { SDL_Log("display_height can't be 0"); return -1; } if (config.title == NULL) { SDL_Log("title is NULL, defaulting to 'LZR'"); config.title = "LZR"; } if (config.tile_size == 0) config.tile_size = 1; if (config.ratio <= 0.0) config.ratio = 1.0; else { const double ratio = (float)config.display_width / (float)config.display_height; config.ratio /= ratio; } if (SDL_Init(SDL_INIT_VIDEO) < 0) { SDL_Log("%s", SDL_GetError()); return -1; } #ifdef LZR_ENABLE_IMAGE if (IMG_Init(IMG_INIT_PNG) != IMG_INIT_PNG) { SDL_Log("%s", IMG_GetError()); return -1; } #endif #ifdef LZR_ENABLE_MIXER if (Mix_Init(MIX_INIT_FLAC) != MIX_INIT_FLAC) { SDL_Log("%s", Mix_GetError()); return -1; } if (Mix_OpenAudio(44100, MIX_DEFAULT_FORMAT, 8, 1024) < 0) { SDL_Log("%s", Mix_GetError()); return -1; } #endif basepath = SDL_GetBasePath(); if (basepath == NULL) { SDL_Log("%s", SDL_GetError()); return -1; } const int dwidth = config.display_width * config.ratio; window = SDL_CreateWindow(config.title, SDL_WINDOWPOS_UNDEFINED, SDL_WINDOWPOS_UNDEFINED, dwidth, config.display_height, SDL_WINDOW_RESIZABLE); if (window == NULL) { SDL_Log("%s", SDL_GetError()); return -1; } renderer = SDL_CreateRenderer(window, -1, SDL_RENDERER_ACCELERATED); if (renderer == NULL) { SDL_Log("%s", SDL_GetError()); return -1; } target = SDL_CreateTexture(renderer, SDL_PIXELFORMAT_RGB888, SDL_TEXTUREACCESS_TARGET, config.display_width, config.display_height); if (target == NULL) { SDL_Log("%s", SDL_GetError()); return -1; } points = calloc(cfg.display_width * cfg.display_height, sizeof(SDL_Point)); if (points == NULL) { SDL_Log("calloc failed"); return -1; } SDL_SetHint(SDL_HINT_RENDER_SCALE_QUALITY, 0); if (config.target_fps) { min_dt = 1000 / config.target_fps; next_time = SDL_GetTicks64(); } if (config.hide_cursor && SDL_ShowCursor(SDL_DISABLE) < 0) { SDL_Log("%s", SDL_GetError()); return -1; } if (LZR_DrawSetColor(1.0f, 1.0f, 1.0f, 1.0f)) return -1; return 0; } void LZR_Quit(void) { #ifdef LZR_ENABLE_MIXER for (int i = 0; i < LZR_MAX_SOUNDS; i--) if (sounds[i].ptr != NULL) { Mix_FreeChunk(sounds[i].ptr); sounds[i].ptr = NULL; } Mix_CloseAudio(); Mix_Quit(); #endif for (int i = 0; i < LZR_MAX_IMAGES; i++) { if (images[i].tex != NULL) { SDL_DestroyTexture(images[i].tex); images[i].tex = NULL; } if (images[i].path != NULL) { free(images[i].path); images[i].path = NULL; } } if (points != NULL) { free(points); points = NULL; } if (target != NULL) { SDL_DestroyTexture(target); target = NULL; } if (renderer != NULL) { SDL_DestroyRenderer(renderer); renderer = NULL; } if (window != NULL) { SDL_DestroyWindow(window); window = NULL; } if (basepath != NULL) { SDL_free(basepath); basepath = NULL; } #ifdef LZR_ENABLE_IMAGE IMG_Quit(); #endif SDL_Quit(); } bool LZR_ShouldQuit(void) { return should_quit; } char *LZR_PathPrefix(const char *path) { if (path == NULL) { SDL_Log("path is NULL"); return NULL; } if (basepath == NULL) { SDL_Log("basepath is NULL"); return _lzrstrdup(path); } char *const buf = malloc(strlen(basepath) + strlen(path) + 1); if (buf == NULL) { SDL_Log("malloc failed"); return NULL; } strcpy(buf, basepath); strcat(buf, path); return buf; } int LZR_ImageLoad(const char *path) { char *apath; long mtime = 0; #ifdef LZR_ENABLE_DEVMODE apath = LZR_PathPrefix(path); if (apath == NULL) return -1; struct stat st = {0}; (void)stat(apath, &st); /* stat can fail safely */ mtime = st.st_mtim.tv_nsec; #endif int i; for (i = 0; i < LZR_MAX_IMAGES; i++) { if (images[i].path != NULL && strcmp(images[i].path, path) == 0) { if (mtime != images[i].mtime) { SDL_Log("reloading %d", i); break; } return i; } if (images[i].tex == NULL) break; } if (i >= LZR_MAX_IMAGES) { SDL_Log("reached image limit (%d)", LZR_MAX_IMAGES); return -1; } apath = LZR_PathPrefix(path); if (apath == NULL) { SDL_Log("LZR_PathPrefix failed"); return -1; } #ifdef LZR_ENABLE_IMAGE SDL_Surface *const surf = IMG_Load(apath); #else SDL_Surface *const surf = SDL_LoadBMP(apath); #endif free(apath); if (surf == NULL) { SDL_Log("%s: %s", path, SDL_GetError()); return -1; } SDL_Texture *const tex = SDL_CreateTextureFromSurface(renderer, surf); SDL_FreeSurface(surf); if (tex == NULL) { SDL_Log("%s", SDL_GetError()); return -1; } if (images[i].tex != NULL) SDL_DestroyTexture(images[i].tex); images[i].tex = tex; images[i].mtime = mtime; if (SDL_SetTextureBlendMode(images[i].tex, SDL_BLENDMODE_BLEND) < 0) { SDL_Log("%s", SDL_GetError()); return -1; } if (SDL_QueryTexture(tex, NULL, NULL, &images[i].width, &images[i].height)) { SDL_Log("%s", SDL_GetError()); return -1; } if (images[i].path == NULL) images[i].path = _lzrstrdup(path); return i; } int LZR_SoundLoad(const char *path, float volume) { #ifdef LZR_ENABLE_MIXER int i; for (i = 0; i < LZR_MAX_SOUNDS; i++) if (sounds[i].ptr == NULL) break; if (i >= LZR_MAX_SOUNDS) { SDL_Log("reached sounds limit (%d)", LZR_MAX_SOUNDS); return -1; } char *const apath = LZR_PathPrefix(path); if (apath == NULL) { SDL_Log("LZR_PathPrefix failed"); return -1; } Mix_Chunk *const chunk = Mix_LoadWAV(apath); free(apath); if (chunk == NULL) { SDL_Log("%s: %s", path, Mix_GetError()); return -1; } Mix_VolumeChunk(chunk, volume); sounds[i].ptr = chunk; return i; #else (void)path, (void)volume; return -1; #endif } bool LZR_PollEvent(LZR_Event *e) { if (e == NULL) { SDL_Log("e is NULL"); return false; } SDL_Event se; while (SDL_PollEvent(&se)) { switch (se.type) { case SDL_QUIT: e->type = LZR_EVENT_QUIT; should_quit = true; return true; case SDL_KEYDOWN: { if (!config.disable_bind_menu && se.key.keysym.scancode == SCODE_BIND_MENU) _bind_menu(); if (se.key.keysym.scancode == SCODE_FULLSCREEN) LZR_ToggleFullscreen(); const int b = _scode_to_button(se.key.keysym.scancode); if (se.key.repeat || b < 0) break; e->type = LZR_EVENT_BUTTON_DOWN; e->button = b; input[b] = true; return true; } case SDL_MOUSEBUTTONDOWN: { e->type = LZR_EVENT_BUTTON_DOWN; e->button = LZR_BUTTON_MOUSE_L + se.button.button - 1; e->x = se.button.x, e->y = se.button.y; LZR_ScreenTransform(&e->x, &e->y); mouse_x = e->x, mouse_y = e->y; if (e->button >= LZR_BUTTON_COUNT) continue; input[e->button] = true; return true; } case SDL_KEYUP: { const int b = _scode_to_button(se.key.keysym.scancode); if (b < 0) break; e->type = LZR_EVENT_BUTTON_UP; e->button = b; input[b] = false; return true; } case SDL_MOUSEBUTTONUP: { e->type = LZR_EVENT_BUTTON_DOWN; e->button = LZR_BUTTON_MOUSE_L + se.button.button - 1; e->x = se.button.x, e->y = se.button.y; LZR_ScreenTransform(&e->x, &e->y); mouse_x = e->x, mouse_y = e->y; if (e->button >= LZR_BUTTON_COUNT) continue; input[e->button] = false; return true; } case SDL_MOUSEMOTION: { e->type = LZR_EVENT_MOUSE_MOVE; e->x = se.motion.x, e->y = se.motion.y; LZR_ScreenTransform(&e->x, &e->y); mouse_x = e->x, mouse_y = e->y; return true; } default: break; } } return false; } void LZR_CycleEvents(void) { LZR_Event e; while (LZR_PollEvent(&e)) ; } bool LZR_ButtonDown(LZR_Button btn) { if (btn >= 0 && btn < LZR_BUTTON_COUNT) return input[btn]; else SDL_Log("%d button doesn't exist", btn); return false; } void LZR_ButtonBind(LZR_Button btn, unsigned int code) { if (btn < LZR_BUTTON_MOUSE_L && code != SCODE_BIND_MENU) { map[btn] = code; SDL_Log("bound key %s to button %u", SDL_GetScancodeName(map[btn]), btn); } else SDL_Log("button %u can't be remapped to key %s", btn, SDL_GetScancodeName(code)); } int LZR_DrawBegin(void) { if (config.target_fps > 0) next_time += min_dt; if (SDL_SetRenderTarget(renderer, target) < 0) { SDL_Log("%s", SDL_GetError()); return -1; } SDL_SetRenderDrawColor(renderer, 0, 0, 0, 255); SDL_RenderClear(renderer); return 0; } int LZR_DrawEnd(void) { if (SDL_SetRenderTarget(renderer, NULL)) { SDL_Log("%s", SDL_GetError()); return -1; } LZR_DrawSetColor(0.0f, 0.0f, 0.0f, 1.0f); if (LZR_DrawClear()) { SDL_Log("LZY_DrawClear failed"); return -1; } if (config.target_fps) { const uint_least64_t cur_time = SDL_GetTicks64(); if (next_time <= cur_time) next_time = cur_time; else SDL_Delay(next_time - cur_time); } int win_w, win_h; SDL_GetWindowSize(window, &win_w, &win_h); const int width = config.display_width * config.ratio; const int height = config.display_height; const int ratio_w = win_w / width; const int ratio_h = win_h / height; scale = (ratio_w <= ratio_h) ? ratio_w : ratio_h; off_x = (win_w - width * scale) / 2; off_y = (win_h - height * scale) / 2; const SDL_Rect dest = {off_x, off_y, width * scale, height * scale}; if (SDL_RenderCopyEx(renderer, target, NULL, &dest, 0.0, NULL, 0) < 0) { SDL_Log("%s", SDL_GetError()); return -1; } SDL_RenderPresent(renderer); tick++; return 0; } int LZR_DrawSetColor(float r, float g, float b, float a) { const unsigned int ur = (unsigned int)(r * 255) & 255; const unsigned int ug = (unsigned int)(g * 255) & 255; const unsigned int ub = (unsigned int)(b * 255) & 255; const unsigned int ua = (unsigned int)(a * 255) & 255; if (SDL_SetRenderDrawColor(renderer, ur, ug, ub, ua) < 0) { SDL_Log("%s", SDL_GetError()); return -1; } color[0] = ur, color[1] = ug, color[2] = ub, color[3] = ua; return 0; } int LZR_DrawClear(void) { if (SDL_RenderClear(renderer) < 0) { SDL_Log("%s", SDL_GetError()); return -1; } return 0; } int LZR_DrawPoint(int x, int y) { if (SDL_RenderDrawPoint(renderer, x, y) < 0) { SDL_Log("%s", SDL_GetError()); return -1; } return 0; } int LZR_DrawPoints(int *x, int *y, int n) { if (n > (int)(config.display_width * config.display_height)) { SDL_Log("%d > %u", n, config.display_width * config.display_height); return -1; } for (int i = 0; i < n; i++) { points[i].x = x[i]; points[i].y = y[i]; } if (SDL_RenderDrawPoints(renderer, points, n) < 0) { SDL_Log("%s", SDL_GetError()); return -1; } return 0; } int LZR_DrawLine(int x0, int y0, int x1, int y1) { if (SDL_RenderDrawLine(renderer, x0, y0, x1, y1) < 0) { SDL_Log("%s", SDL_GetError()); return -1; } return 0; } int LZR_DrawRectangle(bool fill, int x, int y, int w, int h) { SDL_Rect rect = {x, y, w, h}; if ((fill ? SDL_RenderFillRect : SDL_RenderDrawRect)(renderer, &rect)) { SDL_Log("%s", SDL_GetError()); return -1; } return 0; } int LZR_DrawCircle(bool fill, int x, int y, int radius) { #ifdef LZR_ENABLE_GFX if ((fill ? filledCircleRGBA : circleRGBA)(renderer, x, y, radius, UNPACKED_COLOR_RGBA) < 0) { SDL_Log("%s", SDL_GetError()); return -1; } return 0; #else (void)fill, (void)x, (void)y, (void)radius; SDL_Log("LZR GFX module is disabled"); return -1; #endif } int LZR_DrawPolygon(bool fill, int *vx, int *vy, int n) { #ifdef LZR_ENABLE_GFX if (n > 32) { SDL_Log("%d > 32", n); return -1; } Sint16 x[32], y[32]; for (int i = 0; i < n; i++) x[i] = vx[i], y[i] = vy[i]; if ((fill ? filledPolygonRGBA : polygonRGBA)(renderer, x, y, n, UNPACKED_COLOR_RGBA) < 0) { SDL_Log("%s", SDL_GetError()); return -1; } return 0; #else (void)fill, (void)vx, (void)vy, (void)n; SDL_Log("LZR GFX module is disabled"); return -1; #endif } int LZR_DrawImage(int id, int x, int y) { if (id < 0) { SDL_Log("id is negative"); return -1; } if (id >= LZR_MAX_IMAGES || images[id].tex == NULL) { SDL_Log("no image with id %d", id); return -1; } if (SDL_SetTextureColorMod(images[id].tex, UNPACKED_COLOR) < 0) { SDL_Log("%s", SDL_GetError()); return -1; } if (SDL_SetTextureAlphaMod(images[id].tex, color[3]) < 0) { SDL_Log("%s", SDL_GetError()); return -1; } const SDL_Rect dest = {x, y, images[id].width, images[id].height}; if (SDL_RenderCopy(renderer, images[id].tex, NULL, &dest) < 0) { SDL_Log("%s", SDL_GetError()); return -1; } return 0; } int LZR_DrawImageEx(int id, int x, int y, LZR_ImageDrawSettings stg) { if (id < 0) { SDL_Log("id is negative"); return -1; } if (id >= LZR_MAX_IMAGES || images[id].tex == NULL) { SDL_Log("no image with id %d", id); return -1; } const int width = (stg.width > 0) ? stg.width : images[id].width; const int height = (stg.height > 0) ? stg.height : images[id].height; if (stg.center) { x -= stg.scale_x * width / 2; y -= stg.scale_y * height / 2; } SDL_Rect src = {stg.ix, stg.iy, width, height}; SDL_Rect dst = {x, y, width * stg.scale_x, height * stg.scale_y}; if (stg.ix < 0) { src.w += stg.ix; dst.x = 0 - stg.ix; dst.w += stg.ix; } if (stg.iy < 0) { src.y = 0 - stg.iy; src.h += stg.iy; dst.y -= stg.iy; dst.h += stg.iy; } if (stg.ix + width > images[id].width) { src.w = images[id].width - stg.ix; dst.w = images[id].width - stg.ix; } if (stg.iy + height > images[id].height) { src.h = images[id].height - stg.iy; dst.h = images[id].height - stg.iy; } if (SDL_SetTextureColorMod(images[id].tex, UNPACKED_COLOR) < 0) { SDL_Log("%s", SDL_GetError()); return -1; } if (SDL_SetTextureAlphaMod(images[id].tex, color[3]) < 0) { SDL_Log("%s", SDL_GetError()); return -1; } const int flip = (stg.flip_v ? SDL_FLIP_VERTICAL : 0) | (stg.flip_h ? SDL_FLIP_HORIZONTAL : 0); if (SDL_RenderCopyEx(renderer, images[id].tex, &src, &dst, stg.angle * 360.0, NULL, flip)) { SDL_Log("%s", SDL_GetError()); return -1; } return 0; } int LZR_DrawTile(int id, int tile, int x, int y, double rot, int flip) { if (id < 0) { SDL_Log("id is negative"); return -1; } if (id >= LZR_MAX_IMAGES || images[id].tex == NULL) { SDL_Log("no image with id %d", id); return -1; } if (tile < 0) { SDL_Log("tile is negative"); return -1; } const int img_width = images[id].width / config.tile_size; const int img_height = images[id].height / config.tile_size; if (tile >= img_width * img_height) { SDL_Log("tile exceeds boundaries"); return -1; } if (SDL_SetTextureColorMod(images[id].tex, UNPACKED_COLOR) < 0) { SDL_Log("%s", SDL_GetError()); return -1; } if (SDL_SetTextureAlphaMod(images[id].tex, color[3]) < 0) { SDL_Log("%s", SDL_GetError()); return -1; } SDL_Rect src; src.x = (tile % img_width) * config.tile_size; src.y = (tile / img_width) * config.tile_size; src.w = config.tile_size, src.h = config.tile_size; const SDL_Rect dst = {x, y, config.tile_size, config.tile_size}; if (SDL_RenderCopyEx(renderer, images[id].tex, &src, &dst, rot, NULL, flip) < 0) { SDL_Log("%s", SDL_GetError()); return -1; } return 0; } int LZR_PlaySound(int id) { #ifdef LZR_ENABLE_MIXER if (id < 0) { SDL_Log("id is negative"); return -1; } if (id >= LZR_MAX_SOUNDS || sounds[id].ptr == NULL) { SDL_Log("no sound with id %d", id); return -1; } if (Mix_PlayChannel(-1, sounds[id].ptr, 0) < 0) { SDL_Log("%s", Mix_GetError()); return -1; } return 0; #else (void)id; SDL_Log("LZR MIXER module is disabled"); return -1; #endif } int LZR_SetMusicVolume(float volume) { #ifdef LZR_ENABLE_MIXER if (Mix_VolumeMusic(volume * MIX_MAX_VOLUME) < 0) { SDL_Log("%s", Mix_GetError()); return -1; } return 0; #else (void)volume; SDL_Log("LZR MIXER module is disabled"); return -1; #endif } int LZR_PlayMusic(const char *path, int loops) { #ifdef LZR_ENABLE_MIXER LZR_StopMusic(); char *const apath = LZR_PathPrefix(path); if (apath == NULL) { SDL_Log("LZR_PathPrefix failed"); return -1; } music = Mix_LoadMUS(apath); free(apath); if (music == NULL) { SDL_Log("%s: %s", path, Mix_GetError()); return -1; } if (Mix_PlayMusic(music, loops) < 0) { SDL_Log("%s", Mix_GetError()); return -1; } Mix_RewindMusic(); return 0; #else (void)path, (void)loops; SDL_Log("LZR MIXER module is disabled"); return -1; #endif } void LZR_StopMusic(void) { #ifdef LZR_ENABLE_MIXER if (Mix_PlayingMusic()) Mix_HaltMusic(); if (music != NULL) { Mix_FreeMusic(music); music = NULL; } #endif } void LZR_ToggleFullscreen(void) { static int fullscreen = 0; fullscreen = !fullscreen; SDL_SetWindowFullscreen(window, fullscreen * SDL_WINDOW_FULLSCREEN_DESKTOP); } uint64_t LZR_GetTick(void) { return tick; } void LZR_ScreenTransform(int *x, int *y) { if (scale == 0.0) return; if (x != NULL) { *x -= off_x; *x /= scale; } if (y != NULL) { *y -= off_y; *y /= scale; } } void LZR_MousePosition(int *x, int *y) { if (x != NULL) *x = mouse_x; if (y != NULL) *y = mouse_y; } /* ** Copyright (c) 2022, 2023 kdx ** ** Permission is hereby granted, free of charge, to any person obtaining a copy ** of this software and associated documentation files (the "Software"), to ** deal in the Software without restriction, including without limitation the ** rights to use, copy, modify, merge, publish, distribute, sublicense, and/or ** sell copies of the Software, and to permit persons to whom the Software is ** furnished to do so, subject to the following conditions: ** ** The above copyright notice and this permission notice shall be included in ** all copies or substantial portions of the Software. ** ** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR ** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, ** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE ** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER ** LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING ** FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS ** IN THE SOFTWARE. */