// ======================================================================= // GUIslice library (driver layer for SDL 1.2 & 2.0) // - Calvin Hass // - https://www.impulseadventure.com/elec/guislice-gui.html // - https://github.com/ImpulseAdventure/GUIslice // ======================================================================= // // The MIT License // // Copyright 2016-2020 Calvin Hass // // 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. // // ======================================================================= /// \file GUIslice_drv_sdl.c // Compiler guard for requested driver #include "GUIslice_config.h" // Sets DRV_DISP_* #if defined(DRV_DISP_SDL1) || defined(DRV_DISP_SDL2) // ======================================================================= // Driver Layer for SDL // ======================================================================= // GUIslice library #include "GUIslice_drv_sdl.h" #include // ------------------------------------------------------------------------ // Load display & touch drivers // ------------------------------------------------------------------------ // Optionally enable SDL clean start VT workaround #if (DRV_SDL_FIX_START) #include // For O_RDONLY #include // For errno #include // For close() #include // For ioctl() #include // for KDSETMODE #include // for VT_UNLOCKSWITCH #define DRV_SDL_FIX_TTY "/dev/tty0" #endif // Define driver names #if defined(DRV_DISP_SDL1) const char* m_acDrvDisp = "SDL1"; #elif defined(DRV_DISP_SDL2) const char* m_acDrvDisp = "SDL2"; #endif #if defined(DRV_TOUCH_TSLIB) const char* m_acDrvTouch = "TSLIB"; #else const char* m_acDrvTouch = "SDL"; #endif // ======================================================================= // Public APIs to GUIslice core library // ======================================================================= // ----------------------------------------------------------------------- // Configuration Functions // ----------------------------------------------------------------------- bool gslc_DrvInit(gslc_tsGui* pGui) { // Report any debug info (before init) if enabled #if defined(DBG_DRIVER) gslc_DrvReportInfoPre(); #endif // Primary surface definitions pGui->sImgRefBkgnd = gslc_ResetImage(); // Initialize any SDL version-specific members if (pGui->pvDriver) { gslc_tsDriver* pDriver = (gslc_tsDriver*)(pGui->pvDriver); #if defined(DRV_DISP_SDL1) pDriver->pSurfScreen = NULL; pGui->bRedrawPartialEn = true; #endif #if defined(DRV_DISP_SDL2) pDriver->pWind = NULL; pDriver->pRender = NULL; // In SDL2, always need full page redraw since backbuffer // is treated as invalidated after every RenderPresent() pGui->bRedrawPartialEn = false; #endif } #if (DRV_SDL_FIX_START) // Force a clean start to SDL to workaround any bad state // left behind by a previous SDL application's failure to // clean up. // TODO: Allow compiler option to skip this workaround // TODO: Allow determination of the TTY to use gslc_DrvCleanStart(DRV_SDL_FIX_TTY); #endif // Setup SDL if (SDL_Init(SDL_INIT_VIDEO) < 0) { GSLC_DEBUG2_PRINT("ERROR: DrvInit() error in SDL_Init(): %s\n",SDL_GetError()); return false; } // Now that we have successfully initialized SDL // we need to register an exit handler so that SDL_Quit() // gets called at program termination. If we don't do this // then some types of errors/aborts will result in // the SDL environment being left in a bad state that // affects the next SDL program execution. atexit(SDL_Quit); // Report any debug info (after init) if enabled #if defined(DBG_DRIVER) gslc_DrvReportInfoPost(); #endif gslc_tsDriver* pDriver = (gslc_tsDriver*)(pGui->pvDriver); #if defined(DRV_DISP_SDL1) // Set up pGui->pSurfScreen const SDL_VideoInfo* videoInfo = SDL_GetVideoInfo(); int16_t nSystemX = videoInfo->current_w; int16_t nSystemY = videoInfo->current_h; uint8_t nBpp = videoInfo->vfmt->BitsPerPixel ; // Save a copy of the display dimensions pGui->nDispW = nSystemX; pGui->nDispH = nSystemY; pGui->nDispDepth = nBpp; #if defined(DBG_DRIVER) GSLC_DEBUG_PRINT("DBG: Video mode: %u x %u x %u bit/pixel\n", pGui->nDispW,pGui->nDispH,pGui->nDispDepth); #endif // SDL_SWSURFACE is apparently more reliable pDriver->pSurfScreen = SDL_SetVideoMode(nSystemX,nSystemY,nBpp,SDL_SWSURFACE | SDL_FULLSCREEN); if (!pDriver->pSurfScreen) { GSLC_DEBUG_PRINT("ERROR: DrvInit() error in SDL_SetVideoMode(): %s\n",SDL_GetError()); return false; } #endif #if defined(DRV_DISP_SDL2) // Default to using OpenGL rendering engine and full-screen // When using SDL_WINDOW_FULLSCREEN, the width & height dimensions are ignored pDriver->pWind = SDL_CreateWindow("GUIslice",SDL_WINDOWPOS_UNDEFINED,SDL_WINDOWPOS_UNDEFINED, 0,0,SDL_WINDOW_FULLSCREEN | SDL_WINDOW_OPENGL); if (!pDriver->pWind) { GSLC_DEBUG_PRINT("ERROR: DrvInit() error in SDL_CreateWindow(): %s\n",SDL_GetError()); return false; } // Save a copy of the display dimensions from the created fullscreen window int nSystemX,nSystemY; SDL_GetWindowSize(pDriver->pWind,&nSystemX,&nSystemY); pGui->nDispW = nSystemX; pGui->nDispH = nSystemY; // Fetch the bit depth of the first display SDL_DisplayMode sDispMode; int nRet = SDL_GetCurrentDisplayMode(0,&sDispMode); if (nRet != 0) { // TODO: ERROR return false; } pGui->nDispDepth = SDL_BITSPERPIXEL(sDispMode.format); #if defined(DBG_DRIVER) GSLC_DEBUG_PRINT("DBG: Video mode: %u x %u x %u bit/pixel\n", pGui->nDispW,pGui->nDispH,pGui->nDispDepth); #endif // Create renderer // TODO: Resolve performance with "accelerated" renderer (SDL_RENDERER_ACCELERATED. // For now, use software renderer (SDL_RENDERER_SOFTWARE) which appears // to be much faster. // If we are sure that the OpenGL ES 2.0 driver has been installed, we // can request that SDL use this as a renderer. Disable this for now. // SDL_SetHint(SDL_HINT_RENDER_DRIVER,"opengles2"); #if (DRV_SDL_RENDER_ACCEL) pDriver->pRender = SDL_CreateRenderer(pDriver->pWind,-1,SDL_RENDERER_ACCELERATED); #else pDriver->pRender = SDL_CreateRenderer(pDriver->pWind,-1,SDL_RENDERER_SOFTWARE); #endif if (!pDriver->pRender) { GSLC_DEBUG_PRINT("ERROR: DrvInit() error in SDL_CreateRenderer(): %s\n",SDL_GetError()); return false; } #if defined(DBG_DRIVER) SDL_RendererInfo sRendInfo; SDL_GetRendererInfo(pDriver->pRender,&sRendInfo); GSLC_DEBUG_PRINT("DBG: Renderer selected: [%s]\n", sRendInfo.name); #endif // If we wanted to support scaling of the renderer, we would call // SDL_RenderSetLogicalSize() here. For now, don't scale. #endif // Initialize font engine if (TTF_Init() == -1) { GSLC_DEBUG_PRINT("ERROR: DrvInit(%s) error in TTF_Init()\n",""); return false; } // Since the mouse cursor is based on SDL coords which are badly // scaled when in touch mode, we will disable the mouse pointer. // Note that the SDL coords seem to be OK when in actual mouse mode. #if (!DRV_SDL_MOUSE_SHOW) SDL_ShowCursor(SDL_DISABLE); #endif return true; } void* gslc_DrvGetDriverDisp(gslc_tsGui* pGui) { return (pGui->pvDriver); } void gslc_DrvDestruct(gslc_tsGui* pGui) { #if defined(DRV_DISP_SDL2) gslc_tsDriver* pDriver = (gslc_tsDriver*)(pGui->pvDriver); if (pDriver->pRender) { SDL_DestroyRenderer(pDriver->pRender); pDriver->pRender = NULL; } if (pDriver->pWind) { SDL_DestroyWindow(pDriver->pWind); pDriver->pWind = NULL; } #endif // Close down SDL SDL_Quit(); } const char* gslc_DrvGetNameDisp(gslc_tsGui* pGui) { return m_acDrvDisp; } const char* gslc_DrvGetNameTouch(gslc_tsGui* pGui) { return m_acDrvTouch; } // ----------------------------------------------------------------------- // Image/surface handling Functions // ----------------------------------------------------------------------- void* gslc_DrvLoadImage(gslc_tsGui* pGui,gslc_tsImgRef sImgRef) { if (sImgRef.eImgFlags == GSLC_IMGREF_NONE) { return NULL; } else if ((sImgRef.eImgFlags & GSLC_IMGREF_SRC) == GSLC_IMGREF_SRC_SD) { // Load image from SD card // TODO: Not yet supported return NULL; } else if ((sImgRef.eImgFlags & GSLC_IMGREF_SRC) == GSLC_IMGREF_SRC_RAM) { // Load image from RAM // TODO: Not yet supported return NULL; } else if ((sImgRef.eImgFlags & GSLC_IMGREF_SRC) == GSLC_IMGREF_SRC_PROG) { // Load image from FLASH // TODO: Not yet supported return NULL; } else if ((sImgRef.eImgFlags & GSLC_IMGREF_SRC) == GSLC_IMGREF_SRC_FILE) { // Load image from file system const char* pStrFname = sImgRef.pFname; // Pointer to the surface image that was loaded SDL_Surface* pSurfLoaded = NULL; // Load the image // - The SDL_LoadBMP() routine should be able to handle a multitude of // BMP format types. // - TODO: Check (eImgFlags & GSLC_IMGREF_FMT) to ensure type is supported pSurfLoaded = SDL_LoadBMP(pStrFname); // Confirm that the image loaded correctly if (pSurfLoaded == NULL) { GSLC_DEBUG2_PRINT("ERROR: DrvLoadBmpFile(%s) failed: %s\n",pStrFname,SDL_GetError()); return NULL; } #if defined(DRV_DISP_SDL1) //Create an optimized surface //The optimized surface that will be used SDL_Surface* pSurfOptimized = SDL_DisplayFormat( pSurfLoaded ); //Free the old surface SDL_FreeSurface( pSurfLoaded ); //If the surface was optimized if( pSurfOptimized != NULL ) { // Support optional transparency if (GSLC_BMP_TRANS_EN) { // Color key surface // - Use transparency color key defined in BMP_TRANS_RGB SDL_SetColorKey( pSurfOptimized, SDL_SRCCOLORKEY, SDL_MapRGB( pSurfOptimized->format, GSLC_BMP_TRANS_RGB ) ); } // GSLC_BMP_TRANS_EN } //Return the optimized surface return (void*)(pSurfOptimized); #endif #if defined(DRV_DISP_SDL2) gslc_tsDriver* pDriver = (gslc_tsDriver*)(pGui->pvDriver); SDL_Texture* pTex = NULL; if( pSurfLoaded != NULL ) { // Support optional transparency if (GSLC_BMP_TRANS_EN) { // Color key surface // - Use transparency color key defined in BMP_TRANS_RGB // - SDL2 passes SDL_TRUE instead of SDL_SRCCOLORKEY SDL_SetColorKey( pSurfLoaded, SDL_TRUE, SDL_MapRGB( pSurfLoaded->format, GSLC_BMP_TRANS_RGB ) ); } // GSLC_BMP_TRANS_EN } pTex = (void*)SDL_CreateTextureFromSurface(pDriver->pRender,pSurfLoaded); if (pTex == NULL) { GSLC_DEBUG2_PRINT("ERROR: DrvLoadBmp(%s) SDL_CreateTextureFromSurface() failed: %s\n",pStrFname,SDL_GetError()); return NULL; } // Dispose of surface SDL_FreeSurface(pSurfLoaded); pSurfLoaded = NULL; //Return the texture return (void*)pTex; #endif } // eImgFlags // If reached here, it is an error return NULL; } bool gslc_DrvSetBkgndImage(gslc_tsGui* pGui,gslc_tsImgRef sImgRef) { // Dispose of previous background if (pGui->sImgRefBkgnd.eImgFlags != GSLC_IMGREF_NONE) { gslc_DrvImageDestruct(pGui->sImgRefBkgnd.pvImgRaw); pGui->sImgRefBkgnd = gslc_ResetImage(); } pGui->sImgRefBkgnd = sImgRef; pGui->sImgRefBkgnd.pvImgRaw = gslc_DrvLoadImage(pGui,sImgRef); if (pGui->sImgRefBkgnd.pvImgRaw == NULL) { GSLC_DEBUG2_PRINT("ERROR: DrvSetBkgndImage(%s) failed\n",""); return false; } return true; } bool gslc_DrvSetBkgndColor(gslc_tsGui* pGui,gslc_tsColor nCol) { // Dispose of previous background if (pGui->sImgRefBkgnd.eImgFlags != GSLC_IMGREF_NONE) { gslc_DrvImageDestruct(pGui->sImgRefBkgnd.pvImgRaw); pGui->sImgRefBkgnd = gslc_ResetImage(); } SDL_Surface* pSurfBkgnd = NULL; uint16_t nScreenW = pGui->nDispW; uint16_t nScreenH = pGui->nDispH; uint8_t nBpp = pGui->nDispDepth; // Create surface that we can draw into // - For the masks, we can pass 0 to get defaults #if defined(DRV_DISP_SDL1) pSurfBkgnd = SDL_CreateRGBSurface(SDL_SWSURFACE,nScreenW,nScreenH,nBpp,0,0,0,0); #endif #if defined(DRV_DISP_SDL2) gslc_tsDriver* pDriver = (gslc_tsDriver*)(pGui->pvDriver); // - In SDL2, the flags field is ignored, so set to 0 pSurfBkgnd = SDL_CreateRGBSurface(0,nScreenW,nScreenH,nBpp,0,0,0,0); #endif if (pSurfBkgnd == NULL) { GSLC_DEBUG2_PRINT("ERROR: DrvSetBkgndColor() SDL_CreateRGBSurface failed: %s\n",SDL_GetError()); return false; } // Fill with requested color SDL_FillRect(pSurfBkgnd,NULL, SDL_MapRGB(pSurfBkgnd->format,nCol.r,nCol.g,nCol.b)); #if defined(DRV_DISP_SDL1) // Save surface into GUI struct pGui->sImgRefBkgnd.pvImgRaw = (void*)(pSurfBkgnd); #endif #if defined(DRV_DISP_SDL2) // Convert to texture and save into GUI struct pGui->sImgRefBkgnd.pvImgRaw = (void*)SDL_CreateTextureFromSurface(pDriver->pRender,pSurfBkgnd); if (pGui->sImgRefBkgnd.pvImgRaw == NULL) { GSLC_DEBUG2_PRINT("ERROR: DrvSetBkgndColor() SDL_CreateTextureFromSurface failed: %s\n",SDL_GetError()); return false; } // Dispose of temporary surface SDL_FreeSurface(pSurfBkgnd); pSurfBkgnd = NULL; #endif return true; } bool gslc_DrvSetElemImageNorm(gslc_tsGui* pGui,gslc_tsElem* pElem,gslc_tsImgRef sImgRef) { // Dispose of previous image if (pElem->sImgRefNorm.eImgFlags != GSLC_IMGREF_NONE) { gslc_DrvImageDestruct(pElem->sImgRefNorm.pvImgRaw); pElem->sImgRefNorm = gslc_ResetImage(); } pElem->sImgRefNorm = sImgRef; pElem->sImgRefNorm.pvImgRaw = gslc_DrvLoadImage(pGui,sImgRef); if (pElem->sImgRefNorm.pvImgRaw == NULL) { GSLC_DEBUG2_PRINT("ERROR: DrvSetElemImageNorm(%s) failed\n",""); return false; } return true; } bool gslc_DrvSetElemImageGlow(gslc_tsGui* pGui,gslc_tsElem* pElem,gslc_tsImgRef sImgRef) { // Dispose of previous image if (pElem->sImgRefGlow.eImgFlags != GSLC_IMGREF_NONE) { gslc_DrvImageDestruct(pElem->sImgRefGlow.pvImgRaw); pElem->sImgRefGlow = gslc_ResetImage(); } pElem->sImgRefGlow = sImgRef; pElem->sImgRefGlow.pvImgRaw = gslc_DrvLoadImage(pGui,sImgRef); if (pElem->sImgRefGlow.pvImgRaw == NULL) { GSLC_DEBUG2_PRINT("ERROR: DrvSetElemImageGlow(%s) failed\n",""); return false; } return true; } void gslc_DrvImageDestruct(void* pvImg) { if (pvImg == NULL) { return; } #if defined(DRV_DISP_SDL1) SDL_FreeSurface((SDL_Surface*)pvImg); #endif #if defined(DRV_DISP_SDL2) SDL_DestroyTexture((SDL_Texture*)pvImg); #endif } bool gslc_DrvSetClipRect(gslc_tsGui* pGui,gslc_tsRect* pRect) { gslc_tsDriver* pDriver = (gslc_tsDriver*)(pGui->pvDriver); #if defined(DRV_DISP_SDL1) SDL_Surface* pScreen = pDriver->pSurfScreen; if (pRect == NULL) { SDL_SetClipRect(pScreen,NULL); } else { SDL_Rect rSRect = gslc_DrvAdaptRect(*pRect); SDL_SetClipRect(pScreen,&rSRect); } return true; #endif #if defined(DRV_DISP_SDL2) SDL_Renderer* pRender = pDriver->pRender; if (pRect == NULL) { SDL_RenderSetClipRect(pRender,NULL); } else { SDL_Rect rSRect = gslc_DrvAdaptRect(*pRect); SDL_RenderSetClipRect(pRender,&rSRect); } return true; #endif } // ----------------------------------------------------------------------- // Font handling Functions // ----------------------------------------------------------------------- const void* gslc_DrvFontAdd(gslc_teFontRefType eFontRefType,const void* pvFontRef,uint16_t nFontSz) { // UNIX/SDL mode currently only supports font definitions from files if (eFontRefType != GSLC_FONTREF_FNAME) { GSLC_DEBUG_PRINT("ERROR: DrvFontAdd(%s) failed - SDL mode only supports file-based fonts\n",""); return NULL; } TTF_Font* pFont; // In the case of SDL, the pvFontRef is a string that defines the // file path to the font file pFont = TTF_OpenFont((const char*)pvFontRef,nFontSz); if (pFont == NULL) { GSLC_DEBUG_PRINT("ERROR: DrvFontAdd(%s) failed in TTF_OpenFont\n",pvFontRef); return NULL; } return (const void*)pFont; } void gslc_DrvFontsDestruct(gslc_tsGui* pGui) { uint16_t nFontInd; TTF_Font* pFont = NULL; for (nFontInd=0;nFontIndnFontCnt;nFontInd++) { if (pGui->asFont[nFontInd].pvFont != NULL) { pFont = (TTF_Font*)(pGui->asFont[nFontInd].pvFont); TTF_CloseFont(pFont); pGui->asFont[nFontInd].pvFont = NULL; } } pGui->nFontCnt = 0; TTF_Quit(); } bool gslc_DrvGetTxtSize(gslc_tsGui* pGui,gslc_tsFont* pFont,const char* pStr,gslc_teTxtFlags eTxtFlags, int16_t* pnTxtX,int16_t* pnTxtY,uint16_t* pnTxtSzW,uint16_t* pnTxtSzH) { // NOTE: Shouldn't need to process eTxtFlags int32_t nTxtSzW,nTxtSzH; TTF_Font* pDrvFont = (TTF_Font*)(pFont->pvFont); if (!pDrvFont) { return false; } if ((eTxtFlags & GSLC_TXT_ENC) == GSLC_TXT_ENC_UTF8) { TTF_SizeUTF8(pDrvFont,pStr,&nTxtSzW,&nTxtSzH); } else { TTF_SizeText(pDrvFont,pStr,&nTxtSzW,&nTxtSzH); } *pnTxtSzW = (uint16_t)nTxtSzW; *pnTxtSzH = (uint16_t)nTxtSzH; // No offset coordinates used *pnTxtX = 0; *pnTxtY = 0; return true; } // NOTE: SDL driver is compiled as pure C, so can't use default parameters. // Other drivers have specified colBg as a default, but so far no callers // are depending on the default. // TODO: Update DrvDrawTxt() and DrvDrawTxtAlign() APIs for all drivers to // no longer use the default param for consistency. bool gslc_DrvDrawTxt(gslc_tsGui* pGui,int16_t nTxtX,int16_t nTxtY,gslc_tsFont* pFont,const char* pStr,gslc_teTxtFlags eTxtFlags,gslc_tsColor colTxt, gslc_tsColor colBg) { if ((pGui == NULL) || (pFont == NULL)) { GSLC_DEBUG2_PRINT("ERROR: DrvDrawTxt(%s) with NULL ptr\n",""); return false; } if ((pStr == NULL) || (pStr[0] == '\0')) { return true; } gslc_tsDriver* pDriver = (gslc_tsDriver*)(pGui->pvDriver); SDL_Surface* pSurfTxt = NULL; TTF_Font* pDrvFont = (TTF_Font*)(pFont->pvFont); if (!pDrvFont) { return false; } if ((eTxtFlags & GSLC_TXT_ENC) == GSLC_TXT_ENC_UTF8) { pSurfTxt = TTF_RenderUTF8_Blended(pDrvFont,pStr,gslc_DrvAdaptColor(colTxt)); } else { pSurfTxt = TTF_RenderText_Blended(pDrvFont,pStr,gslc_DrvAdaptColor(colTxt)); } if (pSurfTxt == NULL) { GSLC_DEBUG2_PRINT("ERROR: DrvDrawTxt() failed in TTF_RenderText_Solid() (%s)\n",pStr); return false; } #if defined(DRV_DISP_SDL1) gslc_DrvPasteSurface(pGui,nTxtX,nTxtY,pSurfTxt,pDriver->pSurfScreen); #endif #if defined(DRV_DISP_SDL2) SDL_Rect rRect = (SDL_Rect){nTxtX,nTxtY,pSurfTxt->w,pSurfTxt->h}; SDL_Renderer* pRender = pDriver->pRender; SDL_Texture* pTex = SDL_CreateTextureFromSurface(pRender,pSurfTxt); if (pTex == NULL) { GSLC_DEBUG2_PRINT("ERROR: DrvDrawTxt() error in SDL_CreateTextureFromSurface(): %s\n",SDL_GetError()); return false; } SDL_RenderCopy(pRender,pTex,NULL,&rRect); // Destroy texture // TODO: Consider if worth optimizing by retaining texture in case // we need to redraw it without changing content SDL_DestroyTexture(pTex); pTex = NULL; #endif // Dispose of temporary surface if (pSurfTxt != NULL) { SDL_FreeSurface(pSurfTxt); pSurfTxt = NULL; } return true; } // ----------------------------------------------------------------------- // Screen Management Functions // ----------------------------------------------------------------------- void gslc_DrvPageFlipNow(gslc_tsGui* pGui) { gslc_tsDriver* pDriver = (gslc_tsDriver*)(pGui->pvDriver); #if defined(DRV_DISP_SDL1) SDL_Surface* pScreen = pDriver->pSurfScreen; SDL_Flip(pScreen); #endif #if defined(DRV_DISP_SDL2) SDL_Renderer* pRender = pDriver->pRender; if (pRender) { // Flip the offscreen buffer so we can display our drawing output SDL_RenderPresent(pRender); // Clear the drawing before any new drawing occurs SDL_SetRenderDrawColor(pRender,0x00,0x00,0x00,0xFF); SDL_RenderClear(pRender); } #endif } // ----------------------------------------------------------------------- // Graphics Primitives Functions // ----------------------------------------------------------------------- bool gslc_DrvDrawPoint(gslc_tsGui* pGui,int16_t nX,int16_t nY,gslc_tsColor nCol) { #if defined(DRV_DISP_SDL1) if (gslc_DrvScreenLock(pGui)) { uint32_t nColRaw = gslc_DrvAdaptColorRaw(pGui,nCol); gslc_DrvDrawSetPixelRaw(pGui,nX,nY,nColRaw); gslc_DrvScreenUnlock(pGui); } #endif #if defined(DRV_DISP_SDL2) gslc_tsDriver* pDriver = (gslc_tsDriver*)(pGui->pvDriver); SDL_Renderer* pRender = pDriver->pRender; SDL_SetRenderDrawColor(pRender,nCol.r,nCol.g,nCol.b,255); // Call SDL optimized routine SDL_RenderDrawPoint(pRender,nX,nY); #endif return true; } bool gslc_DrvDrawPoints(gslc_tsGui* pGui,gslc_tsPt* asPt,uint16_t nNumPt,gslc_tsColor nCol) { #if defined(DRV_DISP_SDL1) uint16_t nIndPt; if (gslc_DrvScreenLock(pGui)) { uint32_t nColRaw = gslc_DrvAdaptColorRaw(pGui,nCol); for (nIndPt=0;nIndPtpvDriver); SDL_Renderer* pRender = pDriver->pRender; // NOTE: gslc_tsPt is defined to have the same layout as SDL_Point // so we simply typecast it here. This saves us from having // to perform any malloc() and type conversion. SDL_SetRenderDrawColor(pRender,nCol.r,nCol.g,nCol.b,255); // Call SDL optimized routine SDL_RenderDrawPoints(pRender,(SDL_Point*)asPt,(int)nNumPt); #endif return true; } bool gslc_DrvDrawFillRect(gslc_tsGui* pGui,gslc_tsRect rRect,gslc_tsColor nCol) { gslc_tsDriver* pDriver = (gslc_tsDriver*)(pGui->pvDriver); #if defined(DRV_DISP_SDL1) // Typecast SDL_Rect rSRect = gslc_DrvAdaptRect(rRect); SDL_Surface* pScreen = pDriver->pSurfScreen; // Call SDL optimized routine SDL_FillRect(pScreen,&rSRect, SDL_MapRGB(pScreen->format,nCol.r,nCol.g,nCol.b)); #endif #if defined(DRV_DISP_SDL2) SDL_Renderer* pRender = pDriver->pRender; SDL_SetRenderDrawColor(pRender,nCol.r,nCol.g,nCol.b,255); // Call SDL optimized routine SDL_Rect rRectSdl; rRectSdl = gslc_DrvAdaptRect(rRect); SDL_RenderFillRect(pRender,&rRectSdl); #endif return true; } bool gslc_DrvDrawFrameRect(gslc_tsGui* pGui,gslc_tsRect rRect,gslc_tsColor nCol) { #if defined(DRV_DISP_SDL1) return false; #endif #if defined(DRV_DISP_SDL2) gslc_tsDriver* pDriver = (gslc_tsDriver*)(pGui->pvDriver); SDL_Renderer* pRender = pDriver->pRender; SDL_SetRenderDrawColor(pRender,nCol.r,nCol.g,nCol.b,255); // Call SDL optimized routine SDL_Rect rRectSdl; rRectSdl = gslc_DrvAdaptRect(rRect); SDL_RenderDrawRect(pRender,&rRectSdl); return true; #endif } bool gslc_DrvDrawLine(gslc_tsGui* pGui,int16_t nX0,int16_t nY0,int16_t nX1,int16_t nY1,gslc_tsColor nCol) { #if defined(DRV_DISP_SDL1) // ERROR return false; #endif #if defined(DRV_DISP_SDL2) gslc_tsDriver* pDriver = (gslc_tsDriver*)(pGui->pvDriver); SDL_Renderer* pRender = pDriver->pRender; SDL_SetRenderDrawColor(pRender,nCol.r,nCol.g,nCol.b,255); // Call SDL optimized routine SDL_RenderDrawLine(pRender,nX0,nY0,nX1,nY1); return true; #endif } bool gslc_DrvDrawImage(gslc_tsGui* pGui,int16_t nDstX,int16_t nDstY,gslc_tsImgRef sImgRef) { if (pGui == NULL) { GSLC_DEBUG2_PRINT("ERROR: DrvDrawImage(%s) with NULL ptr\n",""); return false; } // GUIslice adapter library for SDL always pre-loads // surfaces / textures before calling DrvDrawImage(), so // we just need to confirm that the raw image data is defined. void* pImage = sImgRef.pvImgRaw; if (pImage == NULL) { GSLC_DEBUG2_PRINT("ERROR: DrvDrawImage(%s) with NULL pvImgRaw\n",""); return false; } gslc_tsDriver* pDriver = (gslc_tsDriver*)(pGui->pvDriver); #if defined(DRV_DISP_SDL1) gslc_DrvPasteSurface(pGui,nDstX,nDstY,pImage,pDriver->pSurfScreen); #endif #if defined(DRV_DISP_SDL2) SDL_Renderer* pRender = pDriver->pRender; SDL_Texture* pTex = (SDL_Texture*)pImage; // Determine dest rect based on source texture dimensions and parameterized offset SDL_Rect rDest; rDest.x = nDstX; rDest.y = nDstY; SDL_QueryTexture(pTex,NULL,NULL,&rDest.w,&rDest.h); // Default to copying all of source texture rect by specifying NULL SDL_RenderCopy(pRender,pTex,NULL,&rDest); #endif return true; } /// NOTE: Background image is stored in pGui->sImgRefBkgnd void gslc_DrvDrawBkgnd(gslc_tsGui* pGui) { if (pGui == NULL) { GSLC_DEBUG2_PRINT("ERROR: DrvDrawBkgnd(%s) with NULL ptr\n",""); return; } // GUIslice adapter library for SDL always pre-loads // surfaces / textures before calling DrvDrawBkgnd(), so // we just need to confirm that the raw image data is defined. void* pImage = pGui->sImgRefBkgnd.pvImgRaw; if (pImage == NULL) { GSLC_DEBUG2_PRINT("ERROR: DrvDrawBkgnd(%s) with NULL pvImgRaw\n",""); // Since the image load failed, resort to black background gslc_DrvSetBkgndColor(pGui,GSLC_COL_BLACK); return; } gslc_tsDriver* pDriver = (gslc_tsDriver*)(pGui->pvDriver); #if defined(DRV_DISP_SDL1) gslc_DrvPasteSurface(pGui,0,0,pGui->sImgRefBkgnd.pvImgRaw,pDriver->pSurfScreen); #endif #if defined(DRV_DISP_SDL2) SDL_Renderer* pRender = pDriver->pRender; SDL_Texture* pTex = (SDL_Texture*)(pGui->sImgRefBkgnd.pvImgRaw); // Determine destination rect // TODO: Support other background modes such as: // - Single Corner Unscaled // - Single Centered Unscaled // - Single Centered Scaled // - Tiled Corner Unscaled // For "single centered scaled", we fetch entire viewport SDL_Rect rDest; SDL_RenderGetViewport(pDriver->pRender,&rDest); rDest.x = 0; rDest.y = 0; // Default to copying all of source texture rect by specifying NULL SDL_RenderCopy(pRender,pTex,NULL,&rDest); #endif } // ------------------------------------------------------------------------ // Touch Functions (via SDL) // ------------------------------------------------------------------------ // POST: // - pDriver->pTsDev mapped to touchscreen device bool gslc_DrvInitTouch(gslc_tsGui* pGui,const char* acDev) { if (pGui == NULL) { GSLC_DEBUG2_PRINT("ERROR: DrvInitTouch(%s) called with NULL ptr\n",""); return false; } // Perform any driver-specific touchscreen init here #if defined(DRV_TOUCH_TSLIB) // Assign default gslc_tsDriver* pDriver = (gslc_tsDriver*)(pGui->pvDriver); pDriver->pTsDev = NULL; #endif // Nothing further to do with SDL driver return true; } void* gslc_DrvGetDriverTouch(gslc_tsGui* pGui) { return NULL; } bool gslc_DrvGetTouch(gslc_tsGui* pGui,int16_t* pnX,int16_t* pnY,uint16_t* pnPress,gslc_teInputRawEvent* peInputEvent,int16_t* pnInputVal) { if (pGui == NULL) { GSLC_DEBUG2_PRINT("ERROR: DrvGetTouch(%s) called with NULL ptr\n",""); return false; } // Use SDL for touch events bool bRet = false; SDL_Event sEvent; int32_t nX,nY; int16_t nKeyVal; *peInputEvent = GSLC_INPUT_NONE; if (SDL_PollEvent(&sEvent)) { nKeyVal = (int16_t)(sEvent.key.keysym.sym); // Handle Key presses if (sEvent.type == SDL_KEYDOWN) { #if (GSLC_FEATURE_INPUT) *peInputEvent = GSLC_INPUT_KEY_DOWN; *pnInputVal = nKeyVal; bRet = true; #endif } else if (sEvent.type == SDL_KEYUP) { #if (GSLC_FEATURE_INPUT) *peInputEvent = GSLC_INPUT_KEY_UP; *pnInputVal = nKeyVal; bRet = true; #endif // Handle Touch / Mouse presses } else if (sEvent.type == SDL_MOUSEMOTION) { #if defined(DRV_DISP_SDL1) SDL_GetMouseState(&nX,&nY); #elif defined(DRV_DISP_SDL2) nX = sEvent.button.x; nY = sEvent.button.y; #endif *pnX = (int16_t)nX; *pnY = (int16_t)nY; // For MOUSEMOTION, we want to return the previous state of // the touch pressure in pnPress as MOUSEMOTION is called during // both mouse up and mouse down states. // Note that we can't simply leave pnPress as-is since it // doesn't retain its state in the caller. *pnPress = pGui->nTouchLastPress; *peInputEvent = GSLC_INPUT_TOUCH; bRet = true; } else if (sEvent.type == SDL_MOUSEBUTTONDOWN) { #if defined(DRV_DISP_SDL1) SDL_GetMouseState(&nX,&nY); #elif defined(DRV_DISP_SDL2) nX = sEvent.button.x; nY = sEvent.button.y; #endif *pnX = (int16_t)nX; *pnY = (int16_t)nY; (*pnPress) = 1; *peInputEvent = GSLC_INPUT_TOUCH; bRet = true; } else if (sEvent.type == SDL_MOUSEBUTTONUP) { #if defined(DRV_DISP_SDL1) SDL_GetMouseState(&nX,&nY); #elif defined(DRV_DISP_SDL2) nX = sEvent.motion.x; nY = sEvent.motion.y; #endif *pnX = (int16_t)nX; *pnY = (int16_t)nY; (*pnPress) = 0; *peInputEvent = GSLC_INPUT_TOUCH; bRet = true; // SDL2 defines touch events instead of reusing mouse events #if defined(DRV_DISP_SDL2) } else if (sEvent.type == SDL_FINGERMOTION) { *pnX = (int16_t)(sEvent.tfinger.x); *pnY = (int16_t)(sEvent.tfinger.y); // For FINGERMOTION, we want to return the previous state of // the touch pressure in pnPress as FINGERMOTION is called during // both up and down states. // Note that we can't simply leave pnPress as-is since it // doesn't retain its state in the caller. *pnPress = pGui->nTouchLastPress; *peInputEvent = GSLC_INPUT_TOUCH; bRet = true; } else if (sEvent.type == SDL_FINGERDOWN) { *pnX = (int16_t)(sEvent.tfinger.x); *pnY = (int16_t)(sEvent.tfinger.y); (*pnPress) = 1; *peInputEvent = GSLC_INPUT_TOUCH; bRet = true; } else if (sEvent.type == SDL_FINGERUP) { *pnX = (int16_t)(sEvent.tfinger.x); *pnY = (int16_t)(sEvent.tfinger.y); (*pnPress) = 0; *peInputEvent = GSLC_INPUT_TOUCH; bRet = true; #endif // DRV_DISP_SDL2 } } // SDL_PollEvent() return bRet; } /// Change display rotation and any associated touch orientation bool gslc_DrvRotate(gslc_tsGui* pGui, uint8_t nRotation) { // TODO: Implement support for display rotation GSLC_DEBUG2_PRINT("ERROR: DrvRotate(%s) not supported in current DRV_DISP_SDL* mode yet\n",""); return false; } // ======================================================================= // Private Functions // ======================================================================= // ----------------------------------------------------------------------- // Configuration Functions // ----------------------------------------------------------------------- // Unfortunately, SDL has an issue wherein if a previous // SDL program execution aborts before cleaning up properly (eg. // with SDL_Quit) then the next time SDL_SetVideoMode // is called, it may hang. // // The following code attempts to work around this sensitivity // in SDL, making the SDL_SetVideoMode call more robust. // // DETAILS: // - If an SDL program exits without first calling the cleanup // routines (ie. in SDL_Quit() ), the terminal may be left in // KD_GRAPHICS mode. // - When another SDL program is started and attempts to call // SDL_SetVideoMode(), program execution may hang in // FB_EnterGraphicsMode() during the wait on switch to // KD_GRAPHICS mode. Since we are already in KD_GRAPHICS // this event never occurs. // - This workaround unlocks the switch (which was set at the // end of a previous call to FB_EnterGraphicsMode) and then // forces the terminal to KD_TEXT mode. // - By starting in text mode, we should then observe a VT // switch event as we attempt to transition to graphics mode // in the call to SDL_SetVideoMode(). bool gslc_DrvCleanStart(const char* sTTY) { #if (DRV_SDL_FIX_START) int nFD = -1; int nRet = false; nFD = open(sTTY, O_RDONLY, 0); if (nFD < 0) { GSLC_DEBUG2_PRINT( "ERROR: DrvCleanStart() failed to open console (%s): %s\n", sTTY,strerror(errno)); return false; } nRet = ioctl(nFD, VT_UNLOCKSWITCH, 1); if (nRet != false) { GSLC_DEBUG2_PRINT( "ERROR: DrvCleanStart() failed to unlock console (%s): %s\n", sTTY,strerror(errno)); return false; } nRet = ioctl(nFD, KDSETMODE, KD_TEXT); if (nRet != 0) { GSLC_DEBUG2_PRINT( "ERROR: DrvCleanStart() failed to set text mode (%s): %s\n", sTTY,strerror(errno)); return false; } close(nFD); #endif return true; } // Report some SDL debug info void gslc_DrvReportInfoPre() { #if defined(DRV_DISP_SDL1) #elif defined(DRV_DISP_SDL2) int16_t nDriverInd = 0; int16_t nNumDrivers = 0; // Video driver info const char* pDriverName = NULL; bool bDriverOk = false; nNumDrivers = SDL_GetNumVideoDrivers(); for (nDriverInd=0;nDriverIndpvDriver); SDL_Surface* pScreen = pDriver->pSurfScreen; return SDL_MapRGB(pScreen->format,nCol.r,nCol.g,nCol.b); } // SDL1 requires direct pixel access as there is no "draw point / pixel" // function. SDL2 has native access for point/pixel drawing so there is // no need to access the pixel map directly. bool gslc_DrvScreenLock(gslc_tsGui* pGui) { if (pGui == NULL) { GSLC_DEBUG2_PRINT("ERROR: DrvScreenLock(%s) called with NULL ptr\n",""); return false; } gslc_tsDriver* pDriver = (gslc_tsDriver*)(pGui->pvDriver); // Typecast SDL_Surface* pScreen = pDriver->pSurfScreen; if (SDL_MUSTLOCK(pScreen)) { if (SDL_LockSurface(pScreen) < 0) { GSLC_DEBUG2_PRINT("ERROR: DrvScreenLock() can't lock screen: %s\n",SDL_GetError()); return false; } } return true; } void gslc_DrvScreenUnlock(gslc_tsGui* pGui) { if (pGui == NULL) { GSLC_DEBUG2_PRINT("ERROR: DrvScreenUnlock(%s) called with NULL ptr\n",""); return; } gslc_tsDriver* pDriver = (gslc_tsDriver*)(pGui->pvDriver); // Typecast SDL_Surface* pScreen = pDriver->pSurfScreen; if (SDL_MUSTLOCK(pScreen)) { SDL_UnlockSurface(pScreen); } } // - Based on code from: // - https://www.libsdl.org/release/SDL-1.2.15/docs/html/guidevideo.html uint32_t gslc_DrvDrawGetPixelRaw(gslc_tsGui* pGui, int16_t nX, int16_t nY) { if (pGui == NULL) { GSLC_DEBUG2_PRINT("ERROR: DrvDrawGetPixelRaw(%s) called with NULL ptr\n",""); return 0; } gslc_tsDriver* pDriver = (gslc_tsDriver*)(pGui->pvDriver); SDL_Surface* pScreen = pDriver->pSurfScreen; if (pScreen == NULL) { GSLC_DEBUG2_PRINT("ERROR: DrvDrawGetPixelRaw(%s) screen surface NULL\n",""); return 0; } int nBpp = pScreen->format->BytesPerPixel; // Handle any range violations for entire surface if ( (nX < 0) || (nX >= pScreen->w) || (nY < 0) || (nY >= pScreen->h) ) { // ERROR GSLC_DEBUG2_PRINT("ERROR: DrvDrawGetPixelRaw() out of range (%i,%i)\n",nX,nY); return 0; } // Here pPixel is the address to the pixel we want to get uint8_t *pPixel = (uint8_t *)pScreen->pixels + nY * pScreen->pitch + nX * nBpp; switch(nBpp) { case 1: return *pPixel; case 2: return *(uint16_t *)pPixel; case 3: if (SDL_BYTEORDER == SDL_BIG_ENDIAN) return pPixel[0] << 16 | pPixel[1] << 8 | pPixel[2]; else return pPixel[0] | pPixel[1] << 8 | pPixel[2] << 16; case 4: return *(uint32_t *)pPixel; default: return 0; // shouldn't happen, but avoids warnings } } // - Based on code from: // - https://www.libsdl.org/release/SDL-1.2.15/docs/html/guidevideo.html // - Added range checks from surface clipping rect void gslc_DrvDrawSetPixelRaw(gslc_tsGui* pGui,int16_t nX, int16_t nY, uint32_t nPixelVal) { if (pGui == NULL) { GSLC_DEBUG2_PRINT("ERROR: DrvDrawSetPixelRaw(%s) called with NULL ptr\n",""); return; } gslc_tsDriver* pDriver = (gslc_tsDriver*)(pGui->pvDriver); SDL_Surface* pScreen = pDriver->pSurfScreen; if (pScreen == NULL) { GSLC_DEBUG2_PRINT("ERROR: DrvDrawSetPixelRaw(%s) screen surface NULL\n",""); return; } uint8_t nBpp = pScreen->format->BytesPerPixel; // Handle any clipping if ( (nX < pScreen->clip_rect.x) || (nX >= pScreen->clip_rect.x+pScreen->clip_rect.w) || (nY < pScreen->clip_rect.y) || (nY >= pScreen->clip_rect.y+pScreen->clip_rect.h) ) { return; } // Here pPixel is the address to the pixel we want to set uint8_t *pPixel = (uint8_t *)pScreen->pixels + nY * pScreen->pitch + nX * nBpp; switch(nBpp) { case 1: *pPixel = nPixelVal; break; case 2: *(uint16_t *)pPixel = nPixelVal; break; case 3: if (SDL_BYTEORDER == SDL_BIG_ENDIAN) { pPixel[0] = (nPixelVal >> 16) & 0xff; pPixel[1] = (nPixelVal >> 8) & 0xff; pPixel[2] = nPixelVal & 0xff; } else { pPixel[0] = nPixelVal & 0xff; pPixel[1] = (nPixelVal >> 8) & 0xff; pPixel[2] = (nPixelVal >> 16) & 0xff; } break; case 4: *(uint32_t *)pPixel = nPixelVal; break; } } void gslc_DrvPasteSurface(gslc_tsGui* pGui,int16_t nX, int16_t nY, void* pvSrc, void* pvDest) { if ((pGui == NULL) || (pvSrc == NULL) || (pvDest == NULL)) { GSLC_DEBUG2_PRINT("ERROR: DrvPasteSurface(%s) called with NULL ptr\n",""); return; } SDL_Surface* pSrc = (SDL_Surface*)(pvSrc); SDL_Surface* pDest = (SDL_Surface*)(pvDest); SDL_Rect offset; offset.x = nX; offset.y = nY; SDL_BlitSurface(pSrc,NULL,pDest,&offset); } #endif // ------------------------------------------------------------------------ // Touch Functions (via external tslib) // ------------------------------------------------------------------------ #if defined(DRV_TOUCH_TSLIB) // POST: // - pDriver->pTsDev mapped to touchscreen device bool gslc_TDrvInitTouch(gslc_tsGui* pGui,const char* acDev) { if (pGui == NULL) { GSLC_DEBUG2_PRINT("ERROR: TDrvInitTouch(%s) called with NULL ptr\n",""); return false; } // Perform any driver-specific touchscreen init here // Assign default gslc_tsDriver* pDriver = (gslc_tsDriver*)(pGui->pvDriver); pDriver->pTsDev = NULL; // TODO: Consider using env "TSLIB_TSDEVICE" instead //char* pDevName = NULL; //pDevName = getenv("TSLIB_TSDEVICE"); //pDriver->pTsDev = ts_open(pDevName,1); // Open in non-blocking mode pDriver->pTsDev = ts_open(acDev,1); if (!pDriver->pTsDev) { GSLC_DEBUG2_PRINT("ERROR: TsOpen(%s) failed\n",""); return false; } if (ts_config(pDriver->pTsDev)) { GSLC_DEBUG2_PRINT("ERROR: ts_config(%s) failed\n",""); // Clear the tslib pointer so we don't try to call it again pDriver->pTsDev = NULL; return false; } return true; } bool gslc_TDrvGetTouch(gslc_tsGui* pGui,int16_t* pnX,int16_t* pnY,uint16_t* pnPress,gslc_teInputRawEvent* peInputEvent,int16_t* pnInputVal) { if (pGui == NULL) { GSLC_DEBUG2_PRINT("ERROR: TDrvGetTouch(%s) called with NULL ptr\n",""); return false; } gslc_tsDriver* pDriver = (gslc_tsDriver*)(pGui->pvDriver); // In case tslib was not loaded, exit now if (pDriver->pTsDev == NULL) { return false; } struct ts_sample pSamp; int32_t nRet = ts_read(pDriver->pTsDev,&pSamp,1); // ts_read returns the number of samples actually fetched // Since we are only requesting at most 1 sample, the return // value should either be 0 (no samples) or 1 (sample success) if (nRet > 0) { // Sample successfully fetched (*pnX) = pSamp.x; (*pnY) = pSamp.y; (*pnPress) = pSamp.pressure; (*peInputEvent) = GSLC_INPUT_TOUCH; (*pnInputVal) = 0; return true; } else { // No sample returned return false; } } #endif // DRV_TOUCH_TSLIB #endif // Compiler guard for requested driver