/* * This file is part of the openKropki project. * It provides abstract drawing functions based on SDL. * * Copyright (C) 2014-2020 Mateusz Viste * * MIT License * * 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. */ #include <SDL2/SDL.h> #include <SDL2/SDL_ttf.h> #include <stdio.h> /* sprintf() */ #include <stdint.h> #include <stdlib.h> /* abs() */ #include <string.h> /* memset() */ #include "drv_gra.h" #include "media/data.h" /* contains the font data, sprites etc */ #include "ui.h" /* include self for control */ #include "version.h" static int kratkaSize = 0; static int kratkaThickness = 0; static uint32_t kratkaColor = 0; static uint32_t playercolor[3] = {0, 0, 0}; static uint32_t kartkaBackgroundColor = 0; static int wallThickness = 0; static int kropkaRadius = 0; static int kropkaActiveBorder = 0; static uint32_t kropkaActiveBorderColor = 0; static int kropkaActiveRadius = 0; static int plansza_width = 0, plansza_height = 0; int ui_setparams(int w, int h, int tkratkaSize, int tkratkaThickness, uint32_t tkratkaKolor, uint32_t tplayer1color, uint32_t tplayer2color, uint32_t tkartkaBackgroundColor, int twallThickness, int tkropkaRadius, uint32_t tkropkaActiveBorderColor) { /* Save all parameters */ plansza_width = w; plansza_height = h; kratkaSize = tkratkaSize; kratkaThickness = tkratkaThickness; kratkaColor = tkratkaKolor; playercolor[1] = tplayer1color; playercolor[2] = tplayer2color; kartkaBackgroundColor = tkartkaBackgroundColor; kropkaRadius = tkropkaRadius; if (kropkaRadius < 2) kropkaRadius = 2; kropkaActiveBorder = kropkaRadius / 3; if (kropkaActiveBorder < 2) kropkaActiveBorder = 2; kropkaActiveRadius = kropkaRadius + (2 * kropkaActiveBorder); kropkaActiveBorderColor = tkropkaActiveBorderColor; wallThickness = twallThickness; if (wallThickness < 1) wallThickness = 1; /* get out of here */ return(0); } void ui_drawKartka(int width, int height, const int *scrollvector) { int x, y; /* clear entire screen */ gra_clear(0xffffffu); /* draw the under-playfield background */ gra_bgtile(wood5_bmp_gz, wood5_bmp_gz_len); /* draw an empty page */ gra_drawrect(scrollvector[0], scrollvector[1], (width + 1) * kratkaSize, (height + 1) * kratkaSize, kartkaBackgroundColor, 0xff, 1); /* draw the horizontal lines */ for (y = -1; y <= height; y++) { if ((y == -1) || (y == height)) { /* borders are always black */ gra_drawrect(scrollvector[0], (y + 1) * kratkaSize + scrollvector[1], kratkaThickness + (plansza_width + 1) * kratkaSize, kratkaThickness, 0, 0xff, 1); } else { gra_drawrect(scrollvector[0], (y + 1) * kratkaSize + scrollvector[1], kratkaThickness + (plansza_width + 1) * kratkaSize, kratkaThickness, kratkaColor, 0xff, 1); } } /* draw the vertical lines */ for (x = -1; x <= width; x++) { if ((x == -1) || (x == width)) { /* borders are always black */ gra_drawrect((x + 1) * kratkaSize + scrollvector[0], scrollvector[1] + kratkaThickness, kratkaThickness, (plansza_height + 1) * kratkaSize - kratkaThickness, 0, 0xff, 1); } else { gra_drawrect((x + 1) * kratkaSize + scrollvector[0], scrollvector[1] + kratkaThickness, kratkaThickness, (plansza_height + 1) * kratkaSize - kratkaThickness, kratkaColor, 0xff, 1); } } } void ui_init(int width, int height) { gra_init(width, height, 0, "OpenKropki", icon_bmp_gz, icon_bmp_gz_len); } void ui_drawscores(const int *scores, const char *msg, int msgplayercolor, const char **playernames) { char scoresstr[128]; int x, w, h; struct gra_sprite *sc[2], *msgspr = NULL; gra_getwinsize(&w, &h); /* load textures */ for (x = 0; x < 2; x++) { snprintf(scoresstr, sizeof(scoresstr), "%s: %d", playernames[x], scores[x]); sc[x] = gra_text2sprite(scoresstr, DroidSansBold_ttf, DroidSansBold_ttf_len, 20, playercolor[x+1], -1, 0xff, -1); } if ((msg != NULL) && (msg[0] != 0)) msgspr = gra_text2sprite(msg, DroidSansBold_ttf, DroidSansBold_ttf_len, 20, playercolor[msgplayercolor], -1, 0xff, -1); /* scores bar background */ gra_drawrect(0, 0, w, gra_getspriteheight(sc[0]), 0xffffffu, 0xff, 1); /* draw the message, if any */ gra_drawsprite(msgspr, (w - gra_getspritewidth(msgspr)) / 2, 1); /* draw the score for Players 1 & 2 */ for (x = 0; x < 2; x++) { if (x == 0) { gra_drawsprite(sc[x], 10, 1); } else { gra_drawsprite(sc[x], w - (gra_getspritewidth(sc[x]) + 10), 1); } } /* free textures */ gra_freesprite(sc[0]); gra_freesprite(sc[1]); gra_freesprite(msgspr); } void graph_drawKropka(int x, int y, int player, int activeflag, const int *scrollvector) { if ((player > 0) && (player < 3)) { gra_circle_filled((x+1) * kratkaSize + scrollvector[0], (y+1) * kratkaSize + scrollvector[1], kropkaRadius, playercolor[player], 0xff); if (activeflag != 0) { /* display a circle around last dot (active dot) */ int i; for (i = 0; i < kropkaActiveBorder; i++) { gra_circle((x+1) * kratkaSize + scrollvector[0], (y+1) * kratkaSize + scrollvector[1], kropkaActiveRadius - i, kropkaActiveBorderColor, 0xff); } } } } void graph_drawWall(int x1, int y1, int x2, int y2, int player, const int *scrollvector) { if ((player > 0) && (player < 3)) { gra_drawline((x1+1) * kratkaSize + scrollvector[0], (y1+1) * kratkaSize + scrollvector[1], (x2+1) * kratkaSize + scrollvector[0], (y2+1) * kratkaSize + scrollvector[1], wallThickness, playercolor[player], 0xff); } } void graph_mousemap(int mx, int my, int *dotx, int *doty, const int *scrollvector) { float x, y, x1, y1; int intx, inty; /* apply the scroll vector to x/y coordinates */ mx -= scrollvector[0]; my -= scrollvector[1]; /* compute the field coordinates */ x = mx; x -= kratkaSize; x /= kratkaSize; x1 = x + 0.5f; /* a very primitive way of rounding numbers - but works well, and it's fast */ intx = (int)x1; *dotx = intx; y = my; y -= kratkaSize; y /= kratkaSize; y1 = y + 0.5f; /* a very primitive way of rounding numbers - but works well, and it's fast */ inty = (int)y1; *doty = inty; /* perform some boundaries check, to not take into account clicks into empty spaces */ if ((intx - x < -0.3f) || (intx - x > 0.3f) || (inty - y < -0.3f) || (inty - y > 0.3f)) { *dotx = -1; *doty = -1; } } /* wait for event and fill the ev struct accordingly. this function ignores * mouse movements, unless nofilter has been set to non-zero in which case it * will return an Event_SWEEP or Event_CURSORMOVE event as soon as the mouse * moves */ void ui_getevent(struct ui_event_t *ev, int nofilter) { SDL_Event event; static int clickx = -1, clicky = -1, motion = 0; /* clear the event struct and pre-fill with Event_NONE */ memset(ev, 0, sizeof(*ev)); ev->type = Event_NONE; /* */ for (;;) { if (SDL_WaitEventTimeout(&event, 100) == 0) return; /* no event (timeout) */ if (event.type != SDL_MOUSEMOTION) break; if ((event.type == SDL_MOUSEMOTION) && (clickx >= 0)) break; if (nofilter != 0) break; } switch (event.type) { case SDL_QUIT: ev->type = Event_QUIT; return; case SDL_MOUSEBUTTONDOWN: /* when BUTTON DOWN -> remember pos to differentiate between CLICK and SWEEP later */ if ((event.button.button == SDL_BUTTON_LEFT) && (event.button.button == 1)) { clickx = event.button.x; clicky = event.button.y; } break; case SDL_MOUSEMOTION: /* check if we are dealing with sweep */ if ((event.button.button == 1) && ((abs(clickx - event.button.x) > 3) || (abs(clicky - event.button.y) > 3))) { motion = 1; ev->type = Event_SWEEP; ev->x = clickx - event.button.x; ev->y = clicky - event.button.y; ev->button = 1; clickx = event.button.x; clicky = event.button.y; } else { ev->type = Event_CURSORMOVE; ev->x = event.button.x; ev->y = event.button.y; } return; case SDL_MOUSEWHEEL: ev->type = Event_SCROLL; ev->x = event.wheel.x; ev->y = event.wheel.y; return; case SDL_MOUSEBUTTONUP: clickx = -1; /* if it was a motion, forget it */ if (motion != 0) { motion = 0; ev->type = Event_NONE; return; } /* otherwise it's a simple click */ if (event.button.button == SDL_BUTTON_LEFT) { ev->type = Event_CLICK; ev->x = event.button.x; ev->y = event.button.y; ev->button = 1; return; } if (event.button.button == SDL_BUTTON_RIGHT) { ev->type = Event_CLICK; ev->x = event.button.x; ev->y = event.button.y; ev->button = 2; return; } if (event.button.button == SDL_BUTTON_MIDDLE) { ev->type = Event_CLICK; ev->x = event.button.x; ev->y = event.button.y; ev->button = 3; return; } break; case SDL_KEYDOWN: ev->type = Event_KEYPRESS; ev->button = event.key.keysym.sym; return; } /* */ ev->type = Event_UNKNOWN; } unsigned long ui_getticks(void) { return(SDL_GetTicks()); } void ui_getmousepos(int *x, int *y) { SDL_GetMouseState(x, y); } int ui_menu(const struct ui_menu_description_t *menu, int menucount, int autofreesprites, int maxw, int maxh) { int i, w, h, res = -1; int lastmouseposy, lastmouseposx; struct ui_event_t ev = {0}; /* set mouse trackers to current position */ ui_getmousepos(&lastmouseposx, &lastmouseposy); /* (re)draw screen and wait for events */ for (;;) { int x = 0, y = 0; /* preinit in case ui_menu() gets a list where first entry has RELPOS set */ int hintposx = 0, hintposy = 0; int xmargin = 0, ymargin = 0; struct gra_sprite *hint; gra_clear(0x606070u); /* clear screen (and fill with solid color) */ gra_getwinsize(&w, &h); /* compute virtual width and height, as well as margins (if applicable) */ if (w > maxw) { xmargin = (w - maxw) / 2; w = maxw; } if (h > maxh) { ymargin = (h - maxh) / 2; h = maxh; } /* draw menu background */ gra_drawrect(xmargin, ymargin, w, h, 0xffffffu, 0xff, 1); res = -1; hint = NULL; /* draw each sprite of the menu */ for (i = 0; i < menucount; i++) { struct gra_sprite *spr = menu[i].sprite[0]; /* */ if ((menu[i].flags & UI_MENU_FLAG_XRELPOS) == 0) x = 0; if ((menu[i].flags & UI_MENU_FLAG_YRELPOS) == 0) y = 0; /* */ x += (menu[i].propxpos * w) / 100; if (menu[i].flags & UI_MENU_FLAG_XCENTER) { /* x-center sprite around its middle */ x -= gra_getspritewidth(menu[i].sprite[0]) / 2; } y += (menu[i].propypos * h) / 100; if ((menu[i].flags & UI_MENU_FLAG_YRELPOS) == 0) { y -= gra_getspriteheight(menu[i].sprite[0]) / 2; /* center y-pos at sprite middle */ } /* make sure not to draw sprites out of screen */ if (x < 0) x = 0; if (y < 0) y = 0; if (x + gra_getspritewidth(spr) > w) x = w - gra_getspritewidth(spr); if (y + gra_getspriteheight(spr) > h) y = h - gra_getspriteheight(spr); /* apply margins */ x += xmargin; y += ymargin; /* if on-mouse sprite available, determine whether or not it should be used (is mouse over it?) */ if ((menu[i].sprite[1] != NULL) && (lastmouseposx >= x) && (lastmouseposx <= x + gra_getspritewidth(spr)) && (lastmouseposy >= y) && (lastmouseposy <= y + gra_getspriteheight(spr))) { /* draw the basic sprite only if ORSPR flag is specified */ if (menu[i].flags & UI_MENU_FLAG_ORSPR) gra_drawsprite(menu[i].sprite[0], x, y); /* draw the on-mouse sprite */ gra_drawsprite(menu[i].sprite[1], x, y); res = menu[i].clickid; /* remember result value in case of a click */ /* compute the position of the mouseover hint, if defined, and remember it for later */ if (menu[i].hint != NULL) { hint = menu[i].hint; hintposx = lastmouseposx + 16; hintposy = lastmouseposy + 16; if (hintposx + gra_getspritewidth(menu[i].hint) > w) hintposx = (w - gra_getspritewidth(menu[i].hint)); if (hintposy + gra_getspriteheight(menu[i].hint) > h) hintposy = (h - gra_getspriteheight(menu[i].hint)); } } else { gra_drawsprite(spr, x, y); } /* adjust x and y so they point to the bottom right edge of the sprite, in case next sprite wants to be drawn there */ y += gra_getspriteheight(menu[i].sprite[0]) - ymargin; x += gra_getspritewidth(menu[i].sprite[0]) - xmargin; } /* display the hint now (if present), so it covers all other elements */ if (hint != NULL) gra_drawsprite(hint, hintposx, hintposy); /* refresh screen and look for events */ gra_refresh(); ui_getevent(&ev, 1); switch (ev.type) { case Event_CLICK: if (res >= 0) goto FREE_AND_RETURN; break; case Event_CURSORMOVE: /* update mouse position */ lastmouseposx = ev.x; lastmouseposy = ev.y; break; case Event_QUIT: res = UI_MENU_EXIT; goto FREE_AND_RETURN; case Event_KEYPRESS: if (ev.button == 0x1B) { /* ESC key == "back" */ res = UI_MENU_BACK; goto FREE_AND_RETURN; } break; default: break; } } FREE_AND_RETURN: if (autofreesprites) { /* free sprite resources if autofreesprites enabled */ while (--menucount >= 0) { gra_freesprite(menu[menucount].sprite[0]); gra_freesprite(menu[menucount].sprite[1]); gra_freesprite(menu[menucount].hint); } } return(res); } void ui_throwmsg(const char *msg) { int w, h, x, y, sprw, sprh, margin; struct gra_sprite *msgspr; gra_getwinsize(&w, &h); msgspr = gra_text2sprite(msg, DroidSansBold_ttf, DroidSansBold_ttf_len, 22, 0, -1, 0xff, -1); sprw = gra_getspritewidth(msgspr); sprh = gra_getspriteheight(msgspr); x = (w - sprw) / 2; y = (h - sprh) / 2; margin = sprw * 5 / 100; gra_drawrect(x - margin, y - margin, sprw + (2 * margin), sprh + (2 * margin), 0xe98c36u, 200, 1); gra_drawrect(x - margin, y - margin, sprw + (2 * margin), sprh + (2 * margin), 0x323232u, 0xff, 0); gra_drawsprite(msgspr, x, y); gra_refresh(); gra_freesprite(msgspr); } void ui_delay(unsigned int ms) { SDL_Delay((Uint32)ms); } void ui_changecursor(enum ui_mousecursor_t curid) { static SDL_Cursor *oldcurs, *curs; switch (curid) { case UI_CURSOR_WAIT: curs = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_WAIT); break; case UI_CURSOR_NORMAL: curs = SDL_CreateSystemCursor(SDL_SYSTEM_CURSOR_ARROW); break; } SDL_SetCursor(curs); if (oldcurs != NULL) SDL_FreeCursor(oldcurs); oldcurs = curs; } /* shade a diamond-shaped area around a dot */ void ui_shadediamond(int x, int y, int p, const int *scrollvector) { int x1, x2, yy, i; x1 = (x+1) * kratkaSize + scrollvector[0]; x2 = x1; yy = y * kratkaSize + scrollvector[1]; for (i = kratkaSize * 2 + 2; i > 0; i--) { if ((yy + i) & 1) continue; /* pattern 1-0-1-0-1-0... */ gra_drawline(x1, yy + i, x2, yy + i, 1, playercolor[p], 0xff); if (i > kratkaSize) { x1 -= 2; x2 += 2; } else { x1 += 2; x2 -= 2; } } } /* shade a block of kratka items */ void ui_shadeblock(int x, int y, int w, int h, int p, const int *scrollvector) { int x1, x2, yy, i; x1 = (x + 1) * kratkaSize + scrollvector[0]; x2 = x1 + (w * kratkaSize); yy = y * kratkaSize + scrollvector[1]; for (i = 0; i < (h * kratkaSize); i++) { if ((yy + i) & 1) continue; /* pattern 1-0-1-0-1-0... */ gra_drawline(x1, yy + i, x2, yy + i, 1, playercolor[p], 0xff); } }